rails_pulse 0.2.5.pre.4 → 0.2.5.pre.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/Rakefile +1 -1
- data/app/controllers/concerns/zoom_range_concern.rb +16 -7
- data/app/helpers/rails_pulse/chart_formatters.rb +4 -4
- data/app/javascript/rails_pulse/controllers/chart_controller.js +43 -20
- data/app/models/rails_pulse/queries/charts/average_query_times.rb +3 -1
- data/app/models/rails_pulse/routes/charts/average_response_times.rb +3 -1
- data/app/views/rails_pulse/queries/show.html.erb +8 -6
- data/app/views/rails_pulse/routes/show.html.erb +8 -6
- data/db/rails_pulse_migrate/20260117000000_optimize_rails_pulse_indexes.rb +103 -0
- data/db/rails_pulse_schema.rb +4 -10
- data/lib/generators/rails_pulse/upgrade_generator.rb +2 -4
- data/lib/rails_pulse/middleware/request_collector.rb +2 -2
- data/lib/rails_pulse/version.rb +1 -1
- data/public/rails-pulse-assets/csp-test.js +107 -97
- data/public/rails-pulse-assets/rails-pulse.js +1 -1
- data/public/rails-pulse-assets/rails-pulse.js.map +2 -2
- metadata +31 -5
- data/db/rails_pulse_migrate/20250113000000_add_jobs_to_rails_pulse.rb +0 -95
- data/db/rails_pulse_migrate/20250122000000_add_query_fingerprinting.rb +0 -150
- data/db/rails_pulse_migrate/20250202000000_add_index_to_request_uuid.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b1d65c3afe59858af712a994ef9310c0851485cc6a2c09234fcf0b84d63179c
|
|
4
|
+
data.tar.gz: f5a6e525c6d605b6fa0edc882c8ea7fc672ea5b4bfd803bfbee1508d2c9ff9ec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d391f448da6724d1e095df6534753febd8b696c62db69de9c75618e891889f9a1b8ae7b6dbcf983cf8caa033901023e4c16f868aa7896c62b90ae901388a5b3
|
|
7
|
+
data.tar.gz: e1f190bbf54846a915f875e2381ae5890c4ff9d97baccf9441d4cdf026ccbd55c36d6e2b8b987dc878376d42643884d37b0d8afa4ef713dae7c3cebd19de6f25
|
data/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
**Real-time performance monitoring and debugging for Rails applications**
|
|
7
7
|
|
|
8
8
|

|
|
9
|
-

|
|
9
|
+

|
|
10
10
|

|
|
11
|
-

