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.
Files changed (160) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +638 -0
  4. data/Rakefile +207 -0
  5. data/app/assets/images/rails_pulse/dashboard.png +0 -0
  6. data/app/assets/images/rails_pulse/menu.svg +1 -0
  7. data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
  8. data/app/assets/images/rails_pulse/request.png +0 -0
  9. data/app/assets/images/rails_pulse/routes.png +0 -0
  10. data/app/assets/stylesheets/rails_pulse/application.css +102 -0
  11. data/app/assets/stylesheets/rails_pulse/components/alert.css +24 -0
  12. data/app/assets/stylesheets/rails_pulse/components/badge.css +58 -0
  13. data/app/assets/stylesheets/rails_pulse/components/base.css +79 -0
  14. data/app/assets/stylesheets/rails_pulse/components/breadcrumb.css +31 -0
  15. data/app/assets/stylesheets/rails_pulse/components/button.css +99 -0
  16. data/app/assets/stylesheets/rails_pulse/components/card.css +19 -0
  17. data/app/assets/stylesheets/rails_pulse/components/chart.css +18 -0
  18. data/app/assets/stylesheets/rails_pulse/components/csp_safe_positioning.css +86 -0
  19. data/app/assets/stylesheets/rails_pulse/components/descriptive_list.css +9 -0
  20. data/app/assets/stylesheets/rails_pulse/components/dialog.css +56 -0
  21. data/app/assets/stylesheets/rails_pulse/components/flash.css +47 -0
  22. data/app/assets/stylesheets/rails_pulse/components/input.css +80 -0
  23. data/app/assets/stylesheets/rails_pulse/components/layouts.css +63 -0
  24. data/app/assets/stylesheets/rails_pulse/components/menu.css +43 -0
  25. data/app/assets/stylesheets/rails_pulse/components/popover.css +36 -0
  26. data/app/assets/stylesheets/rails_pulse/components/prose.css +144 -0
  27. data/app/assets/stylesheets/rails_pulse/components/row.css +24 -0
  28. data/app/assets/stylesheets/rails_pulse/components/sidebar_menu.css +79 -0
  29. data/app/assets/stylesheets/rails_pulse/components/skeleton.css +5 -0
  30. data/app/assets/stylesheets/rails_pulse/components/table.css +37 -0
  31. data/app/assets/stylesheets/rails_pulse/components/utilities.css +36 -0
  32. data/app/controllers/concerns/chart_table_concern.rb +82 -0
  33. data/app/controllers/concerns/response_range_concern.rb +24 -0
  34. data/app/controllers/concerns/time_range_concern.rb +67 -0
  35. data/app/controllers/concerns/zoom_range_concern.rb +40 -0
  36. data/app/controllers/rails_pulse/application_controller.rb +67 -0
  37. data/app/controllers/rails_pulse/assets_controller.rb +33 -0
  38. data/app/controllers/rails_pulse/caches_controller.rb +115 -0
  39. data/app/controllers/rails_pulse/csp_test_controller.rb +57 -0
  40. data/app/controllers/rails_pulse/dashboard_controller.rb +6 -0
  41. data/app/controllers/rails_pulse/operations_controller.rb +219 -0
  42. data/app/controllers/rails_pulse/queries_controller.rb +121 -0
  43. data/app/controllers/rails_pulse/requests_controller.rb +69 -0
  44. data/app/controllers/rails_pulse/routes_controller.rb +99 -0
  45. data/app/helpers/rails_pulse/application_helper.rb +111 -0
  46. data/app/helpers/rails_pulse/breadcrumbs_helper.rb +62 -0
  47. data/app/helpers/rails_pulse/cached_component_helper.rb +73 -0
  48. data/app/helpers/rails_pulse/chart_formatters.rb +43 -0
  49. data/app/helpers/rails_pulse/chart_helper.rb +140 -0
  50. data/app/helpers/rails_pulse/formatting_helper.rb +29 -0
  51. data/app/helpers/rails_pulse/status_helper.rb +279 -0
  52. data/app/helpers/rails_pulse/table_helper.rb +54 -0
  53. data/app/javascript/rails_pulse/application.js +119 -0
  54. data/app/javascript/rails_pulse/controllers/color_scheme_controller.js +20 -0
  55. data/app/javascript/rails_pulse/controllers/context_menu_controller.js +16 -0
  56. data/app/javascript/rails_pulse/controllers/dialog_controller.js +21 -0
  57. data/app/javascript/rails_pulse/controllers/expandable_row_controller.js +67 -0
  58. data/app/javascript/rails_pulse/controllers/form_controller.js +39 -0
  59. data/app/javascript/rails_pulse/controllers/icon_controller.js +170 -0
  60. data/app/javascript/rails_pulse/controllers/index_controller.js +230 -0
  61. data/app/javascript/rails_pulse/controllers/menu_controller.js +60 -0
  62. data/app/javascript/rails_pulse/controllers/pagination_controller.js +69 -0
  63. data/app/javascript/rails_pulse/controllers/popover_controller.js +91 -0
  64. data/app/javascript/rails_pulse/controllers/timezone_controller.js +106 -0
  65. data/app/javascript/rails_pulse/theme.js +416 -0
  66. data/app/jobs/rails_pulse/application_job.rb +4 -0
  67. data/app/jobs/rails_pulse/cleanup_job.rb +21 -0
  68. data/app/mailers/rails_pulse/application_mailer.rb +6 -0
  69. data/app/models/rails_pulse/application_record.rb +7 -0
  70. data/app/models/rails_pulse/component_cache_key.rb +33 -0
  71. data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +27 -0
  72. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +37 -0
  73. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +59 -0
  74. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +45 -0
  75. data/app/models/rails_pulse/operation.rb +87 -0
  76. data/app/models/rails_pulse/queries/cards/average_query_times.rb +52 -0
  77. data/app/models/rails_pulse/queries/cards/execution_rate.rb +57 -0
  78. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +71 -0
  79. data/app/models/rails_pulse/queries/charts/average_query_times.rb +112 -0
  80. data/app/models/rails_pulse/query.rb +58 -0
  81. data/app/models/rails_pulse/request.rb +64 -0
  82. data/app/models/rails_pulse/requests/charts/average_response_times.rb +99 -0
  83. data/app/models/rails_pulse/requests/charts/operations_chart.rb +35 -0
  84. data/app/models/rails_pulse/route.rb +77 -0
  85. data/app/models/rails_pulse/routes/cards/average_response_times.rb +54 -0
  86. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +73 -0
  87. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +73 -0
  88. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +59 -0
  89. data/app/models/rails_pulse/routes/charts/average_response_times.rb +115 -0
  90. data/app/models/rails_pulse/routes/tables/index.rb +63 -0
  91. data/app/services/rails_pulse/sql_query_normalizer.rb +124 -0
  92. data/app/views/layouts/rails_pulse/_menu_items.html.erb +19 -0
  93. data/app/views/layouts/rails_pulse/_sidebar_menu.html.erb +44 -0
  94. data/app/views/layouts/rails_pulse/application.html.erb +72 -0
  95. data/app/views/rails_pulse/caches/show.html.erb +9 -0
  96. data/app/views/rails_pulse/components/_breadcrumbs.html.erb +12 -0
  97. data/app/views/rails_pulse/components/_code_panel.html.erb +12 -0
  98. data/app/views/rails_pulse/components/_metric_card.html.erb +55 -0
  99. data/app/views/rails_pulse/components/_metric_row.html.erb +9 -0
  100. data/app/views/rails_pulse/components/_operation_details_popover.html.erb +241 -0
  101. data/app/views/rails_pulse/components/_panel.html.erb +56 -0
  102. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +15 -0
  103. data/app/views/rails_pulse/components/_table.html.erb +50 -0
  104. data/app/views/rails_pulse/components/_table_head.html.erb +20 -0
  105. data/app/views/rails_pulse/components/_table_pagination.html.erb +45 -0
  106. data/app/views/rails_pulse/components/_time_period.html.erb +16 -0
  107. data/app/views/rails_pulse/csp_test/show.html.erb +207 -0
  108. data/app/views/rails_pulse/dashboard/charts/_bar_chart.html.erb +1 -0
  109. data/app/views/rails_pulse/dashboard/index.html.erb +64 -0
  110. data/app/views/rails_pulse/dashboard/tables/_routes_table.html.erb +32 -0
  111. data/app/views/rails_pulse/dashboard/tables/_standard_table.html.erb +1 -0
  112. data/app/views/rails_pulse/operations/_operation_analysis_application.html.erb +43 -0
  113. data/app/views/rails_pulse/operations/_operation_analysis_database.html.erb +12 -0
  114. data/app/views/rails_pulse/operations/_operation_analysis_generic.html.erb +15 -0
  115. data/app/views/rails_pulse/operations/_operation_analysis_other.html.erb +69 -0
  116. data/app/views/rails_pulse/operations/_operation_analysis_view.html.erb +39 -0
  117. data/app/views/rails_pulse/operations/show.html.erb +79 -0
  118. data/app/views/rails_pulse/queries/_show_table.html.erb +19 -0
  119. data/app/views/rails_pulse/queries/_table.html.erb +31 -0
  120. data/app/views/rails_pulse/queries/index.html.erb +64 -0
  121. data/app/views/rails_pulse/queries/show.html.erb +86 -0
  122. data/app/views/rails_pulse/requests/_operations.html.erb +85 -0
  123. data/app/views/rails_pulse/requests/_table.html.erb +31 -0
  124. data/app/views/rails_pulse/requests/index.html.erb +64 -0
  125. data/app/views/rails_pulse/requests/show.html.erb +44 -0
  126. data/app/views/rails_pulse/routes/_table.html.erb +29 -0
  127. data/app/views/rails_pulse/routes/index.html.erb +65 -0
  128. data/app/views/rails_pulse/routes/show.html.erb +67 -0
  129. data/app/views/rails_pulse/skeletons/_chart.html.erb +3 -0
  130. data/app/views/rails_pulse/skeletons/_metric_card.html.erb +20 -0
  131. data/app/views/rails_pulse/skeletons/_panel.html.erb +19 -0
  132. data/app/views/rails_pulse/skeletons/_table.html.erb +8 -0
  133. data/config/importmap.rb +12 -0
  134. data/config/initializers/rails_charts_csp_patch.rb +83 -0
  135. data/config/initializers/rails_pulse.rb +198 -0
  136. data/config/routes.rb +16 -0
  137. data/db/migrate/20250227235904_create_routes.rb +12 -0
  138. data/db/migrate/20250227235915_create_requests.rb +19 -0
  139. data/db/migrate/20250228000000_create_queries.rb +14 -0
  140. data/db/migrate/20250228000056_create_operations.rb +24 -0
  141. data/lib/generators/rails_pulse/install_generator.rb +17 -0
  142. data/lib/generators/rails_pulse/templates/rails_pulse.rb +198 -0
  143. data/lib/rails_pulse/cleanup_service.rb +212 -0
  144. data/lib/rails_pulse/configuration.rb +176 -0
  145. data/lib/rails_pulse/engine.rb +88 -0
  146. data/lib/rails_pulse/middleware/asset_server.rb +84 -0
  147. data/lib/rails_pulse/middleware/request_collector.rb +120 -0
  148. data/lib/rails_pulse/migration.rb +29 -0
  149. data/lib/rails_pulse/subscribers/operation_subscriber.rb +280 -0
  150. data/lib/rails_pulse/version.rb +3 -0
  151. data/lib/rails_pulse.rb +38 -0
  152. data/lib/tasks/rails_pulse_tasks.rake +138 -0
  153. data/public/rails-pulse-assets/csp-test.js +110 -0
  154. data/public/rails-pulse-assets/rails-pulse-icons.js +89 -0
  155. data/public/rails-pulse-assets/rails-pulse-icons.js.map +13 -0
  156. data/public/rails-pulse-assets/rails-pulse.css +1 -0
  157. data/public/rails-pulse-assets/rails-pulse.css.map +1 -0
  158. data/public/rails-pulse-assets/rails-pulse.js +183 -0
  159. data/public/rails-pulse-assets/rails-pulse.js.map +7 -0
  160. 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