rails_pulse 0.4.2 → 0.5.0.pre.1
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/CHANGELOG.md +33 -1
- data/README.md +15 -4
- data/app/assets/stylesheets/rails_pulse/components/chart.css +11 -0
- data/app/assets/stylesheets/rails_pulse/components/descriptive_list.css +19 -1
- data/app/assets/stylesheets/rails_pulse/components/row.css +3 -17
- data/app/controllers/concerns/chart_table_concern.rb +28 -69
- data/app/controllers/concerns/deployment_markers_concern.rb +3 -1
- data/app/controllers/concerns/metric_card_concern.rb +6 -7
- data/app/controllers/rails_pulse/api/v1/base_controller.rb +130 -0
- data/app/controllers/rails_pulse/api/v1/capabilities_controller.rb +27 -0
- data/app/controllers/rails_pulse/api/v1/coverage_controller.rb +158 -0
- data/app/controllers/rails_pulse/api/v1/deployments_controller.rb +24 -0
- data/app/controllers/rails_pulse/api/v1/exceptions_controller.rb +76 -0
- data/app/controllers/rails_pulse/api/v1/job_runs_controller.rb +34 -0
- data/app/controllers/rails_pulse/api/v1/jobs_controller.rb +172 -0
- data/app/controllers/rails_pulse/api/v1/queries_controller.rb +114 -0
- data/app/controllers/rails_pulse/api/v1/requests_controller.rb +58 -0
- data/app/controllers/rails_pulse/api/v1/routes_controller.rb +98 -0
- data/app/controllers/rails_pulse/application_controller.rb +1 -1
- data/app/controllers/rails_pulse/dashboard_controller.rb +20 -20
- data/app/controllers/rails_pulse/deployments_controller.rb +23 -13
- data/app/controllers/rails_pulse/exceptions_controller.rb +10 -12
- data/app/controllers/rails_pulse/jobs_controller.rb +1 -2
- data/app/controllers/rails_pulse/queries_controller.rb +6 -5
- data/app/controllers/rails_pulse/requests_controller.rb +15 -14
- data/app/controllers/rails_pulse/routes_controller.rb +4 -8
- data/app/helpers/rails_pulse/application_helper.rb +15 -0
- data/app/helpers/rails_pulse/chart_helper.rb +9 -2
- data/app/helpers/rails_pulse/formatting_helper.rb +11 -12
- data/app/javascript/rails_pulse/controllers/chart_controller.js +80 -43
- data/app/jobs/rails_pulse/backfill_summaries_job.rb +4 -2
- data/app/models/concerns/rails_pulse/has_metadata.rb +15 -0
- data/app/models/concerns/rails_pulse/taggable.rb +5 -12
- data/app/models/rails_pulse/cards/base.rb +20 -11
- data/app/models/rails_pulse/charts/base.rb +2 -6
- data/app/models/rails_pulse/charts/percentile_chart_base.rb +3 -4
- data/app/models/rails_pulse/dashboard/charts/response_time_percentiles.rb +4 -5
- data/app/models/rails_pulse/dashboard/charts/throughput_and_errors.rb +4 -5
- data/app/models/rails_pulse/dashboard/concerns/time_range_helper.rb +1 -2
- data/app/models/rails_pulse/dashboard/health_summary.rb +30 -3
- data/app/models/rails_pulse/dashboard/needs_attention.rb +2 -3
- data/app/models/rails_pulse/dashboard/storage_pressure.rb +34 -4
- data/app/models/rails_pulse/dashboard/storage_status.rb +36 -0
- data/app/models/rails_pulse/deployment.rb +2 -7
- data/app/models/rails_pulse/event.rb +40 -0
- data/app/models/rails_pulse/exception_occurrence.rb +1 -1
- data/app/models/rails_pulse/exceptions/cards/exception_rate.rb +3 -6
- data/app/models/rails_pulse/exceptions/cards/open_groups.rb +1 -4
- data/app/models/rails_pulse/exceptions/cards/total_occurrences.rb +3 -6
- data/app/models/rails_pulse/exceptions/charts/occurrence_volume.rb +4 -5
- data/app/models/rails_pulse/job_run.rb +1 -1
- data/app/models/rails_pulse/jobs/cards/failure_rate.rb +8 -14
- data/app/models/rails_pulse/jobs/cards/p95_duration.rb +8 -14
- data/app/models/rails_pulse/jobs/cards/total_runs.rb +5 -11
- data/app/models/rails_pulse/jobs/charts/duration.rb +1 -1
- data/app/models/rails_pulse/jobs/charts/execution_volume.rb +1 -1
- data/app/models/rails_pulse/jobs/charts/failure_rate.rb +1 -1
- data/app/models/rails_pulse/like_pattern.rb +29 -0
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +6 -12
- data/app/models/rails_pulse/queries/cards/database_load.rb +0 -9
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +5 -11
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +6 -12
- data/app/models/rails_pulse/queries/charts/database_load.rb +5 -6
- data/app/models/rails_pulse/queries/charts/execution_volume.rb +1 -1
- data/app/models/rails_pulse/request.rb +1 -1
- data/app/models/rails_pulse/routes/cards/error_rates.rb +5 -15
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +5 -17
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +5 -17
- data/app/models/rails_pulse/routes/charts/error_rate.rb +1 -1
- data/app/models/rails_pulse/routes/charts/request_volume.rb +1 -1
- data/app/models/rails_pulse/summary.rb +5 -0
- data/app/models/rails_pulse/tables/base.rb +5 -6
- data/app/models/rails_pulse/time_range.rb +309 -0
- data/app/models/rails_pulse/writer_heartbeat.rb +86 -0
- data/app/serializers/rails_pulse/api/v1/deployment_serializer.rb +20 -0
- data/app/serializers/rails_pulse/api/v1/exception_group_serializer.rb +23 -0
- data/app/serializers/rails_pulse/api/v1/exception_occurrence_serializer.rb +22 -0
- data/app/serializers/rails_pulse/api/v1/job_run_serializer.rb +28 -0
- data/app/serializers/rails_pulse/api/v1/job_serializer.rb +26 -0
- data/app/serializers/rails_pulse/api/v1/query_serializer.rb +25 -0
- data/app/serializers/rails_pulse/api/v1/request_serializer.rb +21 -0
- data/app/serializers/rails_pulse/api/v1/route_serializer.rb +19 -0
- data/app/services/rails_pulse/exception_capture_service.rb +1 -1
- data/app/services/rails_pulse/operations/comparison.rb +1 -1
- data/app/services/rails_pulse/summary_service/from_child_periods.rb +161 -0
- data/app/services/rails_pulse/summary_service/from_raw_rows.rb +115 -0
- data/app/services/rails_pulse/summary_service/metrics.rb +38 -0
- data/app/services/rails_pulse/summary_service.rb +121 -133
- data/app/services/rails_pulse/tag_filter_service.rb +5 -5
- data/app/views/layouts/rails_pulse/_time_range_selector.html.erb +4 -1
- data/app/views/rails_pulse/components/_operation_details_popover.html.erb +1 -1
- data/app/views/rails_pulse/components/_table_head.html.erb +2 -2
- data/app/views/rails_pulse/dashboard/_chart_panel.html.erb +6 -1
- data/app/views/rails_pulse/dashboard/_deployments_panel.html.erb +2 -2
- data/app/views/rails_pulse/dashboard/_health_badge.html.erb +29 -20
- data/app/views/rails_pulse/dashboard/index.html.erb +16 -2
- data/app/views/rails_pulse/jobs/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/jobs/index.html.erb +1 -1
- data/app/views/rails_pulse/queries/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/queries/index.html.erb +1 -1
- data/app/views/rails_pulse/queries/show.html.erb +1 -1
- data/app/views/rails_pulse/requests/index.html.erb +1 -1
- data/app/views/rails_pulse/routes/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/routes/index.html.erb +1 -1
- data/app/views/rails_pulse/routes/show.html.erb +1 -1
- data/app/views/rails_pulse/shared/_aggregation_zone_badge.html.erb +4 -0
- data/app/views/rails_pulse/storage/show.html.erb +37 -0
- data/config/routes.rb +19 -0
- data/db/rails_pulse_migrate/20260506000001_create_rails_pulse_exceptions.rb +1 -1
- data/db/rails_pulse_migrate/20260921000001_create_rails_pulse_events.rb +21 -0
- data/db/rails_pulse_schema.rb +20 -2
- data/exe/rails-pulse +7 -0
- data/lib/generators/rails_pulse/base_methods.rb +1 -0
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +20 -2
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +36 -13
- data/lib/rails_pulse/cleanup_service.rb +26 -20
- data/lib/rails_pulse/cli/agent_files/agents.md +87 -0
- data/lib/rails_pulse/cli/agent_files/claude_skill.md +165 -0
- data/lib/rails_pulse/cli/base_command.rb +37 -0
- data/lib/rails_pulse/cli/client.rb +64 -0
- data/lib/rails_pulse/cli/config.rb +87 -0
- data/lib/rails_pulse/cli/configure.rb +75 -0
- data/lib/rails_pulse/cli/coverage.rb +91 -0
- data/lib/rails_pulse/cli/deployments.rb +40 -0
- data/lib/rails_pulse/cli/exceptions.rb +111 -0
- data/lib/rails_pulse/cli/formatter.rb +31 -0
- data/lib/rails_pulse/cli/install.rb +145 -0
- data/lib/rails_pulse/cli/job_runs.rb +53 -0
- data/lib/rails_pulse/cli/jobs.rb +71 -0
- data/lib/rails_pulse/cli/main.rb +72 -0
- data/lib/rails_pulse/cli/mcp.rb +47 -0
- data/lib/rails_pulse/cli/queries.rb +62 -0
- data/lib/rails_pulse/cli/requests.rb +49 -0
- data/lib/rails_pulse/cli/routes.rb +66 -0
- data/lib/rails_pulse/configuration.rb +23 -1
- data/lib/rails_pulse/current.rb +11 -0
- data/lib/rails_pulse/engine.rb +12 -3
- data/lib/rails_pulse/job_run_collector.rb +15 -15
- data/lib/rails_pulse/mcp/server.rb +76 -0
- data/lib/rails_pulse/mcp/tools/coverage.rb +96 -0
- data/lib/rails_pulse/mcp/tools/deployments.rb +80 -0
- data/lib/rails_pulse/mcp/tools/endpoint.rb +167 -0
- data/lib/rails_pulse/mcp/tools/errors.rb +95 -0
- data/lib/rails_pulse/mcp/tools/exception_detail.rb +118 -0
- data/lib/rails_pulse/mcp/tools/exceptions.rb +114 -0
- data/lib/rails_pulse/mcp/tools/helpers.rb +162 -0
- data/lib/rails_pulse/mcp/tools/jobs.rb +155 -0
- data/lib/rails_pulse/mcp/tools/queries.rb +136 -0
- data/lib/rails_pulse/mcp/tools/routes.rb +97 -0
- data/lib/rails_pulse/mcp/tools/slow_requests.rb +118 -0
- data/lib/rails_pulse/middleware/request_collector.rb +9 -9
- data/lib/rails_pulse/scoped_inflector.rb +35 -0
- data/lib/rails_pulse/statistics.rb +27 -0
- data/lib/rails_pulse/subscribers/exception_subscriber.rb +2 -2
- data/lib/rails_pulse/subscribers/operation_subscriber.rb +15 -29
- data/lib/rails_pulse/tasks/status_reporter.rb +26 -0
- data/lib/rails_pulse/tracker.rb +75 -14
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/rails_pulse.rb +0 -4
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +13 -13
- metadata +96 -24
- data/app/controllers/concerns/page_timings.rb +0 -10
- data/app/controllers/concerns/response_range_concern.rb +0 -45
- data/app/controllers/concerns/time_range_concern.rb +0 -152
- data/app/controllers/concerns/zoom_range_concern.rb +0 -86
data/config/routes.rb
CHANGED
|
@@ -37,6 +37,25 @@ RailsPulse::Engine.routes.draw do
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Read-only JSON API for the rails-pulse CLI, the MCP server and CI, all
|
|
41
|
+
# authenticated by config.api_token (app/controllers/rails_pulse/api/v1).
|
|
42
|
+
scope path: "api/v1", module: "api/v1", as: :api_v1 do
|
|
43
|
+
resources :routes, only: :index
|
|
44
|
+
resources :requests, only: :index
|
|
45
|
+
resources :queries, only: :index
|
|
46
|
+
resources :jobs, only: :index
|
|
47
|
+
resources :job_runs, only: :index
|
|
48
|
+
resources :deployments, only: :index
|
|
49
|
+
resources :exceptions, only: %i[index show]
|
|
50
|
+
|
|
51
|
+
# Whether the data is there, rather than a question about the data.
|
|
52
|
+
get "coverage", to: "coverage#show", as: :coverage
|
|
53
|
+
|
|
54
|
+
# Which application, environment and version answered, so results gathered
|
|
55
|
+
# against staging are not read as production.
|
|
56
|
+
get "capabilities", to: "capabilities#show", as: :capabilities
|
|
57
|
+
end
|
|
58
|
+
|
|
40
59
|
# CSP compliance testing (development/test only)
|
|
41
60
|
if Rails.env.local?
|
|
42
61
|
get "csp_test", to: "csp_test#show", as: :csp_test
|
|
@@ -32,7 +32,7 @@ class CreateRailsPulseExceptions < ActiveRecord::Migration[7.0]
|
|
|
32
32
|
t.string :request_url, comment: "Nullable — web requests only"
|
|
33
33
|
t.string :request_method, comment: "GET, POST, etc."
|
|
34
34
|
t.string :environment, comment: "production, staging, etc."
|
|
35
|
-
t.string :deploy_sha, comment: "Captured
|
|
35
|
+
t.string :deploy_sha, comment: "Captured at occurrence time — cannot backfill later"
|
|
36
36
|
t.text :request_params, comment: "JSON hash of filtered request params — web requests only"
|
|
37
37
|
t.datetime :occurred_at, null: false
|
|
38
38
|
t.timestamps
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CreateRailsPulseEvents < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
return if table_exists?(:rails_pulse_events)
|
|
4
|
+
|
|
5
|
+
create_table :rails_pulse_events do |t|
|
|
6
|
+
t.string :kind, null: false, comment: "What noticed it, e.g. writer_heartbeat"
|
|
7
|
+
t.string :subject, comment: "Who it is about: host:pid, rule name, job name, exception class"
|
|
8
|
+
t.string :outcome, null: false, comment: "sampled, triggered, clean, insufficient_data, ran"
|
|
9
|
+
t.decimal :value, precision: 15, scale: 6, comment: "The number behind it: requests dropped since the last sample, or the triggered metric value"
|
|
10
|
+
t.datetime :occurred_at, null: false
|
|
11
|
+
t.text :message
|
|
12
|
+
t.text :metadata, comment: "JSON with the kind-specific detail"
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :rails_pulse_events, [ :kind, :occurred_at ],
|
|
17
|
+
name: "index_rp_events_on_kind_and_occurred_at"
|
|
18
|
+
add_index :rails_pulse_events, [ :kind, :subject, :occurred_at ],
|
|
19
|
+
name: "index_rp_events_on_kind_subject_and_occurred_at"
|
|
20
|
+
end
|
|
21
|
+
end
|
data/db/rails_pulse_schema.rb
CHANGED
|
@@ -7,7 +7,7 @@ require "rails_pulse/route_indexes" unless defined?(RailsPulse::RouteIndexes)
|
|
|
7
7
|
RailsPulse::Schema = lambda do |connection|
|
|
8
8
|
adapter = connection.adapter_name.downcase
|
|
9
9
|
# Skip if all tables already exist to prevent conflicts
|
|
10
|
-
required_tables = [ :rails_pulse_routes, :rails_pulse_queries, :rails_pulse_requests, :rails_pulse_operations, :rails_pulse_jobs, :rails_pulse_job_runs, :rails_pulse_summaries, :rails_pulse_deployments, :rails_pulse_exception_groups, :rails_pulse_exception_occurrences ]
|
|
10
|
+
required_tables = [ :rails_pulse_routes, :rails_pulse_queries, :rails_pulse_requests, :rails_pulse_operations, :rails_pulse_jobs, :rails_pulse_job_runs, :rails_pulse_summaries, :rails_pulse_deployments, :rails_pulse_exception_groups, :rails_pulse_exception_occurrences, :rails_pulse_events ]
|
|
11
11
|
|
|
12
12
|
# Check which tables already exist
|
|
13
13
|
existing_tables = required_tables.select { |table| connection.table_exists?(table) }
|
|
@@ -249,7 +249,7 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
249
249
|
t.string :request_url, comment: "Nullable — web requests only"
|
|
250
250
|
t.string :request_method, comment: "GET, POST, etc."
|
|
251
251
|
t.string :environment, comment: "production, staging, etc."
|
|
252
|
-
t.string :deploy_sha, comment: "Captured
|
|
252
|
+
t.string :deploy_sha, comment: "Captured at occurrence time — cannot backfill later"
|
|
253
253
|
t.text :request_params, comment: "JSON hash of filtered request params — web requests only"
|
|
254
254
|
t.datetime :occurred_at, null: false
|
|
255
255
|
t.timestamps
|
|
@@ -259,6 +259,24 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
259
259
|
connection.add_index :rails_pulse_exception_occurrences, :exception_group_id, name: "index_rp_exception_occurrences_on_group_id"
|
|
260
260
|
end
|
|
261
261
|
|
|
262
|
+
unless connection.table_exists?(:rails_pulse_events)
|
|
263
|
+
connection.create_table :rails_pulse_events do |t|
|
|
264
|
+
t.string :kind, null: false, comment: "What noticed it, e.g. writer_heartbeat"
|
|
265
|
+
t.string :subject, comment: "Who it is about: host:pid, rule name, job name, exception class"
|
|
266
|
+
t.string :outcome, null: false, comment: "sampled, triggered, clean, insufficient_data, ran"
|
|
267
|
+
t.decimal :value, precision: 15, scale: 6, comment: "The number behind it: requests dropped since the last sample, or the triggered metric value"
|
|
268
|
+
t.datetime :occurred_at, null: false
|
|
269
|
+
t.text :message
|
|
270
|
+
t.text :metadata, comment: "JSON with the kind-specific detail"
|
|
271
|
+
t.timestamps
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
connection.add_index :rails_pulse_events, [ :kind, :occurred_at ],
|
|
275
|
+
name: "index_rp_events_on_kind_and_occurred_at"
|
|
276
|
+
connection.add_index :rails_pulse_events, [ :kind, :subject, :occurred_at ],
|
|
277
|
+
name: "index_rp_events_on_kind_subject_and_occurred_at"
|
|
278
|
+
end
|
|
279
|
+
|
|
262
280
|
# Add indexes to existing tables for efficient aggregation
|
|
263
281
|
unless connection.index_exists?(:rails_pulse_requests, [ :created_at, :route_id ], name: "idx_requests_for_aggregation")
|
|
264
282
|
connection.add_index :rails_pulse_requests, [ :created_at, :route_id ], name: "idx_requests_for_aggregation"
|
data/exe/rails-pulse
ADDED
|
@@ -7,7 +7,7 @@ require "rails_pulse/route_indexes" unless defined?(RailsPulse::RouteIndexes)
|
|
|
7
7
|
RailsPulse::Schema = lambda do |connection|
|
|
8
8
|
adapter = connection.adapter_name.downcase
|
|
9
9
|
# Skip if all tables already exist to prevent conflicts
|
|
10
|
-
required_tables = [ :rails_pulse_routes, :rails_pulse_queries, :rails_pulse_requests, :rails_pulse_operations, :rails_pulse_jobs, :rails_pulse_job_runs, :rails_pulse_summaries, :rails_pulse_deployments, :rails_pulse_exception_groups, :rails_pulse_exception_occurrences ]
|
|
10
|
+
required_tables = [ :rails_pulse_routes, :rails_pulse_queries, :rails_pulse_requests, :rails_pulse_operations, :rails_pulse_jobs, :rails_pulse_job_runs, :rails_pulse_summaries, :rails_pulse_deployments, :rails_pulse_exception_groups, :rails_pulse_exception_occurrences, :rails_pulse_events ]
|
|
11
11
|
|
|
12
12
|
# Check which tables already exist
|
|
13
13
|
existing_tables = required_tables.select { |table| connection.table_exists?(table) }
|
|
@@ -249,7 +249,7 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
249
249
|
t.string :request_url, comment: "Nullable — web requests only"
|
|
250
250
|
t.string :request_method, comment: "GET, POST, etc."
|
|
251
251
|
t.string :environment, comment: "production, staging, etc."
|
|
252
|
-
t.string :deploy_sha, comment: "Captured
|
|
252
|
+
t.string :deploy_sha, comment: "Captured at occurrence time — cannot backfill later"
|
|
253
253
|
t.text :request_params, comment: "JSON hash of filtered request params — web requests only"
|
|
254
254
|
t.datetime :occurred_at, null: false
|
|
255
255
|
t.timestamps
|
|
@@ -259,6 +259,24 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
259
259
|
connection.add_index :rails_pulse_exception_occurrences, :exception_group_id, name: "index_rp_exception_occurrences_on_group_id"
|
|
260
260
|
end
|
|
261
261
|
|
|
262
|
+
unless connection.table_exists?(:rails_pulse_events)
|
|
263
|
+
connection.create_table :rails_pulse_events do |t|
|
|
264
|
+
t.string :kind, null: false, comment: "What noticed it, e.g. writer_heartbeat"
|
|
265
|
+
t.string :subject, comment: "Who it is about: host:pid, rule name, job name, exception class"
|
|
266
|
+
t.string :outcome, null: false, comment: "sampled, triggered, clean, insufficient_data, ran"
|
|
267
|
+
t.decimal :value, precision: 15, scale: 6, comment: "The number behind it: requests dropped since the last sample, or the triggered metric value"
|
|
268
|
+
t.datetime :occurred_at, null: false
|
|
269
|
+
t.text :message
|
|
270
|
+
t.text :metadata, comment: "JSON with the kind-specific detail"
|
|
271
|
+
t.timestamps
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
connection.add_index :rails_pulse_events, [ :kind, :occurred_at ],
|
|
275
|
+
name: "index_rp_events_on_kind_and_occurred_at"
|
|
276
|
+
connection.add_index :rails_pulse_events, [ :kind, :subject, :occurred_at ],
|
|
277
|
+
name: "index_rp_events_on_kind_subject_and_occurred_at"
|
|
278
|
+
end
|
|
279
|
+
|
|
262
280
|
# Add indexes to existing tables for efficient aggregation
|
|
263
281
|
unless connection.index_exists?(:rails_pulse_requests, [ :created_at, :route_id ], name: "idx_requests_for_aggregation")
|
|
264
282
|
connection.add_index :rails_pulse_requests, [ :created_at, :route_id ], name: "idx_requests_for_aggregation"
|
|
@@ -327,22 +327,36 @@ RailsPulse.configure do |config|
|
|
|
327
327
|
# }
|
|
328
328
|
|
|
329
329
|
# ====================================================================================================
|
|
330
|
-
#
|
|
330
|
+
# API TOKENS AND DEPLOYMENT TRACKING
|
|
331
331
|
# ====================================================================================================
|
|
332
|
-
#
|
|
333
|
-
#
|
|
332
|
+
# Two tokens authenticate what is not the dashboard, so a credential given to a coding agent
|
|
333
|
+
# cannot write anything. Both are sent as an `X-Rails-Pulse-Token` header.
|
|
334
334
|
#
|
|
335
|
-
#
|
|
336
|
-
#
|
|
337
|
-
#
|
|
335
|
+
# api_token reads the JSON API under /rails_pulse/api/v1 — what the `rails-pulse` CLI and the
|
|
336
|
+
# MCP server your coding agent uses talk to. Every endpoint under it is read-only.
|
|
337
|
+
# config.api_token = Rails.application.credentials.dig(:rails_pulse, :api_token)
|
|
338
|
+
# config.api_token = ENV["RAILS_PULSE_API_TOKEN"]
|
|
338
339
|
#
|
|
339
|
-
#
|
|
340
|
-
#
|
|
341
|
-
# config.
|
|
340
|
+
# deployment_token records a release through POST /rails_pulse/deployments, which your CI calls.
|
|
341
|
+
# It is the only credential that endpoint accepts: api_token is refused there.
|
|
342
|
+
# config.deployment_token = Rails.application.credentials.dig(:rails_pulse, :deployment_token)
|
|
343
|
+
# config.deployment_token = ENV["RAILS_PULSE_DEPLOYMENT_TOKEN"]
|
|
342
344
|
#
|
|
343
|
-
#
|
|
344
|
-
#
|
|
345
|
-
#
|
|
345
|
+
# Set both to the same value if you would rather run one credential for both.
|
|
346
|
+
#
|
|
347
|
+
# With no api_token the JSON API refuses every request. With no deployment_token the deployments
|
|
348
|
+
# endpoint refuses every request too; a dashboard login is never accepted in its place. The
|
|
349
|
+
# rails_pulse:record_deployment and finish_deployment rake tasks need no token. (deployment_api_token
|
|
350
|
+
# is the pre-0.5 name for deployment_token and still works.)
|
|
351
|
+
#
|
|
352
|
+
# Point the CLI and MCP server at this app with `rails-pulse configure`, or set RAILS_PULSE_URL
|
|
353
|
+
# and RAILS_PULSE_TOKEN in the agent's environment. The MCP server needs `gem "mcp"` in this
|
|
354
|
+
# app's Gemfile (a development group is enough); Rails Pulse does not pull it into production.
|
|
355
|
+
#
|
|
356
|
+
# Deployments draw vertical markers on the charts so a change in performance can be lined up
|
|
357
|
+
# with the release that caused it. Limits: revision ≤ 255 characters, metadata ≤ 4 KB
|
|
358
|
+
# serialized, started_at at most one hour in the future. Rows beyond
|
|
359
|
+
# max_table_records[:rails_pulse_deployments] are pruned oldest-first by the cleanup task.
|
|
346
360
|
#
|
|
347
361
|
# Record a deployment from your CI/CD pipeline:
|
|
348
362
|
# curl -X POST https://yourapp.com/rails_pulse/deployments \
|
|
@@ -356,7 +370,8 @@ RailsPulse.configure do |config|
|
|
|
356
370
|
# Metadata for the rake task comes from an environment variable, as a JSON object:
|
|
357
371
|
# RAILS_PULSE_DEPLOYMENT_METADATA='{"environment":"production"}' rake rails_pulse:record_deployment[abc1234]
|
|
358
372
|
|
|
359
|
-
# config.
|
|
373
|
+
# config.api_token = ENV["RAILS_PULSE_API_TOKEN"]
|
|
374
|
+
# config.deployment_token = ENV["RAILS_PULSE_DEPLOYMENT_TOKEN"]
|
|
360
375
|
|
|
361
376
|
# ====================================================================================================
|
|
362
377
|
# DATA CLEANUP
|
|
@@ -376,6 +391,14 @@ RailsPulse.configure do |config|
|
|
|
376
391
|
# Time-based retention - delete records older than this period
|
|
377
392
|
config.full_retention_period = 30.days
|
|
378
393
|
|
|
394
|
+
# How long rows in rails_pulse_events are kept. The background writer's
|
|
395
|
+
# heartbeats are pruned after a day regardless.
|
|
396
|
+
config.event_retention_period = 90.days
|
|
397
|
+
|
|
398
|
+
# Event kinds exempt from event_retention_period — for rows a writer updates
|
|
399
|
+
# in place rather than appends.
|
|
400
|
+
config.event_retention_exempt_kinds = []
|
|
401
|
+
|
|
379
402
|
# Count-based retention - maximum records to keep per table
|
|
380
403
|
# After time-based cleanup, if tables still exceed these limits,
|
|
381
404
|
# the oldest remaining records will be deleted to stay under the limit
|
|
@@ -29,6 +29,7 @@ module RailsPulse
|
|
|
29
29
|
|
|
30
30
|
perform_time_based_cleanup
|
|
31
31
|
perform_count_based_cleanup
|
|
32
|
+
perform_event_cleanup
|
|
32
33
|
perform_summary_cleanup
|
|
33
34
|
|
|
34
35
|
log_cleanup_summary
|
|
@@ -116,6 +117,22 @@ module RailsPulse
|
|
|
116
117
|
end
|
|
117
118
|
end
|
|
118
119
|
|
|
120
|
+
# event_retention_exempt_kinds are rows a writer updates in place, which
|
|
121
|
+
# must survive so "last ran" never becomes "never ran"
|
|
122
|
+
def perform_event_cleanup
|
|
123
|
+
return unless @config.event_retention_period
|
|
124
|
+
return unless RailsPulse::Event.table_available?
|
|
125
|
+
|
|
126
|
+
cutoff_time = [ @config.event_retention_period.ago, 1.hour.ago ].min
|
|
127
|
+
run_stage(:time_based, :events) do
|
|
128
|
+
delete_in_batches(
|
|
129
|
+
RailsPulse::Event
|
|
130
|
+
.where.not(kind: @config.event_retention_exempt_kinds)
|
|
131
|
+
.where(occurred_at: ...cutoff_time)
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
119
136
|
def perform_count_based_cleanup
|
|
120
137
|
return unless @config.max_table_records&.any?
|
|
121
138
|
|
|
@@ -313,37 +330,26 @@ module RailsPulse
|
|
|
313
330
|
RailsPulse::ExceptionOccurrence.where.not(exception_group_id: preserved_ids)
|
|
314
331
|
end
|
|
315
332
|
|
|
316
|
-
# Prune oldest
|
|
317
|
-
#
|
|
333
|
+
# Prune the oldest deletable groups when over the configured cap. Preserved
|
|
334
|
+
# and ignored groups are outside the cap: counting them would make a cap
|
|
335
|
+
# that they alone fill impossible to satisfy, so every run would trim the
|
|
336
|
+
# deletable groups for no gain (decision 0017). Child occurrences must be
|
|
337
|
+
# deleted first — there is no ON DELETE CASCADE on the FK.
|
|
318
338
|
def cleanup_exception_groups_by_count
|
|
319
339
|
max_records = @config.max_table_records[:rails_pulse_exception_groups]
|
|
320
340
|
return 0 unless max_records
|
|
321
341
|
|
|
322
|
-
current_count = RailsPulse::ExceptionGroup.count
|
|
323
|
-
return 0 if current_count <= max_records
|
|
324
|
-
|
|
325
|
-
overage = current_count - max_records
|
|
326
342
|
scope = RailsPulse::ExceptionGroup.where(preserve: false).where.not(status: "ignored")
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
deletable_count = scope.count
|
|
330
|
-
records_to_delete = [ overage, deletable_count ].min
|
|
331
|
-
return 0 if records_to_delete <= 0
|
|
343
|
+
overage = scope.count - max_records
|
|
344
|
+
return 0 if overage <= 0
|
|
332
345
|
|
|
333
|
-
|
|
346
|
+
delete_parents_in_batches(
|
|
334
347
|
scope,
|
|
335
348
|
order_column: :last_seen_at,
|
|
336
349
|
children: RailsPulse::ExceptionOccurrence,
|
|
337
350
|
foreign_key: :exception_group_id,
|
|
338
|
-
limit:
|
|
351
|
+
limit: overage
|
|
339
352
|
)
|
|
340
|
-
|
|
341
|
-
if overage > deletable_count
|
|
342
|
-
RailsPulse.logger.warn("[RailsPulse] Exception group cap #{max_records} cannot be met: " \
|
|
343
|
-
"#{current_count - deleted} remain (#{current_count - deletable_count} are preserved/ignored)")
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
deleted
|
|
347
353
|
end
|
|
348
354
|
|
|
349
355
|
# Delete groups whose last occurrence was removed — the orphan check and
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Rails Pulse — Agent Integration
|
|
2
|
+
|
|
3
|
+
Rails Pulse gives AI coding agents read access to a Rails application's performance data: requests, SQL queries, background jobs, exceptions and deployments, all from the application's own database. Two interfaces, one token.
|
|
4
|
+
|
|
5
|
+
## MCP server
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
rails-pulse mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The server needs the `mcp` gem, which Rails Pulse does not depend on: add `gem "mcp", "~> 1.0"` to the application's Gemfile (a development group is enough), or `gem install mcp` when running `rails-pulse` outside Bundler.
|
|
12
|
+
|
|
13
|
+
**Claude Code** (`~/.claude.json`):
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"mcpServers": {
|
|
17
|
+
"rails-pulse": {
|
|
18
|
+
"command": "rails-pulse",
|
|
19
|
+
"args": ["mcp"],
|
|
20
|
+
"env": {
|
|
21
|
+
"RAILS_PULSE_URL": "https://myapp.com",
|
|
22
|
+
"RAILS_PULSE_TOKEN": "your-token"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"rails-pulse": { "command": "rails-pulse", "args": ["mcp"] }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Tools
|
|
39
|
+
|
|
40
|
+
All tools are read-only. Each returns a `summary` and `next_steps`.
|
|
41
|
+
|
|
42
|
+
| Tool | Purpose |
|
|
43
|
+
|------|---------|
|
|
44
|
+
| `rails_pulse_routes` | Discover endpoints with request volume, latency and errors |
|
|
45
|
+
| `rails_pulse_slow_requests` | Slowest endpoints for a period |
|
|
46
|
+
| `rails_pulse_coverage` | What has been recorded, how recently, and any collection gaps |
|
|
47
|
+
| `rails_pulse_errors` | Recent errors grouped by endpoint |
|
|
48
|
+
| `rails_pulse_exceptions` | Exception groups with status, count, location and latest message |
|
|
49
|
+
| `rails_pulse_exception` | One group's recent occurrences with backtraces, request and params |
|
|
50
|
+
| `rails_pulse_endpoint` | Deep performance profile of one endpoint |
|
|
51
|
+
| `rails_pulse_queries` | Most expensive SQL queries with N+1 detection |
|
|
52
|
+
| `rails_pulse_jobs` | Background job health and recent failures |
|
|
53
|
+
| `rails_pulse_deployments` | Recent deployments with revision, timing and metadata |
|
|
54
|
+
|
|
55
|
+
Every tool that takes a `period` also takes `since` and `until` as ISO 8601 timestamps, read as UTC when no zone is given, and echoes the bounds it measured back as `window`. `period` itself is only `last_hour`, `last_24_hours` or `last_7_days` (plus `all` on `rails_pulse_exceptions`); any other window is `since`/`until`. Use them to compare two fixed windows — the 24 hours before a release against the 24 hours after — rather than a relative period that moves between calls.
|
|
56
|
+
|
|
57
|
+
## CLI
|
|
58
|
+
|
|
59
|
+
The `rails-pulse` CLI works without MCP. Append `--json` for structured output.
|
|
60
|
+
|
|
61
|
+
| Command | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `rails-pulse routes list` | Tracked routes (`--since` adds request stats) |
|
|
64
|
+
| `rails-pulse requests list` | Recorded requests with status and time filters |
|
|
65
|
+
| `rails-pulse queries list` | SQL queries (`--since` adds timing stats) |
|
|
66
|
+
| `rails-pulse jobs list` | Background jobs: lifetime stats, or one window's with `--since` / `--until` |
|
|
67
|
+
| `rails-pulse job_runs list` | Individual job runs with errors |
|
|
68
|
+
| `rails-pulse exceptions list` | Exception groups with status and occurrence counts |
|
|
69
|
+
| `rails-pulse exceptions show ID` | One group with backtraces and recent occurrences |
|
|
70
|
+
| `rails-pulse deployments list` | Recorded deployments, most recent first |
|
|
71
|
+
| `rails-pulse coverage show` | What has been recorded, how recently, and any collection gaps |
|
|
72
|
+
|
|
73
|
+
Common flags on every list command: `--limit N` (1 to 500, default 25), `--offset N`, `--json`, `--since TIME` / `--until TIME` (ISO 8601; no zone means UTC).
|
|
74
|
+
|
|
75
|
+
## Authentication
|
|
76
|
+
|
|
77
|
+
Set `RAILS_PULSE_URL` and `RAILS_PULSE_TOKEN` (the application's `config.api_token`), or run `rails-pulse configure` to write `~/.rails-pulse`. That token is read-only; recording a deployment needs `config.deployment_token`, which these tools do not carry.
|
|
78
|
+
|
|
79
|
+
## Investigation pattern
|
|
80
|
+
|
|
81
|
+
1. Pin the time window to a deployment (`rails_pulse_deployments`)
|
|
82
|
+
2. Identify slow or failing endpoints (`rails_pulse_slow_requests`); resolve names with `rails_pulse_routes`
|
|
83
|
+
3. Profile the suspect endpoint (`rails_pulse_endpoint`)
|
|
84
|
+
4. Check errors (`rails_pulse_errors`)
|
|
85
|
+
5. Inspect SQL and jobs (`rails_pulse_queries`, `rails_pulse_jobs`)
|
|
86
|
+
6. Correlate with source code: pass the endpoint's `route_id` to `rails_pulse_queries` for the SQL inside it and the file and line each query came from
|
|
87
|
+
7. Fix, deploy, and re-check the same endpoint
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-pulse
|
|
3
|
+
description: Investigate Rails application performance with Rails Pulse. Use when asked about slow endpoints, regressions after a deploy, expensive or N+1 SQL, error rates, failing background jobs, or whether a performance fix worked. Prefers the rails_pulse_* MCP tools; falls back to the rails-pulse CLI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rails Pulse
|
|
7
|
+
|
|
8
|
+
Rails Pulse records every request, SQL query, background job and exception of a Rails application in the application's own database. This skill gives you read access to that data so you can find what is slow, why, and whether a change fixed it. Nothing here can modify the application.
|
|
9
|
+
|
|
10
|
+
## When to use it
|
|
11
|
+
|
|
12
|
+
- Investigating a performance complaint or a slow endpoint
|
|
13
|
+
- Diagnosing a regression after a deployment
|
|
14
|
+
- Finding expensive or N+1 SQL queries
|
|
15
|
+
- Investigating an increased error rate
|
|
16
|
+
- Analysing background job failures or slowness
|
|
17
|
+
- Validating whether a performance fix worked
|
|
18
|
+
|
|
19
|
+
## Interfaces
|
|
20
|
+
|
|
21
|
+
### MCP tools (preferred when available)
|
|
22
|
+
|
|
23
|
+
All tools are read-only. Each returns a `summary` and `next_steps`.
|
|
24
|
+
|
|
25
|
+
| Tool | Purpose |
|
|
26
|
+
|------|---------|
|
|
27
|
+
| `rails_pulse_routes` | Discover endpoints: request volume, latency and errors per route |
|
|
28
|
+
| `rails_pulse_slow_requests` | The slowest endpoints for a period |
|
|
29
|
+
| `rails_pulse_coverage` | What has been recorded, how recently, and any collection gaps |
|
|
30
|
+
| `rails_pulse_errors` | Recent 4xx/5xx responses grouped by endpoint |
|
|
31
|
+
| `rails_pulse_exceptions` | Exception groups with status, occurrence count, location and latest message |
|
|
32
|
+
| `rails_pulse_exception` | One group's recent occurrences with full backtraces, request, params and deploy SHA |
|
|
33
|
+
| `rails_pulse_endpoint` | Deep profile of one endpoint |
|
|
34
|
+
| `rails_pulse_queries` | Most expensive SQL queries, with N+1 detection |
|
|
35
|
+
| `rails_pulse_jobs` | Background job health and recent failures with error classes |
|
|
36
|
+
| `rails_pulse_deployments` | Recent deployments with revision, start and finish time, and metadata |
|
|
37
|
+
|
|
38
|
+
### CLI
|
|
39
|
+
|
|
40
|
+
The `rails-pulse` executable ships with the gem. Add `--json` for structured output.
|
|
41
|
+
|
|
42
|
+
| Command | Purpose |
|
|
43
|
+
|---------|---------|
|
|
44
|
+
| `rails-pulse routes list --json` | Tracked routes (add `--since` for stats) |
|
|
45
|
+
| `rails-pulse requests list --json` | Recorded requests with status and time filters |
|
|
46
|
+
| `rails-pulse queries list --json` | SQL queries (add `--since` for timing stats) |
|
|
47
|
+
| `rails-pulse jobs list --json` | Background jobs: lifetime stats, or one window's with `--since` / `--until` |
|
|
48
|
+
| `rails-pulse job_runs list --json` | Individual job runs with error class and message |
|
|
49
|
+
| `rails-pulse exceptions list --json` | Exception groups with status, count, location and message |
|
|
50
|
+
| `rails-pulse exceptions show ID --json` | One group with backtraces, request, params and deploy SHA |
|
|
51
|
+
| `rails-pulse deployments list --json` | Recorded deployments, most recent first |
|
|
52
|
+
| `rails-pulse coverage show --json` | What has been recorded, how recently, and any collection gaps |
|
|
53
|
+
|
|
54
|
+
## Investigation workflow
|
|
55
|
+
|
|
56
|
+
### 1. Establish the time period
|
|
57
|
+
|
|
58
|
+
Check recent deployments first; a regression usually lines up with one.
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
rails_pulse_deployments(period: "last_7_days")
|
|
62
|
+
rails-pulse deployments list --json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use a deployment's `started_at` as `since` for the tools below, then compare against the same length of time before it.
|
|
66
|
+
|
|
67
|
+
### 2. Identify affected endpoints
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
rails_pulse_slow_requests(period: "last_24_hours", limit: 10)
|
|
71
|
+
rails_pulse_routes(search: "checkout", period: "last_7_days")
|
|
72
|
+
rails-pulse requests list --limit 50 --json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Profile the suspect endpoint
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
rails_pulse_endpoint(endpoint: "CheckoutController#create", period: "last_7_days")
|
|
79
|
+
rails-pulse requests list --since 2026-06-01T00:00:00Z --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 4. Check errors
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
rails_pulse_errors(period: "last_24_hours", status: "5xx")
|
|
86
|
+
rails-pulse requests list --status 5xx --since 2026-06-01T00:00:00Z --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 5. Inspect SQL and background jobs
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
rails_pulse_queries(period: "last_24_hours", sort: "total_duration")
|
|
93
|
+
rails_pulse_queries(n_plus_one_only: true)
|
|
94
|
+
rails_pulse_jobs(period: "last_24_hours")
|
|
95
|
+
rails-pulse queries list --since 2026-06-01T00:00:00Z --json
|
|
96
|
+
rails-pulse job_runs list --status failed --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 6. Correlate with source code
|
|
100
|
+
|
|
101
|
+
Pass the endpoint's `route_id` to `rails_pulse_queries` to see only the SQL that ran inside it,
|
|
102
|
+
with `source_locations` naming the file and line each query was issued from.
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
rails_pulse_queries(route: 42, period: "last_24_hours")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Start at those locations rather than searching for the SQL. Look for N+1 queries, missing
|
|
109
|
+
indexes, expensive work in the request path, unnecessary serialization and missing caching.
|
|
110
|
+
|
|
111
|
+
### 7. Fix, then validate
|
|
112
|
+
|
|
113
|
+
After the fix is deployed, re-check the same endpoint over the period since the deploy.
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
rails_pulse_deployments(period: "last_24_hours")
|
|
117
|
+
rails_pulse_endpoint(endpoint: "CheckoutController#create", period: "last_hour")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
To show a change rather than a snapshot, measure the same endpoint over two fixed windows
|
|
121
|
+
either side of the release. Every windowed tool takes `since` and `until` as ISO 8601
|
|
122
|
+
timestamps, read as UTC when no zone is given, and echoes the bounds it used back as `window`.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
rails_pulse_endpoint(endpoint: "CheckoutController#create", since: "2026-09-24T12:00:00Z", until: "2026-09-25T12:00:00Z")
|
|
126
|
+
rails_pulse_endpoint(endpoint: "CheckoutController#create", since: "2026-09-25T12:00:00Z", until: "2026-09-26T12:00:00Z")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Use the deploy time as the boundary, and exclude the minutes around it when a rolling deploy
|
|
130
|
+
makes them a mix of both versions.
|
|
131
|
+
|
|
132
|
+
## Guidelines
|
|
133
|
+
|
|
134
|
+
- **SQL is not always the cause.** External API calls, serialization, view rendering and application logic slow requests too.
|
|
135
|
+
- **Use more than one tool.** Cross-reference latency with error rates, SQL timing and job performance before concluding.
|
|
136
|
+
- **Orient once before investigating.** `rails_pulse_coverage` names the application, environment and version you are querying. Call it first, so findings are attributed to the installation that actually produced them.
|
|
137
|
+
- **Confirm the data exists before reporting an all-clear.** An empty result means nothing was recorded, which only means nothing happened when collection was healthy over that window. Call `rails_pulse_coverage` before concluding a period was clean, and say so when it flags a gap, stale summaries or a kind that is not tracked.
|
|
138
|
+
- **Check the error rate.** A fast endpoint with a high error rate may be failing early rather than performing well.
|
|
139
|
+
- **Read percentiles, not just averages.** A low average with a high p95 or p99 means intermittent trouble.
|
|
140
|
+
- **Weigh request volume.** A slow endpoint nobody calls may not be worth the work.
|
|
141
|
+
- **Job figures cover the window.** `rails_pulse_jobs` reads them from summaries plus the runs not yet summarized. p95 and p99 are given only when one summary period holds the whole window; otherwise they are null and `note` says why, so compare averages and failure rates instead.
|
|
142
|
+
- **Check what the percentiles covered.** `rails_pulse_endpoint` computes them over the most recent requests it sampled, not the whole window. When `sampled_requests` is below `request_count` it says so in `latency.computed_over`; narrow the window until the two match before comparing percentiles across windows.
|
|
143
|
+
|
|
144
|
+
## Authentication
|
|
145
|
+
|
|
146
|
+
The CLI and MCP server read credentials from environment variables or `~/.rails-pulse`. The token is `config.api_token` in the application's Rails Pulse initializer. It is read-only: recording a deployment needs `config.deployment_token`, which these tools do not carry.
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
RAILS_PULSE_URL=https://myapp.com
|
|
150
|
+
RAILS_PULSE_TOKEN=my-secret-token
|
|
151
|
+
RAILS_PULSE_MOUNT_PATH=/rails_pulse # optional, default /rails_pulse
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
# ~/.rails-pulse
|
|
156
|
+
url: https://myapp.com
|
|
157
|
+
token: my-secret-token
|
|
158
|
+
mount_path: /rails_pulse
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Interactive setup: `rails-pulse configure`.
|
|
162
|
+
|
|
163
|
+
## CLI reference
|
|
164
|
+
|
|
165
|
+
List commands accept `--limit N` (1 to 500, default 25), `--offset N` and `--json`. Every list command also accepts `--since TIME` and `--until TIME` as ISO 8601; a time with no zone is read as UTC. JSON output is `{ "data": [...], "meta": { "total", "limit", "offset" } }`; page with `--offset`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
require_relative "config"
|
|
3
|
+
require_relative "client"
|
|
4
|
+
|
|
5
|
+
module RailsPulse
|
|
6
|
+
module CLI
|
|
7
|
+
class BaseCommand < Thor
|
|
8
|
+
# A usage error (unknown command, missing argument, bad flag) exits 1,
|
|
9
|
+
# so a script or CI step running the CLI can tell it failed.
|
|
10
|
+
def self.exit_on_failure?
|
|
11
|
+
true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# A mistyped flag is an error rather than silently ignored, so
|
|
15
|
+
# `--limt 5` does not quietly return the default page.
|
|
16
|
+
check_unknown_options!
|
|
17
|
+
|
|
18
|
+
no_commands do
|
|
19
|
+
def with_error_handling
|
|
20
|
+
yield
|
|
21
|
+
rescue RailsPulse::CLI::Config::ConfigError => e
|
|
22
|
+
say "Error: #{e.message}.", :red
|
|
23
|
+
exit 1
|
|
24
|
+
rescue RailsPulse::CLI::Client::ApiError => e
|
|
25
|
+
say "API error: #{e.message}", :red
|
|
26
|
+
exit 1
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def client
|
|
33
|
+
@client ||= Client.new
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|