rails_pulse 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +638 -0
- data/Rakefile +207 -0
- data/app/assets/images/rails_pulse/dashboard.png +0 -0
- data/app/assets/images/rails_pulse/menu.svg +1 -0
- data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
- data/app/assets/images/rails_pulse/request.png +0 -0
- data/app/assets/images/rails_pulse/routes.png +0 -0
- data/app/assets/stylesheets/rails_pulse/application.css +102 -0
- data/app/assets/stylesheets/rails_pulse/components/alert.css +24 -0
- data/app/assets/stylesheets/rails_pulse/components/badge.css +58 -0
- data/app/assets/stylesheets/rails_pulse/components/base.css +79 -0
- data/app/assets/stylesheets/rails_pulse/components/breadcrumb.css +31 -0
- data/app/assets/stylesheets/rails_pulse/components/button.css +99 -0
- data/app/assets/stylesheets/rails_pulse/components/card.css +19 -0
- data/app/assets/stylesheets/rails_pulse/components/chart.css +18 -0
- data/app/assets/stylesheets/rails_pulse/components/csp_safe_positioning.css +86 -0
- data/app/assets/stylesheets/rails_pulse/components/descriptive_list.css +9 -0
- data/app/assets/stylesheets/rails_pulse/components/dialog.css +56 -0
- data/app/assets/stylesheets/rails_pulse/components/flash.css +47 -0
- data/app/assets/stylesheets/rails_pulse/components/input.css +80 -0
- data/app/assets/stylesheets/rails_pulse/components/layouts.css +63 -0
- data/app/assets/stylesheets/rails_pulse/components/menu.css +43 -0
- data/app/assets/stylesheets/rails_pulse/components/popover.css +36 -0
- data/app/assets/stylesheets/rails_pulse/components/prose.css +144 -0
- data/app/assets/stylesheets/rails_pulse/components/row.css +24 -0
- data/app/assets/stylesheets/rails_pulse/components/sidebar_menu.css +79 -0
- data/app/assets/stylesheets/rails_pulse/components/skeleton.css +5 -0
- data/app/assets/stylesheets/rails_pulse/components/table.css +37 -0
- data/app/assets/stylesheets/rails_pulse/components/utilities.css +36 -0
- data/app/controllers/concerns/chart_table_concern.rb +82 -0
- data/app/controllers/concerns/response_range_concern.rb +24 -0
- data/app/controllers/concerns/time_range_concern.rb +67 -0
- data/app/controllers/concerns/zoom_range_concern.rb +40 -0
- data/app/controllers/rails_pulse/application_controller.rb +67 -0
- data/app/controllers/rails_pulse/assets_controller.rb +33 -0
- data/app/controllers/rails_pulse/caches_controller.rb +115 -0
- data/app/controllers/rails_pulse/csp_test_controller.rb +57 -0
- data/app/controllers/rails_pulse/dashboard_controller.rb +6 -0
- data/app/controllers/rails_pulse/operations_controller.rb +219 -0
- data/app/controllers/rails_pulse/queries_controller.rb +121 -0
- data/app/controllers/rails_pulse/requests_controller.rb +69 -0
- data/app/controllers/rails_pulse/routes_controller.rb +99 -0
- data/app/helpers/rails_pulse/application_helper.rb +111 -0
- data/app/helpers/rails_pulse/breadcrumbs_helper.rb +62 -0
- data/app/helpers/rails_pulse/cached_component_helper.rb +73 -0
- data/app/helpers/rails_pulse/chart_formatters.rb +43 -0
- data/app/helpers/rails_pulse/chart_helper.rb +140 -0
- data/app/helpers/rails_pulse/formatting_helper.rb +29 -0
- data/app/helpers/rails_pulse/status_helper.rb +279 -0
- data/app/helpers/rails_pulse/table_helper.rb +54 -0
- data/app/javascript/rails_pulse/application.js +119 -0
- data/app/javascript/rails_pulse/controllers/color_scheme_controller.js +20 -0
- data/app/javascript/rails_pulse/controllers/context_menu_controller.js +16 -0
- data/app/javascript/rails_pulse/controllers/dialog_controller.js +21 -0
- data/app/javascript/rails_pulse/controllers/expandable_row_controller.js +67 -0
- data/app/javascript/rails_pulse/controllers/form_controller.js +39 -0
- data/app/javascript/rails_pulse/controllers/icon_controller.js +170 -0
- data/app/javascript/rails_pulse/controllers/index_controller.js +230 -0
- data/app/javascript/rails_pulse/controllers/menu_controller.js +60 -0
- data/app/javascript/rails_pulse/controllers/pagination_controller.js +69 -0
- data/app/javascript/rails_pulse/controllers/popover_controller.js +91 -0
- data/app/javascript/rails_pulse/controllers/timezone_controller.js +106 -0
- data/app/javascript/rails_pulse/theme.js +416 -0
- data/app/jobs/rails_pulse/application_job.rb +4 -0
- data/app/jobs/rails_pulse/cleanup_job.rb +21 -0
- data/app/mailers/rails_pulse/application_mailer.rb +6 -0
- data/app/models/rails_pulse/application_record.rb +7 -0
- data/app/models/rails_pulse/component_cache_key.rb +33 -0
- data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +27 -0
- data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +37 -0
- data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +59 -0
- data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +45 -0
- data/app/models/rails_pulse/operation.rb +87 -0
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +52 -0
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +57 -0
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +71 -0
- data/app/models/rails_pulse/queries/charts/average_query_times.rb +112 -0
- data/app/models/rails_pulse/query.rb +58 -0
- data/app/models/rails_pulse/request.rb +64 -0
- data/app/models/rails_pulse/requests/charts/average_response_times.rb +99 -0
- data/app/models/rails_pulse/requests/charts/operations_chart.rb +35 -0
- data/app/models/rails_pulse/route.rb +77 -0
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +54 -0
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +73 -0
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +73 -0
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +59 -0
- data/app/models/rails_pulse/routes/charts/average_response_times.rb +115 -0
- data/app/models/rails_pulse/routes/tables/index.rb +63 -0
- data/app/services/rails_pulse/sql_query_normalizer.rb +124 -0
- data/app/views/layouts/rails_pulse/_menu_items.html.erb +19 -0
- data/app/views/layouts/rails_pulse/_sidebar_menu.html.erb +44 -0
- data/app/views/layouts/rails_pulse/application.html.erb +72 -0
- data/app/views/rails_pulse/caches/show.html.erb +9 -0
- data/app/views/rails_pulse/components/_breadcrumbs.html.erb +12 -0
- data/app/views/rails_pulse/components/_code_panel.html.erb +12 -0
- data/app/views/rails_pulse/components/_metric_card.html.erb +55 -0
- data/app/views/rails_pulse/components/_metric_row.html.erb +9 -0
- data/app/views/rails_pulse/components/_operation_details_popover.html.erb +241 -0
- data/app/views/rails_pulse/components/_panel.html.erb +56 -0
- data/app/views/rails_pulse/components/_sparkline_stats.html.erb +15 -0
- data/app/views/rails_pulse/components/_table.html.erb +50 -0
- data/app/views/rails_pulse/components/_table_head.html.erb +20 -0
- data/app/views/rails_pulse/components/_table_pagination.html.erb +45 -0
- data/app/views/rails_pulse/components/_time_period.html.erb +16 -0
- data/app/views/rails_pulse/csp_test/show.html.erb +207 -0
- data/app/views/rails_pulse/dashboard/charts/_bar_chart.html.erb +1 -0
- data/app/views/rails_pulse/dashboard/index.html.erb +64 -0
- data/app/views/rails_pulse/dashboard/tables/_routes_table.html.erb +32 -0
- data/app/views/rails_pulse/dashboard/tables/_standard_table.html.erb +1 -0
- data/app/views/rails_pulse/operations/_operation_analysis_application.html.erb +43 -0
- data/app/views/rails_pulse/operations/_operation_analysis_database.html.erb +12 -0
- data/app/views/rails_pulse/operations/_operation_analysis_generic.html.erb +15 -0
- data/app/views/rails_pulse/operations/_operation_analysis_other.html.erb +69 -0
- data/app/views/rails_pulse/operations/_operation_analysis_view.html.erb +39 -0
- data/app/views/rails_pulse/operations/show.html.erb +79 -0
- data/app/views/rails_pulse/queries/_show_table.html.erb +19 -0
- data/app/views/rails_pulse/queries/_table.html.erb +31 -0
- data/app/views/rails_pulse/queries/index.html.erb +64 -0
- data/app/views/rails_pulse/queries/show.html.erb +86 -0
- data/app/views/rails_pulse/requests/_operations.html.erb +85 -0
- data/app/views/rails_pulse/requests/_table.html.erb +31 -0
- data/app/views/rails_pulse/requests/index.html.erb +64 -0
- data/app/views/rails_pulse/requests/show.html.erb +44 -0
- data/app/views/rails_pulse/routes/_table.html.erb +29 -0
- data/app/views/rails_pulse/routes/index.html.erb +65 -0
- data/app/views/rails_pulse/routes/show.html.erb +67 -0
- data/app/views/rails_pulse/skeletons/_chart.html.erb +3 -0
- data/app/views/rails_pulse/skeletons/_metric_card.html.erb +20 -0
- data/app/views/rails_pulse/skeletons/_panel.html.erb +19 -0
- data/app/views/rails_pulse/skeletons/_table.html.erb +8 -0
- data/config/importmap.rb +12 -0
- data/config/initializers/rails_charts_csp_patch.rb +83 -0
- data/config/initializers/rails_pulse.rb +198 -0
- data/config/routes.rb +16 -0
- data/db/migrate/20250227235904_create_routes.rb +12 -0
- data/db/migrate/20250227235915_create_requests.rb +19 -0
- data/db/migrate/20250228000000_create_queries.rb +14 -0
- data/db/migrate/20250228000056_create_operations.rb +24 -0
- data/lib/generators/rails_pulse/install_generator.rb +17 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +198 -0
- data/lib/rails_pulse/cleanup_service.rb +212 -0
- data/lib/rails_pulse/configuration.rb +176 -0
- data/lib/rails_pulse/engine.rb +88 -0
- data/lib/rails_pulse/middleware/asset_server.rb +84 -0
- data/lib/rails_pulse/middleware/request_collector.rb +120 -0
- data/lib/rails_pulse/migration.rb +29 -0
- data/lib/rails_pulse/subscribers/operation_subscriber.rb +280 -0
- data/lib/rails_pulse/version.rb +3 -0
- data/lib/rails_pulse.rb +38 -0
- data/lib/tasks/rails_pulse_tasks.rake +138 -0
- data/public/rails-pulse-assets/csp-test.js +110 -0
- data/public/rails-pulse-assets/rails-pulse-icons.js +89 -0
- data/public/rails-pulse-assets/rails-pulse-icons.js.map +13 -0
- data/public/rails-pulse-assets/rails-pulse.css +1 -0
- data/public/rails-pulse-assets/rails-pulse.css.map +1 -0
- data/public/rails-pulse-assets/rails-pulse.js +183 -0
- data/public/rails-pulse-assets/rails-pulse.js.map +7 -0
- metadata +339 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
# Monkey patch for RailsCharts CSP compliance
|
2
|
+
# This adds nonce attributes to script tags generated by the RailsCharts gem
|
3
|
+
|
4
|
+
if defined?(RailsCharts)
|
5
|
+
module RailsCharts
|
6
|
+
module CspPatch
|
7
|
+
def line_chart(data_source, options = {})
|
8
|
+
# Get the original chart HTML
|
9
|
+
chart_html = super(data_source, options)
|
10
|
+
|
11
|
+
# Try to get CSP nonce from various sources
|
12
|
+
nonce = get_csp_nonce
|
13
|
+
|
14
|
+
if nonce.present? && chart_html.present?
|
15
|
+
# Add nonce to all script tags in the chart HTML
|
16
|
+
chart_html = add_nonce_to_scripts(chart_html.to_s, nonce)
|
17
|
+
# Ensure the HTML is marked as safe for Rails to render
|
18
|
+
chart_html = chart_html.html_safe if chart_html.respond_to?(:html_safe)
|
19
|
+
end
|
20
|
+
|
21
|
+
chart_html
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def get_csp_nonce
|
27
|
+
# Try various methods to get the CSP nonce
|
28
|
+
nonce = nil
|
29
|
+
|
30
|
+
# Method 1: Check for Rails 6+ CSP nonce helper
|
31
|
+
if respond_to?(:content_security_policy_nonce)
|
32
|
+
nonce = content_security_policy_nonce
|
33
|
+
end
|
34
|
+
|
35
|
+
# Method 2: Check for custom csp_nonce helper
|
36
|
+
if nonce.blank? && respond_to?(:csp_nonce)
|
37
|
+
nonce = csp_nonce
|
38
|
+
end
|
39
|
+
|
40
|
+
# Method 3: Check request environment
|
41
|
+
if nonce.blank? && defined?(request) && request
|
42
|
+
nonce = request.env["action_dispatch.content_security_policy_nonce"] ||
|
43
|
+
request.env["secure_headers.content_security_policy_nonce"] ||
|
44
|
+
request.env["csp_nonce"]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Method 4: Check thread/request store
|
48
|
+
if nonce.blank?
|
49
|
+
nonce = Thread.current[:rails_pulse_csp_nonce] ||
|
50
|
+
(defined?(RequestStore) && RequestStore.store[:rails_pulse_csp_nonce])
|
51
|
+
end
|
52
|
+
|
53
|
+
nonce.presence
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_nonce_to_scripts(html, nonce)
|
57
|
+
# Use regex to add nonce to script tags that don't already have one
|
58
|
+
html.gsub(/<script(?![^>]*\snonce=)([^>]*)>/i) do |match|
|
59
|
+
# Insert nonce attribute before the closing >
|
60
|
+
attributes = $1
|
61
|
+
if attributes.strip.empty?
|
62
|
+
"<script nonce=\"#{nonce}\">"
|
63
|
+
else
|
64
|
+
"<script#{attributes} nonce=\"#{nonce}\">"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Apply the patch to ApplicationHelper and any modules that include chart helpers
|
73
|
+
Rails.application.config.to_prepare do
|
74
|
+
if defined?(RailsCharts)
|
75
|
+
# Patch ActionView::Base to include our CSP patch for line_chart
|
76
|
+
ActionView::Base.prepend(RailsCharts::CspPatch)
|
77
|
+
|
78
|
+
# Also patch any Rails Pulse helpers that might use charts
|
79
|
+
if defined?(RailsPulse::ApplicationHelper)
|
80
|
+
RailsPulse::ApplicationHelper.prepend(RailsCharts::CspPatch)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
RailsPulse.configure do |config|
|
2
|
+
# ====================================================================================================
|
3
|
+
# GLOBAL CONFIGURATION
|
4
|
+
# ====================================================================================================
|
5
|
+
|
6
|
+
# Enable or disable Rails Pulse
|
7
|
+
config.enabled = true
|
8
|
+
|
9
|
+
# ====================================================================================================
|
10
|
+
# THRESHOLDS
|
11
|
+
# ====================================================================================================
|
12
|
+
# These thresholds are used to determine if a route, request, or query is slow, very slow, or critical.
|
13
|
+
# Values are in milliseconds (ms). Adjust these based on your application's performance requirements.
|
14
|
+
|
15
|
+
# Thresholds for an individual route
|
16
|
+
config.route_thresholds = {
|
17
|
+
slow: 500,
|
18
|
+
very_slow: 1500,
|
19
|
+
critical: 3000
|
20
|
+
}
|
21
|
+
|
22
|
+
# Thresholds for an individual request
|
23
|
+
config.request_thresholds = {
|
24
|
+
slow: 700,
|
25
|
+
very_slow: 2000,
|
26
|
+
critical: 4000
|
27
|
+
}
|
28
|
+
|
29
|
+
# Thresholds for an individual database query
|
30
|
+
config.query_thresholds = {
|
31
|
+
slow: 100,
|
32
|
+
very_slow: 500,
|
33
|
+
critical: 1000
|
34
|
+
}
|
35
|
+
|
36
|
+
# ====================================================================================================
|
37
|
+
# FILTERING
|
38
|
+
# ====================================================================================================
|
39
|
+
|
40
|
+
# Asset Tracking Configuration
|
41
|
+
# By default, Rails Pulse ignores asset requests (images, CSS, JS files) to focus on application performance.
|
42
|
+
# Set track_assets to true if you want to monitor asset delivery performance.
|
43
|
+
config.track_assets = false
|
44
|
+
|
45
|
+
# Custom asset patterns to ignore (in addition to the built-in defaults)
|
46
|
+
# Only applies when track_assets is false. Add patterns for app-specific asset paths.
|
47
|
+
config.custom_asset_patterns = [
|
48
|
+
# Example: ignore specific asset directories
|
49
|
+
# %r{^/uploads/},
|
50
|
+
# %r{^/media/},
|
51
|
+
# "/special-assets/"
|
52
|
+
]
|
53
|
+
|
54
|
+
# Rails Pulse Mount Path (optional)
|
55
|
+
# If Rails Pulse is mounted at a custom path, specify it here to prevent
|
56
|
+
# Rails Pulse from tracking its own requests. Leave as nil for default '/rails_pulse'.
|
57
|
+
# Examples:
|
58
|
+
# config.mount_path = "/admin/monitoring"
|
59
|
+
config.mount_path = nil
|
60
|
+
|
61
|
+
# Manual route filtering
|
62
|
+
# Specify additional routes, requests, or queries to ignore from performance tracking.
|
63
|
+
# Each array can include strings (exact matches) or regular expressions.
|
64
|
+
#
|
65
|
+
# Examples:
|
66
|
+
# config.ignored_routes = ["/health_check", %r{^/admin}]
|
67
|
+
# config.ignored_requests = ["GET /status", %r{POST /api/v1/.*}]
|
68
|
+
# config.ignored_queries = ["SELECT 1", %r{FROM \"schema_migrations\"}]
|
69
|
+
|
70
|
+
config.ignored_routes = []
|
71
|
+
config.ignored_requests = []
|
72
|
+
config.ignored_queries = []
|
73
|
+
|
74
|
+
# ====================================================================================================
|
75
|
+
# CACHING
|
76
|
+
# ====================================================================================================
|
77
|
+
# Configure metric card caching to improve performance of the Rails Pulse dashboard.
|
78
|
+
# Caching reduces database load for expensive metric calculations.
|
79
|
+
|
80
|
+
# Enable/disable metric card caching
|
81
|
+
config.component_cache_enabled = true
|
82
|
+
|
83
|
+
# How long to cache metric card results
|
84
|
+
config.component_cache_duration = 1.hour
|
85
|
+
|
86
|
+
# ====================================================================================================
|
87
|
+
# DATABASE CONFIGURATION
|
88
|
+
# ====================================================================================================
|
89
|
+
# Configure Rails Pulse to use a separate database for performance monitoring data.
|
90
|
+
# This is optional but recommended for production applications to isolate performance
|
91
|
+
# data from your main application database.
|
92
|
+
#
|
93
|
+
# Uncomment and configure one of the following patterns:
|
94
|
+
|
95
|
+
# Option 1: Separate single database for Rails Pulse
|
96
|
+
# config.connects_to = {
|
97
|
+
# database: { writing: :rails_pulse, reading: :rails_pulse }
|
98
|
+
# }
|
99
|
+
|
100
|
+
# Option 2: Primary/replica configuration for Rails Pulse
|
101
|
+
# config.connects_to = {
|
102
|
+
# database: { writing: :rails_pulse_primary, reading: :rails_pulse_replica }
|
103
|
+
# }
|
104
|
+
|
105
|
+
# Don't forget to add the database configuration to config/database.yml:
|
106
|
+
#
|
107
|
+
# production:
|
108
|
+
# # ... your main database config ...
|
109
|
+
# rails_pulse:
|
110
|
+
# adapter: postgresql # or mysql2, sqlite3
|
111
|
+
# database: myapp_rails_pulse_production
|
112
|
+
# username: rails_pulse_user
|
113
|
+
# password: <%= Rails.application.credentials.dig(:rails_pulse, :database_password) %>
|
114
|
+
# host: localhost
|
115
|
+
# pool: 5
|
116
|
+
|
117
|
+
# ====================================================================================================
|
118
|
+
# AUTHENTICATION
|
119
|
+
# ====================================================================================================
|
120
|
+
# Configure authentication to secure access to the Rails Pulse dashboard.
|
121
|
+
# Authentication is ENABLED BY DEFAULT in production environments for security.
|
122
|
+
#
|
123
|
+
# If no authentication method is configured, Rails Pulse will use HTTP Basic Auth
|
124
|
+
# with credentials from RAILS_PULSE_USERNAME (default: 'admin') and RAILS_PULSE_PASSWORD
|
125
|
+
# environment variables. Set RAILS_PULSE_PASSWORD to enable this fallback.
|
126
|
+
#
|
127
|
+
# Uncomment and configure one of the following patterns based on your authentication system:
|
128
|
+
|
129
|
+
# Enable/disable authentication (enabled by default in production)
|
130
|
+
# config.authentication_enabled = Rails.env.production?
|
131
|
+
|
132
|
+
# Where to redirect unauthorized users
|
133
|
+
# config.authentication_redirect_path = "/"
|
134
|
+
|
135
|
+
# Custom authentication method - choose one of the examples below:
|
136
|
+
|
137
|
+
# Example 1: Devise with admin role check
|
138
|
+
# config.authentication_method = proc {
|
139
|
+
# unless user_signed_in? && current_user.admin?
|
140
|
+
# redirect_to main_app.root_path, alert: "Access denied"
|
141
|
+
# end
|
142
|
+
# }
|
143
|
+
|
144
|
+
# Example 2: Custom session-based authentication
|
145
|
+
# config.authentication_method = proc {
|
146
|
+
# unless session[:user_id] && User.find_by(id: session[:user_id])&.admin?
|
147
|
+
# redirect_to main_app.login_path, alert: "Please log in as an admin"
|
148
|
+
# end
|
149
|
+
# }
|
150
|
+
|
151
|
+
# Example 3: Warden authentication
|
152
|
+
# config.authentication_method = proc {
|
153
|
+
# warden.authenticate!(:scope => :admin)
|
154
|
+
# }
|
155
|
+
|
156
|
+
# Example 4: Basic HTTP authentication
|
157
|
+
# config.authentication_method = proc {
|
158
|
+
# authenticate_or_request_with_http_basic do |username, password|
|
159
|
+
# username == ENV['RAILS_PULSE_USERNAME'] && password == ENV['RAILS_PULSE_PASSWORD']
|
160
|
+
# end
|
161
|
+
# }
|
162
|
+
|
163
|
+
# Example 5: Custom authorization check
|
164
|
+
# config.authentication_method = proc {
|
165
|
+
# current_user = User.find_by(id: session[:user_id])
|
166
|
+
# unless current_user&.can_access_rails_pulse?
|
167
|
+
# render plain: "Forbidden", status: :forbidden
|
168
|
+
# end
|
169
|
+
# }
|
170
|
+
|
171
|
+
# ====================================================================================================
|
172
|
+
# DATA CLEANUP
|
173
|
+
# ====================================================================================================
|
174
|
+
# Configure automatic cleanup of old performance data to manage database size.
|
175
|
+
# Rails Pulse provides two cleanup mechanisms that work together:
|
176
|
+
#
|
177
|
+
# 1. Time-based cleanup: Delete records older than the retention period
|
178
|
+
# 2. Count-based cleanup: Keep only the specified number of records per table
|
179
|
+
#
|
180
|
+
# Cleanup order respects foreign key constraints:
|
181
|
+
# operations → requests → queries/routes
|
182
|
+
|
183
|
+
# Enable or disable automatic data cleanup
|
184
|
+
config.archiving_enabled = true
|
185
|
+
|
186
|
+
# Time-based retention - delete records older than this period
|
187
|
+
config.full_retention_period = 2.weeks
|
188
|
+
|
189
|
+
# Count-based retention - maximum records to keep per table
|
190
|
+
# After time-based cleanup, if tables still exceed these limits,
|
191
|
+
# the oldest remaining records will be deleted to stay under the limit
|
192
|
+
config.max_table_records = {
|
193
|
+
rails_pulse_requests: 10000, # HTTP requests (moderate volume)
|
194
|
+
rails_pulse_operations: 50000, # Operations within requests (high volume)
|
195
|
+
rails_pulse_routes: 1000, # Unique routes (low volume)
|
196
|
+
rails_pulse_queries: 500 # Normalized SQL queries (low volume)
|
197
|
+
}
|
198
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
RailsPulse::Engine.routes.draw do
|
2
|
+
root to: "dashboard#index"
|
3
|
+
|
4
|
+
resources :routes, only: %i[index show]
|
5
|
+
resources :requests, only: %i[index show]
|
6
|
+
resources :queries, only: %i[index show]
|
7
|
+
resources :operations, only: %i[show]
|
8
|
+
resources :caches, only: %i[show], as: :cache
|
9
|
+
patch "pagination/limit", to: "application#set_pagination_limit"
|
10
|
+
|
11
|
+
# CSP compliance testing
|
12
|
+
get "csp_test", to: "csp_test#show", as: :csp_test
|
13
|
+
|
14
|
+
# Asset serving fallback
|
15
|
+
get "rails-pulse-assets/:asset_name", to: "assets#show", as: :asset, constraints: { asset_name: /.*/ }
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateRoutes < RailsPulse::Migration
|
2
|
+
def change
|
3
|
+
create_table :rails_pulse_routes do |t|
|
4
|
+
t.string :method, null: false, comment: "HTTP method (e.g., GET, POST)"
|
5
|
+
t.string :path, null: false, comment: "Request path (e.g., /posts/index)"
|
6
|
+
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :rails_pulse_routes, [ :method, :path ], unique: true, name: 'index_rails_pulse_routes_on_method_and_path'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateRequests < RailsPulse::Migration
|
2
|
+
def change
|
3
|
+
create_table :rails_pulse_requests do |t|
|
4
|
+
t.references :route, null: false, foreign_key: { to_table: :rails_pulse_routes }, comment: "Link to the route"
|
5
|
+
t.decimal :duration, precision: 15, scale: 6, null: false, comment: "Total request duration in milliseconds"
|
6
|
+
t.integer :status, null: false, comment: "HTTP status code (e.g., 200, 500)"
|
7
|
+
t.boolean :is_error, null: false, default: false, comment: "True if status >= 500"
|
8
|
+
t.string :request_uuid, null: false, comment: "Unique identifier for the request (e.g., UUID)"
|
9
|
+
t.string :controller_action, comment: "Controller and action handling the request (e.g., PostsController#show)"
|
10
|
+
t.timestamp :occurred_at, null: false, comment: "When the request started"
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index :rails_pulse_requests, :occurred_at, name: 'index_rails_pulse_requests_on_occurred_at'
|
16
|
+
add_index :rails_pulse_requests, :request_uuid, unique: true, name: 'index_rails_pulse_requests_on_request_uuid'
|
17
|
+
add_index :rails_pulse_requests, [ :route_id, :occurred_at ], name: 'index_rails_pulse_requests_on_route_id_and_occurred_at'
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateQueries < RailsPulse::Migration
|
2
|
+
def change
|
3
|
+
create_table :rails_pulse_queries do |t|
|
4
|
+
# Use string with reasonable limit instead of text to avoid MySQL index length issues
|
5
|
+
# 1000 characters should be sufficient for most normalized SQL queries
|
6
|
+
t.string :normalized_sql, limit: 1000, null: false, comment: "Normalized SQL query string (e.g., SELECT * FROM users WHERE id = ?)"
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
# Now using string instead of text, no need for database-specific logic
|
12
|
+
add_index :rails_pulse_queries, :normalized_sql, unique: true, name: "index_rails_pulse_queries_on_normalized_sql", length: 191
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class CreateOperations < RailsPulse::Migration
|
2
|
+
def change
|
3
|
+
create_table :rails_pulse_operations do |t|
|
4
|
+
t.references :request, null: false, foreign_key: { to_table: :rails_pulse_requests }, comment: "Link to the request"
|
5
|
+
t.references :query, foreign_key: { to_table: :rails_pulse_queries }, index: true, comment: "Link to the normalized SQL query"
|
6
|
+
t.string :operation_type, null: false, comment: "Type of operation (e.g., database, view, gem_call)"
|
7
|
+
t.string :label, null: false, comment: "Descriptive name (e.g., SELECT FROM users WHERE id = 1, render layout)"
|
8
|
+
t.decimal :duration, precision: 15, scale: 6, null: false, comment: "Operation duration in milliseconds"
|
9
|
+
t.string :codebase_location, comment: "File and line number (e.g., app/models/user.rb:25)"
|
10
|
+
t.float :start_time, null: false, default: 0.0, comment: "Operation start time in milliseconds"
|
11
|
+
t.timestamp :occurred_at, null: false, comment: "When the request started"
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :rails_pulse_operations, :operation_type, name: 'index_rails_pulse_operations_on_operation_type'
|
17
|
+
add_index :rails_pulse_operations, :occurred_at, name: 'index_rails_pulse_operations_on_occurred_at'
|
18
|
+
|
19
|
+
# Performance indexes for queries page optimization
|
20
|
+
add_index :rails_pulse_operations, [ :query_id, :occurred_at ], name: 'index_rails_pulse_operations_on_query_and_time'
|
21
|
+
add_index :rails_pulse_operations, [ :query_id, :duration, :occurred_at ], name: 'index_rails_pulse_operations_query_performance'
|
22
|
+
add_index :rails_pulse_operations, [ :occurred_at, :duration, :operation_type ], name: 'index_rails_pulse_operations_on_time_duration_type'
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RailsPulse
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
desc "Copies Rails Pulse migrations to the application."
|
7
|
+
def copy_migrations
|
8
|
+
rake "rails_pulse:install:migrations"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Copies Rails Pulse example configuration file to the application."
|
12
|
+
def copy_initializer
|
13
|
+
copy_file "rails_pulse.rb", "config/initializers/rails_pulse.rb"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
RailsPulse.configure do |config|
|
2
|
+
# ====================================================================================================
|
3
|
+
# GLOBAL CONFIGURATION
|
4
|
+
# ====================================================================================================
|
5
|
+
|
6
|
+
# Enable or disable Rails Pulse
|
7
|
+
config.enabled = true
|
8
|
+
|
9
|
+
# ====================================================================================================
|
10
|
+
# THRESHOLDS
|
11
|
+
# ====================================================================================================
|
12
|
+
# These thresholds are used to determine if a route, request, or query is slow, very slow, or critical.
|
13
|
+
# Values are in milliseconds (ms). Adjust these based on your application's performance requirements.
|
14
|
+
|
15
|
+
# Thresholds for an individual route
|
16
|
+
config.route_thresholds = {
|
17
|
+
slow: 500,
|
18
|
+
very_slow: 1500,
|
19
|
+
critical: 3000
|
20
|
+
}
|
21
|
+
|
22
|
+
# Thresholds for an individual request
|
23
|
+
config.request_thresholds = {
|
24
|
+
slow: 700,
|
25
|
+
very_slow: 2000,
|
26
|
+
critical: 4000
|
27
|
+
}
|
28
|
+
|
29
|
+
# Thresholds for an individual database query
|
30
|
+
config.query_thresholds = {
|
31
|
+
slow: 100,
|
32
|
+
very_slow: 500,
|
33
|
+
critical: 1000
|
34
|
+
}
|
35
|
+
|
36
|
+
# ====================================================================================================
|
37
|
+
# FILTERING
|
38
|
+
# ====================================================================================================
|
39
|
+
|
40
|
+
# Asset Tracking Configuration
|
41
|
+
# By default, Rails Pulse ignores asset requests (images, CSS, JS files) to focus on application performance.
|
42
|
+
# Set track_assets to true if you want to monitor asset delivery performance.
|
43
|
+
config.track_assets = false
|
44
|
+
|
45
|
+
# Custom asset patterns to ignore (in addition to the built-in defaults)
|
46
|
+
# Only applies when track_assets is false. Add patterns for app-specific asset paths.
|
47
|
+
config.custom_asset_patterns = [
|
48
|
+
# Example: ignore specific asset directories
|
49
|
+
# %r{^/uploads/},
|
50
|
+
# %r{^/media/},
|
51
|
+
# "/special-assets/"
|
52
|
+
]
|
53
|
+
|
54
|
+
# Rails Pulse Mount Path (optional)
|
55
|
+
# If Rails Pulse is mounted at a custom path, specify it here to prevent
|
56
|
+
# Rails Pulse from tracking its own requests. Leave as nil for default '/rails_pulse'.
|
57
|
+
# Examples:
|
58
|
+
# config.mount_path = "/admin/monitoring"
|
59
|
+
config.mount_path = nil
|
60
|
+
|
61
|
+
# Manual route filtering
|
62
|
+
# Specify additional routes, requests, or queries to ignore from performance tracking.
|
63
|
+
# Each array can include strings (exact matches) or regular expressions.
|
64
|
+
#
|
65
|
+
# Examples:
|
66
|
+
# config.ignored_routes = ["/health_check", %r{^/admin}]
|
67
|
+
# config.ignored_requests = ["GET /status", %r{POST /api/v1/.*}]
|
68
|
+
# config.ignored_queries = ["SELECT 1", %r{FROM \"schema_migrations\"}]
|
69
|
+
|
70
|
+
config.ignored_routes = []
|
71
|
+
config.ignored_requests = []
|
72
|
+
config.ignored_queries = []
|
73
|
+
|
74
|
+
# ====================================================================================================
|
75
|
+
# CACHING
|
76
|
+
# ====================================================================================================
|
77
|
+
# Configure metric card caching to improve performance of the Rails Pulse dashboard.
|
78
|
+
# Caching reduces database load for expensive metric calculations.
|
79
|
+
|
80
|
+
# Enable/disable metric card caching
|
81
|
+
config.component_cache_enabled = true
|
82
|
+
|
83
|
+
# How long to cache metric card results
|
84
|
+
config.component_cache_duration = 1.hour
|
85
|
+
|
86
|
+
# ====================================================================================================
|
87
|
+
# DATABASE CONFIGURATION
|
88
|
+
# ====================================================================================================
|
89
|
+
# Configure Rails Pulse to use a separate database for performance monitoring data.
|
90
|
+
# This is optional but recommended for production applications to isolate performance
|
91
|
+
# data from your main application database.
|
92
|
+
#
|
93
|
+
# Uncomment and configure one of the following patterns:
|
94
|
+
|
95
|
+
# Option 1: Separate single database for Rails Pulse
|
96
|
+
# config.connects_to = {
|
97
|
+
# database: { writing: :rails_pulse, reading: :rails_pulse }
|
98
|
+
# }
|
99
|
+
|
100
|
+
# Option 2: Primary/replica configuration for Rails Pulse
|
101
|
+
# config.connects_to = {
|
102
|
+
# database: { writing: :rails_pulse_primary, reading: :rails_pulse_replica }
|
103
|
+
# }
|
104
|
+
|
105
|
+
# Don't forget to add the database configuration to config/database.yml:
|
106
|
+
#
|
107
|
+
# production:
|
108
|
+
# # ... your main database config ...
|
109
|
+
# rails_pulse:
|
110
|
+
# adapter: postgresql # or mysql2, sqlite3
|
111
|
+
# database: myapp_rails_pulse_production
|
112
|
+
# username: rails_pulse_user
|
113
|
+
# password: <%= Rails.application.credentials.dig(:rails_pulse, :database_password) %>
|
114
|
+
# host: localhost
|
115
|
+
# pool: 5
|
116
|
+
|
117
|
+
# ====================================================================================================
|
118
|
+
# AUTHENTICATION
|
119
|
+
# ====================================================================================================
|
120
|
+
# Configure authentication to secure access to the Rails Pulse dashboard.
|
121
|
+
# Authentication is ENABLED BY DEFAULT in production environments for security.
|
122
|
+
#
|
123
|
+
# If no authentication method is configured, Rails Pulse will use HTTP Basic Auth
|
124
|
+
# with credentials from RAILS_PULSE_USERNAME (default: 'admin') and RAILS_PULSE_PASSWORD
|
125
|
+
# environment variables. Set RAILS_PULSE_PASSWORD to enable this fallback.
|
126
|
+
#
|
127
|
+
# Uncomment and configure one of the following patterns based on your authentication system:
|
128
|
+
|
129
|
+
# Enable/disable authentication (enabled by default in production)
|
130
|
+
# config.authentication_enabled = Rails.env.production?
|
131
|
+
|
132
|
+
# Where to redirect unauthorized users
|
133
|
+
# config.authentication_redirect_path = "/"
|
134
|
+
|
135
|
+
# Custom authentication method - choose one of the examples below:
|
136
|
+
|
137
|
+
# Example 1: Devise with admin role check
|
138
|
+
# config.authentication_method = proc {
|
139
|
+
# unless user_signed_in? && current_user.admin?
|
140
|
+
# redirect_to main_app.root_path, alert: "Access denied"
|
141
|
+
# end
|
142
|
+
# }
|
143
|
+
|
144
|
+
# Example 2: Custom session-based authentication
|
145
|
+
# config.authentication_method = proc {
|
146
|
+
# unless session[:user_id] && User.find_by(id: session[:user_id])&.admin?
|
147
|
+
# redirect_to main_app.login_path, alert: "Please log in as an admin"
|
148
|
+
# end
|
149
|
+
# }
|
150
|
+
|
151
|
+
# Example 3: Warden authentication
|
152
|
+
# config.authentication_method = proc {
|
153
|
+
# warden.authenticate!(:scope => :admin)
|
154
|
+
# }
|
155
|
+
|
156
|
+
# Example 4: Basic HTTP authentication
|
157
|
+
# config.authentication_method = proc {
|
158
|
+
# authenticate_or_request_with_http_basic do |username, password|
|
159
|
+
# username == ENV['RAILS_PULSE_USERNAME'] && password == ENV['RAILS_PULSE_PASSWORD']
|
160
|
+
# end
|
161
|
+
# }
|
162
|
+
|
163
|
+
# Example 5: Custom authorization check
|
164
|
+
# config.authentication_method = proc {
|
165
|
+
# current_user = User.find_by(id: session[:user_id])
|
166
|
+
# unless current_user&.can_access_rails_pulse?
|
167
|
+
# render plain: "Forbidden", status: :forbidden
|
168
|
+
# end
|
169
|
+
# }
|
170
|
+
|
171
|
+
# ====================================================================================================
|
172
|
+
# DATA CLEANUP
|
173
|
+
# ====================================================================================================
|
174
|
+
# Configure automatic cleanup of old performance data to manage database size.
|
175
|
+
# Rails Pulse provides two cleanup mechanisms that work together:
|
176
|
+
#
|
177
|
+
# 1. Time-based cleanup: Delete records older than the retention period
|
178
|
+
# 2. Count-based cleanup: Keep only the specified number of records per table
|
179
|
+
#
|
180
|
+
# Cleanup order respects foreign key constraints:
|
181
|
+
# operations → requests → queries/routes
|
182
|
+
|
183
|
+
# Enable or disable automatic data cleanup
|
184
|
+
config.archiving_enabled = true
|
185
|
+
|
186
|
+
# Time-based retention - delete records older than this period
|
187
|
+
config.full_retention_period = 2.weeks
|
188
|
+
|
189
|
+
# Count-based retention - maximum records to keep per table
|
190
|
+
# After time-based cleanup, if tables still exceed these limits,
|
191
|
+
# the oldest remaining records will be deleted to stay under the limit
|
192
|
+
config.max_table_records = {
|
193
|
+
rails_pulse_requests: 10000, # HTTP requests (moderate volume)
|
194
|
+
rails_pulse_operations: 50000, # Operations within requests (high volume)
|
195
|
+
rails_pulse_routes: 1000, # Unique routes (low volume)
|
196
|
+
rails_pulse_queries: 500 # Normalized SQL queries (low volume)
|
197
|
+
}
|
198
|
+
end
|