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
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_pulse
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.5.pre.
|
|
4
|
+
version: 0.2.5.pre.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rails Pulse
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-01-22 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -139,6 +139,34 @@ dependencies:
|
|
|
139
139
|
- - ">="
|
|
140
140
|
- !ruby/object:Gem::Version
|
|
141
141
|
version: '1.0'
|
|
142
|
+
- !ruby/object:Gem::Dependency
|
|
143
|
+
name: minitest
|
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
|
145
|
+
requirements:
|
|
146
|
+
- - ">="
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: '5.0'
|
|
149
|
+
type: :development
|
|
150
|
+
prerelease: false
|
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '5.0'
|
|
156
|
+
- !ruby/object:Gem::Dependency
|
|
157
|
+
name: ostruct
|
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - ">="
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '0'
|
|
163
|
+
type: :development
|
|
164
|
+
prerelease: false
|
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - ">="
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: '0'
|
|
142
170
|
description: Ruby on Rails performance monitoring tool that provides insights into
|
|
143
171
|
your application's performance, helping you identify bottlenecks and optimize your
|
|
144
172
|
code for better efficiency.
|
|
@@ -333,9 +361,7 @@ files:
|
|
|
333
361
|
- config/importmap.rb
|
|
334
362
|
- config/initializers/rails_pulse.rb
|
|
335
363
|
- config/routes.rb
|
|
336
|
-
- db/rails_pulse_migrate/
|
|
337
|
-
- db/rails_pulse_migrate/20250122000000_add_query_fingerprinting.rb
|
|
338
|
-
- db/rails_pulse_migrate/20250202000000_add_index_to_request_uuid.rb
|
|
364
|
+
- db/rails_pulse_migrate/20260117000000_optimize_rails_pulse_indexes.rb
|
|
339
365
|
- db/rails_pulse_schema.rb
|
|
340
366
|
- lib/generators/rails_pulse/convert_to_migrations_generator.rb
|
|
341
367
|
- lib/generators/rails_pulse/install_generator.rb
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Add background job tracking to Rails Pulse
|
|
2
|
-
class AddJobsToRailsPulse < ActiveRecord::Migration[7.0]
|
|
3
|
-
def up
|
|
4
|
-
# Create jobs table for storing job definitions
|
|
5
|
-
unless table_exists?(:rails_pulse_jobs)
|
|
6
|
-
create_table :rails_pulse_jobs do |t|
|
|
7
|
-
t.string :name, null: false, comment: "Job class name"
|
|
8
|
-
t.string :queue_name, comment: "Default queue"
|
|
9
|
-
t.text :description, comment: "Optional description"
|
|
10
|
-
t.integer :runs_count, null: false, default: 0, comment: "Cache of total runs"
|
|
11
|
-
t.integer :failures_count, null: false, default: 0, comment: "Cache of failed runs"
|
|
12
|
-
t.integer :retries_count, null: false, default: 0, comment: "Cache of retried runs"
|
|
13
|
-
t.decimal :avg_duration, precision: 15, scale: 6, comment: "Average duration in milliseconds"
|
|
14
|
-
t.text :tags, comment: "JSON array of tags"
|
|
15
|
-
t.timestamps
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
add_index :rails_pulse_jobs, :name, unique: true, name: "index_rails_pulse_jobs_on_name"
|
|
19
|
-
add_index :rails_pulse_jobs, :queue_name, name: "index_rails_pulse_jobs_on_queue"
|
|
20
|
-
add_index :rails_pulse_jobs, :runs_count, name: "index_rails_pulse_jobs_on_runs_count"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Create job_runs table for individual job executions
|
|
24
|
-
unless table_exists?(:rails_pulse_job_runs)
|
|
25
|
-
create_table :rails_pulse_job_runs do |t|
|
|
26
|
-
t.references :job, null: false, foreign_key: { to_table: :rails_pulse_jobs }, comment: "Link to job definition"
|
|
27
|
-
t.string :run_id, null: false, comment: "Adapter specific run id"
|
|
28
|
-
t.decimal :duration, precision: 15, scale: 6, comment: "Execution duration in milliseconds"
|
|
29
|
-
t.string :status, null: false, comment: "Execution status"
|
|
30
|
-
t.string :error_class, comment: "Error class name"
|
|
31
|
-
t.text :error_message, comment: "Error message"
|
|
32
|
-
t.integer :attempts, null: false, default: 0, comment: "Retry attempts"
|
|
33
|
-
t.timestamp :occurred_at, null: false, comment: "When the job started"
|
|
34
|
-
t.timestamp :enqueued_at, comment: "When the job was enqueued"
|
|
35
|
-
t.text :arguments, comment: "Serialized arguments"
|
|
36
|
-
t.string :adapter, comment: "Queue adapter"
|
|
37
|
-
t.text :tags, comment: "Execution tags"
|
|
38
|
-
t.timestamps
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
add_index :rails_pulse_job_runs, :run_id, unique: true, name: "index_rails_pulse_job_runs_on_run_id"
|
|
42
|
-
add_index :rails_pulse_job_runs, [ :job_id, :occurred_at ], name: "index_rails_pulse_job_runs_on_job_and_occurred"
|
|
43
|
-
add_index :rails_pulse_job_runs, :occurred_at, name: "index_rails_pulse_job_runs_on_occurred_at"
|
|
44
|
-
add_index :rails_pulse_job_runs, :status, name: "index_rails_pulse_job_runs_on_status"
|
|
45
|
-
add_index :rails_pulse_job_runs, [ :job_id, :status ], name: "index_rails_pulse_job_runs_on_job_and_status"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Add job_run_id to operations table if it doesn't exist
|
|
49
|
-
if table_exists?(:rails_pulse_operations) && !column_exists?(:rails_pulse_operations, :job_run_id)
|
|
50
|
-
# Make request_id nullable to allow job operations
|
|
51
|
-
change_column_null :rails_pulse_operations, :request_id, true
|
|
52
|
-
|
|
53
|
-
# Add job_run_id reference
|
|
54
|
-
add_reference :rails_pulse_operations, :job_run,
|
|
55
|
-
null: true,
|
|
56
|
-
foreign_key: { to_table: :rails_pulse_job_runs },
|
|
57
|
-
comment: "Link to a background job execution"
|
|
58
|
-
|
|
59
|
-
# Add check constraint for PostgreSQL and MySQL to ensure either request_id or job_run_id is present
|
|
60
|
-
adapter = connection.adapter_name.downcase
|
|
61
|
-
if adapter.include?("postgres") || adapter.include?("mysql")
|
|
62
|
-
execute <<-SQL
|
|
63
|
-
ALTER TABLE rails_pulse_operations
|
|
64
|
-
ADD CONSTRAINT rails_pulse_operations_request_or_job_run
|
|
65
|
-
CHECK (request_id IS NOT NULL OR job_run_id IS NOT NULL)
|
|
66
|
-
SQL
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def down
|
|
72
|
-
# Remove check constraint first
|
|
73
|
-
adapter = connection.adapter_name.downcase
|
|
74
|
-
if adapter.include?("postgres") || adapter.include?("mysql")
|
|
75
|
-
execute <<-SQL
|
|
76
|
-
ALTER TABLE rails_pulse_operations
|
|
77
|
-
DROP CONSTRAINT IF EXISTS rails_pulse_operations_request_or_job_run
|
|
78
|
-
SQL
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Remove job_run_id from operations
|
|
82
|
-
if column_exists?(:rails_pulse_operations, :job_run_id)
|
|
83
|
-
remove_reference :rails_pulse_operations, :job_run, foreign_key: { to_table: :rails_pulse_job_runs }
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Make request_id non-nullable again
|
|
87
|
-
if column_exists?(:rails_pulse_operations, :request_id)
|
|
88
|
-
change_column_null :rails_pulse_operations, :request_id, false
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Drop job tables
|
|
92
|
-
drop_table :rails_pulse_job_runs if table_exists?(:rails_pulse_job_runs)
|
|
93
|
-
drop_table :rails_pulse_jobs if table_exists?(:rails_pulse_jobs)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Add query fingerprinting to handle long SQL queries
|
|
4
|
-
# Uses MD5 hash of normalized SQL as unique identifier
|
|
5
|
-
class AddQueryFingerprinting < ActiveRecord::Migration[7.0]
|
|
6
|
-
def up
|
|
7
|
-
return unless table_exists?(:rails_pulse_queries)
|
|
8
|
-
|
|
9
|
-
# Add hashed_sql column if it doesn't exist
|
|
10
|
-
unless column_exists?(:rails_pulse_queries, :hashed_sql)
|
|
11
|
-
say "Adding hashed_sql column to rails_pulse_queries..."
|
|
12
|
-
add_column :rails_pulse_queries, :hashed_sql, :string, limit: 32
|
|
13
|
-
|
|
14
|
-
# Backfill existing records with MD5 hash
|
|
15
|
-
say "Backfilling query hashes for existing records..."
|
|
16
|
-
backfill_query_hashes
|
|
17
|
-
|
|
18
|
-
# Make it required and unique
|
|
19
|
-
say "Adding constraints and indexes..."
|
|
20
|
-
change_column_null :rails_pulse_queries, :hashed_sql, false
|
|
21
|
-
add_index :rails_pulse_queries, :hashed_sql, unique: true,
|
|
22
|
-
name: "index_rails_pulse_queries_on_hashed_sql"
|
|
23
|
-
|
|
24
|
-
# Remove old index
|
|
25
|
-
say "Removing old normalized_sql index..."
|
|
26
|
-
if index_exists?(:rails_pulse_queries, :normalized_sql, name: "index_rails_pulse_queries_on_normalized_sql")
|
|
27
|
-
remove_index :rails_pulse_queries, :normalized_sql, name: "index_rails_pulse_queries_on_normalized_sql"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Change normalized_sql to text (remove 1000 char limit)
|
|
31
|
-
say "Changing normalized_sql to text type (removing length limit)..."
|
|
32
|
-
change_column :rails_pulse_queries, :normalized_sql, :text
|
|
33
|
-
|
|
34
|
-
say "Query fingerprinting migration completed successfully!", :green
|
|
35
|
-
else
|
|
36
|
-
say "Query fingerprinting already applied. Skipping.", :yellow
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def down
|
|
41
|
-
# Prevent rollback if there are queries longer than 1000 characters
|
|
42
|
-
if has_long_queries?
|
|
43
|
-
raise ActiveRecord::IrreversibleMigration,
|
|
44
|
-
"Cannot rollback: normalized_sql contains queries longer than 1000 characters. " \
|
|
45
|
-
"Rolling back would truncate data."
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
return unless column_exists?(:rails_pulse_queries, :hashed_sql)
|
|
49
|
-
|
|
50
|
-
say "Rolling back query fingerprinting changes..."
|
|
51
|
-
|
|
52
|
-
# Restore varchar limit (safe because we checked for long queries)
|
|
53
|
-
change_column :rails_pulse_queries, :normalized_sql, :string, limit: 1000
|
|
54
|
-
|
|
55
|
-
# Restore old index
|
|
56
|
-
add_index :rails_pulse_queries, :normalized_sql, unique: true,
|
|
57
|
-
name: "index_rails_pulse_queries_on_normalized_sql", length: 191
|
|
58
|
-
|
|
59
|
-
# Remove new index
|
|
60
|
-
if index_exists?(:rails_pulse_queries, :hashed_sql, name: "index_rails_pulse_queries_on_hashed_sql")
|
|
61
|
-
remove_index :rails_pulse_queries, :hashed_sql, name: "index_rails_pulse_queries_on_hashed_sql"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Remove hashed_sql column
|
|
65
|
-
remove_column :rails_pulse_queries, :hashed_sql
|
|
66
|
-
|
|
67
|
-
say "Rollback completed.", :green
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def backfill_query_hashes
|
|
73
|
-
adapter = connection.adapter_name.downcase
|
|
74
|
-
|
|
75
|
-
if adapter.include?("postgres") || adapter.include?("mysql")
|
|
76
|
-
# Use database MD5 function for better performance
|
|
77
|
-
execute <<-SQL
|
|
78
|
-
UPDATE rails_pulse_queries
|
|
79
|
-
SET hashed_sql = MD5(normalized_sql)
|
|
80
|
-
WHERE hashed_sql IS NULL
|
|
81
|
-
SQL
|
|
82
|
-
else
|
|
83
|
-
# SQLite - use Ruby MD5 (slower but works)
|
|
84
|
-
require "digest"
|
|
85
|
-
RailsPulse::Query.where(hashed_sql: nil).find_each do |query|
|
|
86
|
-
query.update_column(:hashed_sql, Digest::MD5.hexdigest(query.normalized_sql))
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Handle potential duplicates (queries with same normalized SQL)
|
|
91
|
-
handle_duplicate_hashes
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def handle_duplicate_hashes
|
|
95
|
-
# Group queries by hash and find duplicates
|
|
96
|
-
query_groups = RailsPulse::Query
|
|
97
|
-
.select(:hashed_sql)
|
|
98
|
-
.group(:hashed_sql)
|
|
99
|
-
.having("COUNT(*) > 1")
|
|
100
|
-
.pluck(:hashed_sql)
|
|
101
|
-
|
|
102
|
-
return if query_groups.empty?
|
|
103
|
-
|
|
104
|
-
say "Found #{query_groups.size} duplicate query groups. Merging...", :yellow
|
|
105
|
-
|
|
106
|
-
query_groups.each do |hash|
|
|
107
|
-
# Get all queries with this hash, ordered by creation time
|
|
108
|
-
queries = RailsPulse::Query.where(hashed_sql: hash).order(:created_at).to_a
|
|
109
|
-
keep_query = queries.first
|
|
110
|
-
duplicate_queries = queries[1..]
|
|
111
|
-
|
|
112
|
-
duplicate_queries.each do |dup_query|
|
|
113
|
-
# Count operations before merge
|
|
114
|
-
operations_count = RailsPulse::Operation.where(query_id: dup_query.id).count
|
|
115
|
-
|
|
116
|
-
if operations_count > 0
|
|
117
|
-
say " Merging #{operations_count} operations from query ##{dup_query.id} into ##{keep_query.id}"
|
|
118
|
-
|
|
119
|
-
# Reassign operations to the kept query
|
|
120
|
-
RailsPulse::Operation.where(query_id: dup_query.id).update_all(query_id: keep_query.id)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Delete the duplicate query
|
|
124
|
-
dup_query.delete
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
say "Merged #{query_groups.size} duplicate query groups successfully.", :green
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def has_long_queries?
|
|
132
|
-
# Check if any queries exceed 1000 characters
|
|
133
|
-
adapter = connection.adapter_name.downcase
|
|
134
|
-
|
|
135
|
-
if adapter.include?("postgres")
|
|
136
|
-
result = execute("SELECT EXISTS(SELECT 1 FROM rails_pulse_queries WHERE LENGTH(normalized_sql) > 1000)")
|
|
137
|
-
# Handle both Rails 7.2 and 8.0 result formats
|
|
138
|
-
result.first.is_a?(Hash) ? result.first["exists"] == "t" : result.first[0] == "t"
|
|
139
|
-
elsif adapter.include?("mysql")
|
|
140
|
-
result = execute("SELECT EXISTS(SELECT 1 FROM rails_pulse_queries WHERE LENGTH(normalized_sql) > 1000) as result")
|
|
141
|
-
# Handle both result formats
|
|
142
|
-
result.first.is_a?(Hash) ? result.first["result"] == 1 : result.first[0] == 1
|
|
143
|
-
else
|
|
144
|
-
# SQLite
|
|
145
|
-
result = execute("SELECT COUNT(*) as count FROM rails_pulse_queries WHERE LENGTH(normalized_sql) > 1000")
|
|
146
|
-
count = result.first.is_a?(Hash) ? result.first["count"] : result.first[0]
|
|
147
|
-
count.to_i > 0
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Add index to rails_pulse_requests.request_uuid for efficient lookups
|
|
2
|
-
class AddIndexToRequestUuid < ActiveRecord::Migration[7.0]
|
|
3
|
-
def up
|
|
4
|
-
unless index_exists?(:rails_pulse_requests, :request_uuid)
|
|
5
|
-
add_index :rails_pulse_requests, :request_uuid, unique: true, name: "index_rails_pulse_requests_on_request_uuid"
|
|
6
|
-
end
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def down
|
|
10
|
-
if index_exists?(:rails_pulse_requests, :request_uuid)
|
|
11
|
-
remove_index :rails_pulse_requests, name: "index_rails_pulse_requests_on_request_uuid"
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|