|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
---
|
|
@@ -786,7 +786,7 @@ rails test:matrix
|
|
|
786
786
|
|
|
787
787
|
This command tests all combinations locally:
|
|
788
788
|
- **Databases**: SQLite3, PostgreSQL, MySQL2 (local testing only)
|
|
789
|
-
- **Rails versions**: 7.2, 8.0
|
|
789
|
+
- **Rails versions**: 7.2, 8.0, 8.1
|
|
790
790
|
|
|
791
791
|
**Note**: CI only tests SQLite3 + PostgreSQL for reliability. MySQL is available for local testing but excluded from CI due to flakiness.
|
|
792
792
|
|
|
@@ -840,7 +840,7 @@ DB=mysql2 FORCE_DB_CONFIG=true rails test:all
|
|
|
840
840
|
|
|
841
841
|
GitHub Actions CI automatically tests:
|
|
842
842
|
- **Databases**: SQLite3, PostgreSQL only (MySQL excluded for reliability)
|
|
843
|
-
- **Rails versions**: 7.2, 8.0
|
|
843
|
+
- **Rails versions**: 7.2, 8.0, 8.1
|
|
844
844
|
- **Environment**: Uses memory SQLite and PostgreSQL service
|
|
845
845
|
|
|
846
846
|
**Local vs CI differences**:
|
data/Rakefile
CHANGED
|
@@ -189,7 +189,7 @@ end
|
|
|
189
189
|
desc "Test all database and Rails version combinations"
|
|
190
190
|
task :test_matrix do
|
|
191
191
|
databases = %w[sqlite3 postgresql mysql2]
|
|
192
|
-
rails_versions = %w[rails-7-2 rails-8-0]
|
|
192
|
+
rails_versions = %w[rails-7-2 rails-8-0 rails-8-1]
|
|
193
193
|
|
|
194
194
|
failed_combinations = []
|
|
195
195
|
total_combinations = databases.size * rails_versions.size
|
|
@@ -33,17 +33,21 @@ module ZoomRangeConcern
|
|
|
33
33
|
private
|
|
34
34
|
|
|
35
35
|
def normalize_column_time(column_time, main_start_time, main_end_time)
|
|
36
|
+
# Convert from JavaScript milliseconds to Unix seconds
|
|
37
|
+
# Chart data uses milliseconds (timestamp * 1000), so divide by 1000 to get seconds
|
|
38
|
+
column_time_seconds = column_time / 1000
|
|
39
|
+
|
|
36
40
|
# Determine period type based on main time range (same logic as ChartTableConcern)
|
|
37
41
|
time_diff_hours = (main_end_time - main_start_time) / 3600.0
|
|
38
42
|
|
|
39
43
|
if time_diff_hours <= 25
|
|
40
44
|
# Hourly period - normalize to beginning/end of hour
|
|
41
|
-
column_time_obj = Time.zone&.at(
|
|
45
|
+
column_time_obj = Time.zone&.at(column_time_seconds) || Time.at(column_time_seconds)
|
|
42
46
|
start_time = column_time_obj&.beginning_of_hour || column_time_obj
|
|
43
47
|
end_time = column_time_obj&.end_of_hour || column_time_obj
|
|
44
48
|
else
|
|
45
49
|
# Daily period - normalize to beginning/end of day
|
|
46
|
-
column_time_obj = Time.zone&.at(
|
|
50
|
+
column_time_obj = Time.zone&.at(column_time_seconds) || Time.at(column_time_seconds)
|
|
47
51
|
start_time = column_time_obj&.beginning_of_day || column_time_obj
|
|
48
52
|
end_time = column_time_obj&.end_of_day || column_time_obj
|
|
49
53
|
end
|
|
@@ -52,16 +56,21 @@ module ZoomRangeConcern
|
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
def normalize_zoom_times(start_time, end_time)
|
|
55
|
-
|
|
59
|
+
# Convert from JavaScript milliseconds to Unix seconds
|
|
60
|
+
# Chart data uses milliseconds (timestamp * 1000), so divide by 1000 to get seconds
|
|
61
|
+
start_time_seconds = start_time / 1000
|
|
62
|
+
end_time_seconds = end_time / 1000
|
|
63
|
+
|
|
64
|
+
time_diff = (end_time_seconds - start_time_seconds) / 3600.0
|
|
56
65
|
|
|
57
66
|
if time_diff <= 25
|
|
58
|
-
start_time_obj = Time.zone&.at(
|
|
59
|
-
end_time_obj = Time.zone&.at(
|
|
67
|
+
start_time_obj = Time.zone&.at(start_time_seconds) || Time.at(start_time_seconds)
|
|
68
|
+
end_time_obj = Time.zone&.at(end_time_seconds) || Time.at(end_time_seconds)
|
|
60
69
|
start_time = start_time_obj&.beginning_of_hour || start_time_obj
|
|
61
70
|
end_time = end_time_obj&.end_of_hour || end_time_obj
|
|
62
71
|
else
|
|
63
|
-
start_time_obj = Time.zone&.at(
|
|
64
|
-
end_time_obj = Time.zone&.at(
|
|
72
|
+
start_time_obj = Time.zone&.at(start_time_seconds) || Time.at(start_time_seconds)
|
|
73
|
+
end_time_obj = Time.zone&.at(end_time_seconds) || Time.at(end_time_seconds)
|
|
65
74
|
start_time = start_time_obj&.beginning_of_day || start_time_obj
|
|
66
75
|
end_time = end_time_obj&.end_of_day || end_time_obj
|
|
67
76
|
end
|
|
@@ -4,14 +4,14 @@ module RailsPulse
|
|
|
4
4
|
if time_diff_hours <= 25
|
|
5
5
|
<<~JS
|
|
6
6
|
function(value) {
|
|
7
|
-
const date = new Date(value
|
|
7
|
+
const date = new Date(value);
|
|
8
8
|
return date.getHours().toString().padStart(2, '0') + ':00';
|
|
9
9
|
}
|
|
10
10
|
JS
|
|
11
11
|
else
|
|
12
12
|
<<~JS
|
|
13
13
|
function(value) {
|
|
14
|
-
const date = new Date(value
|
|
14
|
+
const date = new Date(value);
|
|
15
15
|
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
16
16
|
}
|
|
17
17
|
JS
|
|
@@ -23,7 +23,7 @@ module RailsPulse
|
|
|
23
23
|
<<~JS
|
|
24
24
|
function(params) {
|
|
25
25
|
const data = params[0];
|
|
26
|
-
const date = new Date(data.axisValue
|
|
26
|
+
const date = new Date(data.axisValue);
|
|
27
27
|
const dateString = date.getHours().toString().padStart(2, '0') + ':00';
|
|
28
28
|
return `${dateString} <br /> ${data.marker} ${parseInt(data.data)} ms`;
|
|
29
29
|
}
|
|
@@ -32,7 +32,7 @@ module RailsPulse
|
|
|
32
32
|
<<~JS
|
|
33
33
|
function(params) {
|
|
34
34
|
const data = params[0];
|
|
35
|
-
const date = new Date(data.axisValue
|
|
35
|
+
const date = new Date(data.axisValue);
|
|
36
36
|
const dateString = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
37
37
|
return `${dateString} <br /> ${data.marker} ${parseInt(data.data)} ms`;
|
|
38
38
|
}
|
|
@@ -213,20 +213,35 @@ export default class extends Controller {
|
|
|
213
213
|
return value
|
|
214
214
|
},
|
|
215
215
|
|
|
216
|
-
// Date only
|
|
216
|
+
// Date only (formatted as "Mon DD" to match Rails Pulse formatters)
|
|
217
217
|
'date': (value) => {
|
|
218
218
|
if (typeof value === 'number' || typeof value === 'string') {
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
// Convert to number if string
|
|
220
|
+
const numValue = typeof value === 'string' ? parseInt(value) : value
|
|
221
|
+
const date = new Date(numValue)
|
|
222
|
+
|
|
223
|
+
if (isNaN(date.getTime())) {
|
|
224
|
+
return value.toString()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
|
221
228
|
}
|
|
222
229
|
return value
|
|
223
230
|
},
|
|
224
231
|
|
|
225
|
-
// Time only
|
|
232
|
+
// Time only (formatted as "HH:00" to match Rails Pulse formatters)
|
|
226
233
|
'time': (value) => {
|
|
227
234
|
if (typeof value === 'number' || typeof value === 'string') {
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
// Convert to number if string
|
|
236
|
+
const numValue = typeof value === 'string' ? parseInt(value) : value
|
|
237
|
+
const date = new Date(numValue)
|
|
238
|
+
|
|
239
|
+
const hours = date.getHours()
|
|
240
|
+
if (isNaN(hours)) {
|
|
241
|
+
return value.toString()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return hours.toString().padStart(2, '0') + ':00'
|
|
230
245
|
}
|
|
231
246
|
return value
|
|
232
247
|
},
|
|
@@ -248,33 +263,41 @@ export default class extends Controller {
|
|
|
248
263
|
}
|
|
249
264
|
}
|
|
250
265
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
266
|
+
// IMPORTANT: Check for SPECIFIC patterns FIRST before generic keyword matching
|
|
267
|
+
// The order matters! More specific patterns should be checked before generic ones.
|
|
268
|
+
|
|
269
|
+
// Check for specific function calls that uniquely identify the formatter type
|
|
270
|
+
if (formatterString.includes('getHours')) {
|
|
271
|
+
return SAFE_FORMATTERS.time
|
|
257
272
|
}
|
|
258
273
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
274
|
+
if (formatterString.includes('toLocaleDateString')) {
|
|
275
|
+
return SAFE_FORMATTERS.date
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (formatterString.includes('toLocaleTimeString')) {
|
|
279
|
+
return SAFE_FORMATTERS.time
|
|
262
280
|
}
|
|
263
281
|
|
|
264
282
|
if (formatterString.includes('toLocaleString')) {
|
|
265
283
|
return SAFE_FORMATTERS.number_delimited
|
|
266
284
|
}
|
|
267
285
|
|
|
268
|
-
if (formatterString.includes('
|
|
269
|
-
return SAFE_FORMATTERS.
|
|
286
|
+
if (formatterString.includes('toFixed(2)') && formatterString.includes('ms')) {
|
|
287
|
+
return SAFE_FORMATTERS.duration_ms
|
|
270
288
|
}
|
|
271
289
|
|
|
272
|
-
|
|
273
|
-
|
|
290
|
+
// Try to match the formatter string to a known safe pattern by key name
|
|
291
|
+
// This is less specific and should come after the function call checks above
|
|
292
|
+
for (const [key, formatter] of Object.entries(SAFE_FORMATTERS)) {
|
|
293
|
+
if (formatterString.includes(key) ||
|
|
294
|
+
formatterString.includes(key.replace('_', ''))) {
|
|
295
|
+
return formatter
|
|
296
|
+
}
|
|
274
297
|
}
|
|
275
298
|
|
|
276
299
|
// Default: return a safe identity function that just returns the value
|
|
277
|
-
console.warn('[RailsPulse] Unknown formatter pattern, using identity function:', formatterString)
|
|
300
|
+
console.warn('[RailsPulse] Unknown formatter pattern, using identity function:', formatterString.substring(0, 100))
|
|
278
301
|
return (value) => value
|
|
279
302
|
}
|
|
280
303
|
|
|
@@ -24,10 +24,12 @@ module RailsPulse
|
|
|
24
24
|
.transform_keys(&:to_i)
|
|
25
25
|
|
|
26
26
|
# Pad missing data points with zeros
|
|
27
|
+
# Convert timestamps to milliseconds for JavaScript Date compatibility
|
|
27
28
|
step = @period_type == :hour ? 1.hour : 1.day
|
|
28
29
|
data = {}
|
|
29
30
|
(@start_time.to_i..@end_time.to_i).step(step) do |timestamp|
|
|
30
|
-
|
|
31
|
+
# Multiply by 1000 to convert Unix seconds to JavaScript milliseconds
|
|
32
|
+
data[timestamp.to_i * 1000] = summaries[timestamp.to_i].to_f.round(2)
|
|
31
33
|
end
|
|
32
34
|
data
|
|
33
35
|
end
|
|
@@ -29,10 +29,12 @@ module RailsPulse
|
|
|
29
29
|
.transform_keys(&:to_i)
|
|
30
30
|
|
|
31
31
|
# Pad missing data points with zeros
|
|
32
|
+
# Convert timestamps to milliseconds for JavaScript Date compatibility
|
|
32
33
|
step = @period_type == :hour ? 1.hour : 1.day
|
|
33
34
|
data = {}
|
|
34
35
|
(@start_time.to_i..@end_time.to_i).step(step) do |timestamp|
|
|
35
|
-
|
|
36
|
+
# Multiply by 1000 to convert Unix seconds to JavaScript milliseconds
|
|
37
|
+
data[timestamp.to_i * 1000] = summaries[timestamp.to_i].to_f.round(2)
|
|
36
38
|
end
|
|
37
39
|
data
|
|
38
40
|
end
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
<% end %>
|
|
28
28
|
|
|
29
|
-
<%
|
|
29
|
+
<% unless turbo_frame_request? %>
|
|
30
30
|
<% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
|
|
31
31
|
<div
|
|
32
32
|
class="chart-container chart-container--slim"
|
|
@@ -52,14 +52,16 @@
|
|
|
52
52
|
) %>
|
|
53
53
|
</div>
|
|
54
54
|
<% end %>
|
|
55
|
+
<% end %>
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
<%= turbo_frame_tag :index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
|
58
|
+
<% if @table_data && @table_data.any? %>
|
|
57
59
|
<%= render 'rails_pulse/queries/show_table' %>
|
|
60
|
+
<% else %>
|
|
61
|
+
<%= render 'rails_pulse/components/empty_state',
|
|
62
|
+
title: 'No query responses found for the selected filters.',
|
|
63
|
+
description: 'Try adjusting your time range or filters to see results.' %>
|
|
58
64
|
<% end %>
|
|
59
|
-
<% else %>
|
|
60
|
-
<%= render 'rails_pulse/components/empty_state',
|
|
61
|
-
title: 'No query responses found for the selected filters.',
|
|
62
|
-
description: 'Try adjusting your time range or filters to see results.' %>
|
|
63
65
|
<% end %>
|
|
64
66
|
<% end %>
|
|
65
67
|
</div>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
</div>
|
|
30
30
|
<% end %>
|
|
31
31
|
|
|
32
|
-
<%
|
|
32
|
+
<% unless turbo_frame_request? %>
|
|
33
33
|
<% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
|
|
34
34
|
<div
|
|
35
35
|
class="chart-container chart-container--slim"
|
|
@@ -55,14 +55,16 @@
|
|
|
55
55
|
) %>
|
|
56
56
|
</div>
|
|
57
57
|
<% end %>
|
|
58
|
+
<% end %>
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
<%= turbo_frame_tag :index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
|
61
|
+
<% if @table_data && @table_data.any? %>
|
|
60
62
|
<%= render 'rails_pulse/routes/requests_table' %>
|
|
63
|
+
<% else %>
|
|
64
|
+
<%= render 'rails_pulse/components/empty_state',
|
|
65
|
+
title: 'No route requests found for the selected filters.',
|
|
66
|
+
description: 'Try adjusting your time range or filters to see results.' %>
|
|
61
67
|
<% end %>
|
|
62
|
-
<% else %>
|
|
63
|
-
<%= render 'rails_pulse/components/empty_state',
|
|
64
|
-
title: 'No route requests found for the selected filters.',
|
|
65
|
-
description: 'Try adjusting your time range or filters to see results.' %>
|
|
66
68
|
<% end %>
|
|
67
69
|
<% end %>
|
|
68
70
|
</div>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class OptimizeRailsPulseIndexes < ActiveRecord::Migration[7.0]
|
|
4
|
+
def up
|
|
5
|
+
# Remove redundant indexes that are covered by composite indexes
|
|
6
|
+
# These were identified by PgHero as being redundant
|
|
7
|
+
|
|
8
|
+
# Operations table - remove 3 redundant indexes
|
|
9
|
+
if index_exists?(:rails_pulse_operations, :created_at, name: "idx_operations_created_at")
|
|
10
|
+
remove_index :rails_pulse_operations, :created_at, name: :idx_operations_created_at, **index_options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if index_exists?(:rails_pulse_operations, :occurred_at, name: "index_rails_pulse_operations_on_occurred_at")
|
|
14
|
+
remove_index :rails_pulse_operations, :occurred_at, name: :index_rails_pulse_operations_on_occurred_at, **index_options
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if index_exists?(:rails_pulse_operations, :query_id, name: "index_rails_pulse_operations_on_query_id")
|
|
18
|
+
remove_index :rails_pulse_operations, :query_id, name: :index_rails_pulse_operations_on_query_id, **index_options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Requests table - remove 2 redundant indexes
|
|
22
|
+
if index_exists?(:rails_pulse_requests, :created_at, name: "idx_requests_created_at")
|
|
23
|
+
remove_index :rails_pulse_requests, :created_at, name: :idx_requests_created_at, **index_options
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if index_exists?(:rails_pulse_requests, :route_id, name: "index_rails_pulse_requests_on_route_id")
|
|
27
|
+
remove_index :rails_pulse_requests, :route_id, name: :index_rails_pulse_requests_on_route_id, **index_options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Summaries table - remove 1 redundant index
|
|
31
|
+
if index_exists?(:rails_pulse_summaries, [ :summarizable_type, :summarizable_id ], name: "index_rails_pulse_summaries_on_summarizable")
|
|
32
|
+
remove_index :rails_pulse_summaries, [ :summarizable_type, :summarizable_id ], name: :index_rails_pulse_summaries_on_summarizable, **index_options
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Add missing indexes for better query performance
|
|
36
|
+
unless index_exists?(:rails_pulse_summaries, :summarizable_id, name: "index_rails_pulse_summaries_on_summarizable_id")
|
|
37
|
+
add_index :rails_pulse_summaries, :summarizable_id, name: :index_rails_pulse_summaries_on_summarizable_id, **index_options
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unless index_exists?(:rails_pulse_routes, :path, name: "index_rails_pulse_routes_on_path")
|
|
41
|
+
add_index :rails_pulse_routes, :path, name: :index_rails_pulse_routes_on_path, **index_options
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
unless index_exists?(:rails_pulse_summaries, :period_start, name: "index_rails_pulse_summaries_on_period_start")
|
|
45
|
+
add_index :rails_pulse_summaries, :period_start, name: :index_rails_pulse_summaries_on_period_start, **index_options
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def down
|
|
50
|
+
# Restore the removed indexes
|
|
51
|
+
unless index_exists?(:rails_pulse_operations, :created_at, name: "idx_operations_created_at")
|
|
52
|
+
add_index :rails_pulse_operations, :created_at, name: :idx_operations_created_at, **index_options
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
unless index_exists?(:rails_pulse_operations, :occurred_at, name: "index_rails_pulse_operations_on_occurred_at")
|
|
56
|
+
add_index :rails_pulse_operations, :occurred_at, name: :index_rails_pulse_operations_on_occurred_at, **index_options
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
unless index_exists?(:rails_pulse_operations, :query_id, name: "index_rails_pulse_operations_on_query_id")
|
|
60
|
+
add_index :rails_pulse_operations, :query_id, name: :index_rails_pulse_operations_on_query_id, **index_options
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
unless index_exists?(:rails_pulse_requests, :created_at, name: "idx_requests_created_at")
|
|
64
|
+
add_index :rails_pulse_requests, :created_at, name: :idx_requests_created_at, **index_options
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
unless index_exists?(:rails_pulse_requests, :route_id, name: "index_rails_pulse_requests_on_route_id")
|
|
68
|
+
add_index :rails_pulse_requests, :route_id, name: :index_rails_pulse_requests_on_route_id, **index_options
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless index_exists?(:rails_pulse_summaries, [ :summarizable_type, :summarizable_id ], name: "index_rails_pulse_summaries_on_summarizable")
|
|
72
|
+
add_index :rails_pulse_summaries, [ :summarizable_type, :summarizable_id ], name: :index_rails_pulse_summaries_on_summarizable, **index_options
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Remove the added indexes
|
|
76
|
+
if index_exists?(:rails_pulse_summaries, :summarizable_id, name: "index_rails_pulse_summaries_on_summarizable_id")
|
|
77
|
+
remove_index :rails_pulse_summaries, :summarizable_id, name: :index_rails_pulse_summaries_on_summarizable_id, **index_options
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if index_exists?(:rails_pulse_routes, :path, name: "index_rails_pulse_routes_on_path")
|
|
81
|
+
remove_index :rails_pulse_routes, :path, name: :index_rails_pulse_routes_on_path, **index_options
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if index_exists?(:rails_pulse_summaries, :period_start, name: "index_rails_pulse_summaries_on_period_start")
|
|
85
|
+
remove_index :rails_pulse_summaries, :period_start, name: :index_rails_pulse_summaries_on_period_start, **index_options
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def index_options
|
|
92
|
+
# Use concurrent indexing for PostgreSQL, standard for others
|
|
93
|
+
if postgresql?
|
|
94
|
+
{ algorithm: :concurrently }
|
|
95
|
+
else
|
|
96
|
+
{}
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def postgresql?
|
|
101
|
+
connection.adapter_name.downcase.include?("postgres")
|
|
102
|
+
end
|
|
103
|
+
end
|
data/db/rails_pulse_schema.rb
CHANGED
|
@@ -35,6 +35,7 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
connection.add_index :rails_pulse_routes, [ :method, :path ], unique: true, name: "index_rails_pulse_routes_on_method_and_path"
|
|
38
|
+
connection.add_index :rails_pulse_routes, :path, name: "index_rails_pulse_routes_on_path"
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
unless connection.table_exists?(:rails_pulse_queries)
|
|
@@ -121,7 +122,7 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
121
122
|
connection.create_table :rails_pulse_operations do |t|
|
|
122
123
|
t.references :request, null: true, foreign_key: { to_table: :rails_pulse_requests }, comment: "Link to the request"
|
|
123
124
|
t.references :job_run, null: true, foreign_key: { to_table: :rails_pulse_job_runs }, comment: "Link to a background job execution"
|
|
124
|
-
t.references :query, foreign_key: { to_table: :rails_pulse_queries }, index:
|
|
125
|
+
t.references :query, foreign_key: { to_table: :rails_pulse_queries }, index: false, comment: "Link to the normalized SQL query"
|
|
125
126
|
t.string :operation_type, null: false, comment: "Type of operation (e.g., database, view, gem_call)"
|
|
126
127
|
t.string :label, null: false, comment: "Descriptive name (e.g., SELECT FROM users WHERE id = 1, render layout)"
|
|
127
128
|
t.decimal :duration, precision: 15, scale: 6, null: false, comment: "Operation duration in milliseconds"
|
|
@@ -132,7 +133,6 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
132
133
|
end
|
|
133
134
|
|
|
134
135
|
connection.add_index :rails_pulse_operations, :operation_type, name: "index_rails_pulse_operations_on_operation_type"
|
|
135
|
-
connection.add_index :rails_pulse_operations, :occurred_at, name: "index_rails_pulse_operations_on_occurred_at"
|
|
136
136
|
connection.add_index :rails_pulse_operations, [ :query_id, :occurred_at ], name: "index_rails_pulse_operations_on_query_and_time"
|
|
137
137
|
connection.add_index :rails_pulse_operations, [ :query_id, :duration, :occurred_at ], name: "index_rails_pulse_operations_query_performance"
|
|
138
138
|
connection.add_index :rails_pulse_operations, [ :occurred_at, :duration, :operation_type ], name: "index_rails_pulse_operations_on_time_duration_type"
|
|
@@ -184,6 +184,8 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
184
184
|
name: "idx_pulse_summaries_unique"
|
|
185
185
|
connection.add_index :rails_pulse_summaries, [ :period_type, :period_start ], name: "index_rails_pulse_summaries_on_period"
|
|
186
186
|
connection.add_index :rails_pulse_summaries, :created_at, name: "index_rails_pulse_summaries_on_created_at"
|
|
187
|
+
connection.add_index :rails_pulse_summaries, :summarizable_id, name: "index_rails_pulse_summaries_on_summarizable_id"
|
|
188
|
+
connection.add_index :rails_pulse_summaries, :period_start, name: "index_rails_pulse_summaries_on_period_start"
|
|
187
189
|
end
|
|
188
190
|
|
|
189
191
|
# Add indexes to existing tables for efficient aggregation
|
|
@@ -191,18 +193,10 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
191
193
|
connection.add_index :rails_pulse_requests, [ :created_at, :route_id ], name: "idx_requests_for_aggregation"
|
|
192
194
|
end
|
|
193
195
|
|
|
194
|
-
unless connection.index_exists?(:rails_pulse_requests, :created_at, name: "idx_requests_created_at")
|
|
195
|
-
connection.add_index :rails_pulse_requests, :created_at, name: "idx_requests_created_at"
|
|
196
|
-
end
|
|
197
|
-
|
|
198
196
|
unless connection.index_exists?(:rails_pulse_operations, [ :created_at, :query_id ], name: "idx_operations_for_aggregation")
|
|
199
197
|
connection.add_index :rails_pulse_operations, [ :created_at, :query_id ], name: "idx_operations_for_aggregation"
|
|
200
198
|
end
|
|
201
199
|
|
|
202
|
-
unless connection.index_exists?(:rails_pulse_operations, :created_at, name: "idx_operations_created_at")
|
|
203
|
-
connection.add_index :rails_pulse_operations, :created_at, name: "idx_operations_created_at"
|
|
204
|
-
end
|
|
205
|
-
|
|
206
200
|
# Log successful creation
|
|
207
201
|
created_tables = required_tables.select { |table| connection.table_exists?(table) }
|
|
208
202
|
newly_created = created_tables - existing_tables
|
|
@@ -173,8 +173,7 @@ module RailsPulse
|
|
|
173
173
|
say "\nMigrations copied successfully!", :green
|
|
174
174
|
say "\nNext steps:", :green
|
|
175
175
|
say "1. Run migrations for the rails_pulse database:"
|
|
176
|
-
say " rails db:migrate
|
|
177
|
-
say " OR manually run the migration files in db/rails_pulse_migrate/"
|
|
176
|
+
say " rails db:migrate:rails_pulse"
|
|
178
177
|
say "2. Restart your Rails server"
|
|
179
178
|
else
|
|
180
179
|
# Fall back to detecting missing columns
|
|
@@ -203,8 +202,7 @@ module RailsPulse
|
|
|
203
202
|
|
|
204
203
|
Next steps:
|
|
205
204
|
1. Run migrations for the rails_pulse database:
|
|
206
|
-
rails db:migrate
|
|
207
|
-
OR manually run the migration files in db/rails_pulse_migrate/
|
|
205
|
+
rails db:migrate:rails_pulse
|
|
208
206
|
2. Restart your Rails server
|
|
209
207
|
|
|
210
208
|
This migration will add: #{missing_columns.keys.join(', ')}
|
|
@@ -31,8 +31,8 @@ module RailsPulse
|
|
|
31
31
|
return result
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# Clear any previous request data
|
|
35
|
-
RequestStore.store[:rails_pulse_request_id] =
|
|
34
|
+
# Clear any previous request data and set a placeholder ID
|
|
35
|
+
RequestStore.store[:rails_pulse_request_id] = SecureRandom.uuid
|
|
36
36
|
RequestStore.store[:rails_pulse_operations] = []
|
|
37
37
|
|
|
38
38
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
data/lib/rails_pulse/version.rb
CHANGED