rails_pulse 0.2.4 ā 0.2.5.pre.pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +269 -12
- data/Rakefile +142 -8
- data/app/assets/stylesheets/rails_pulse/components/table.css +16 -1
- data/app/assets/stylesheets/rails_pulse/components/tags.css +7 -2
- data/app/assets/stylesheets/rails_pulse/components/utilities.css +3 -0
- data/app/controllers/concerns/chart_table_concern.rb +2 -1
- data/app/controllers/rails_pulse/application_controller.rb +11 -1
- data/app/controllers/rails_pulse/assets_controller.rb +18 -2
- data/app/controllers/rails_pulse/job_runs_controller.rb +37 -0
- data/app/controllers/rails_pulse/jobs_controller.rb +80 -0
- data/app/controllers/rails_pulse/operations_controller.rb +43 -31
- data/app/controllers/rails_pulse/queries_controller.rb +1 -1
- data/app/controllers/rails_pulse/requests_controller.rb +3 -9
- data/app/controllers/rails_pulse/routes_controller.rb +1 -1
- data/app/controllers/rails_pulse/tags_controller.rb +31 -5
- data/app/helpers/rails_pulse/application_helper.rb +32 -1
- data/app/helpers/rails_pulse/breadcrumbs_helper.rb +15 -1
- data/app/helpers/rails_pulse/status_helper.rb +16 -0
- data/app/helpers/rails_pulse/tags_helper.rb +39 -1
- data/app/javascript/rails_pulse/controllers/chart_controller.js +112 -8
- data/app/models/concerns/rails_pulse/taggable.rb +25 -2
- data/app/models/rails_pulse/charts/operations_chart.rb +33 -0
- data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +1 -2
- data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +1 -1
- data/app/models/rails_pulse/job.rb +85 -0
- data/app/models/rails_pulse/job_run.rb +76 -0
- data/app/models/rails_pulse/jobs/cards/average_duration.rb +85 -0
- data/app/models/rails_pulse/jobs/cards/base.rb +70 -0
- data/app/models/rails_pulse/jobs/cards/failure_rate.rb +85 -0
- data/app/models/rails_pulse/jobs/cards/total_jobs.rb +74 -0
- data/app/models/rails_pulse/jobs/cards/total_runs.rb +48 -0
- data/app/models/rails_pulse/operation.rb +16 -3
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +3 -3
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +1 -1
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +1 -1
- data/app/models/rails_pulse/queries/tables/index.rb +2 -1
- data/app/models/rails_pulse/query.rb +10 -1
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +3 -2
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +1 -1
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +1 -1
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +1 -1
- data/app/models/rails_pulse/routes/tables/index.rb +2 -1
- data/app/models/rails_pulse/summary.rb +10 -3
- data/app/services/rails_pulse/summary_service.rb +46 -0
- data/app/views/layouts/rails_pulse/_menu_items.html.erb +7 -0
- data/app/views/layouts/rails_pulse/application.html.erb +23 -0
- data/app/views/rails_pulse/components/_active_filters.html.erb +7 -6
- data/app/views/rails_pulse/components/_page_header.html.erb +8 -7
- data/app/views/rails_pulse/components/_table.html.erb +7 -4
- data/app/views/rails_pulse/dashboard/index.html.erb +1 -1
- data/app/views/rails_pulse/job_runs/_operations.html.erb +78 -0
- data/app/views/rails_pulse/job_runs/index.html.erb +3 -0
- data/app/views/rails_pulse/job_runs/show.html.erb +51 -0
- data/app/views/rails_pulse/jobs/_job_runs_table.html.erb +35 -0
- data/app/views/rails_pulse/jobs/_table.html.erb +43 -0
- data/app/views/rails_pulse/jobs/index.html.erb +34 -0
- data/app/views/rails_pulse/jobs/show.html.erb +49 -0
- data/app/views/rails_pulse/operations/_operation_analysis_application.html.erb +29 -27
- data/app/views/rails_pulse/operations/_operation_analysis_view.html.erb +11 -9
- data/app/views/rails_pulse/operations/show.html.erb +10 -8
- data/app/views/rails_pulse/queries/_table.html.erb +3 -3
- data/app/views/rails_pulse/requests/_table.html.erb +6 -6
- data/app/views/rails_pulse/routes/_table.html.erb +3 -3
- data/app/views/rails_pulse/routes/show.html.erb +1 -1
- data/app/views/rails_pulse/tags/_tag_manager.html.erb +7 -14
- data/config/brakeman.ignore +213 -0
- data/config/brakeman.yml +68 -0
- data/config/initializers/rails_pulse.rb +52 -0
- data/config/routes.rb +6 -0
- data/db/rails_pulse_migrate/20250113000000_add_jobs_to_rails_pulse.rb +95 -0
- data/db/rails_pulse_migrate/20250122000000_add_query_fingerprinting.rb +150 -0
- data/db/rails_pulse_migrate/20250202000000_add_index_to_request_uuid.rb +14 -0
- data/db/rails_pulse_schema.rb +186 -103
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +186 -103
- data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +30 -1
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +31 -0
- data/lib/rails_pulse/active_job_extensions.rb +13 -0
- data/lib/rails_pulse/adapters/delayed_job_plugin.rb +25 -0
- data/lib/rails_pulse/adapters/sidekiq_middleware.rb +41 -0
- data/lib/rails_pulse/cleanup_service.rb +65 -0
- data/lib/rails_pulse/configuration.rb +80 -7
- data/lib/rails_pulse/engine.rb +34 -3
- data/lib/rails_pulse/extensions/active_record.rb +82 -0
- data/lib/rails_pulse/job_run_collector.rb +172 -0
- data/lib/rails_pulse/middleware/request_collector.rb +20 -43
- data/lib/rails_pulse/subscribers/operation_subscriber.rb +11 -5
- data/lib/rails_pulse/tracker.rb +82 -0
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/rails_pulse.rb +2 -0
- data/lib/rails_pulse_server.ru +107 -0
- data/lib/tasks/rails_pulse_benchmark.rake +382 -0
- data/public/rails-pulse-assets/rails-pulse-icons.js +3 -2
- data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +1 -1
- data/public/rails-pulse-assets/rails-pulse.js.map +3 -3
- metadata +35 -7
- data/app/models/rails_pulse/requests/charts/operations_chart.rb +0 -35
- data/db/migrate/20250930105043_install_rails_pulse_tables.rb +0 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 534617e370c257d7fd5947149d59a73a8fd988d3898605a1b8f2a61c38340b10
|
|
4
|
+
data.tar.gz: c82d0e9acbed69e4c8d602c21204da8da165805506c0d57568762883ea2f7aab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dd8507f19548fe4be681e07b5d9128779344528e4d7ebdc5ac4f820e175656568fed1f76255e048657c8da50333a443f257ec5f847c5c56fbab9084d2a02094
|
|
7
|
+
data.tar.gz: 5546a5fd09af8c73a921e784b7e347ec5c1ac6cd1f36b5246290154e315c68f0245bfff5ddb62bbfc1631d248c164dc6122d3634962bc93b36f848f26a1b588a
|
data/README.md
CHANGED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
- [Installation](#installation)
|
|
23
23
|
- [Quick Setup](#quick-setup)
|
|
24
24
|
- [Basic Configuration](#basic-configuration)
|
|
25
|
+
- [Background Job Monitoring](#background-job-monitoring)
|
|
26
|
+
- [Overview](#overview)
|
|
27
|
+
- [Supported Adapters](#supported-adapters)
|
|
28
|
+
- [Job Tracking Configuration](#job-tracking-configuration)
|
|
29
|
+
- [Privacy & Security](#privacy--security)
|
|
25
30
|
- [Authentication](#authentication)
|
|
26
31
|
- [Authentication Setup](#authentication-setup)
|
|
27
32
|
- [Authentication Examples](#authentication-examples)
|
|
@@ -37,6 +42,9 @@
|
|
|
37
42
|
- [Configuration](#configuration)
|
|
38
43
|
- [Database Configuration](#database-configuration)
|
|
39
44
|
- [Schema Loading](#schema-loading)
|
|
45
|
+
- [Performance Impact](#performance-impact)
|
|
46
|
+
- [Running Performance Benchmarks](#running-performance-benchmarks)
|
|
47
|
+
- [Standalone Dashboard Deployment](#standalone-dashboard-deployment)
|
|
40
48
|
- [Testing](#testing)
|
|
41
49
|
- [Technology Stack](#technology-stack)
|
|
42
50
|
- [Advantages Over Other Solutions](#advantages-over-other-solutions)
|
|
@@ -54,8 +62,16 @@ Rails Pulse is a comprehensive performance monitoring and debugging gem that pro
|
|
|
54
62
|
- Interactive dashboard with response time charts and request analytics
|
|
55
63
|
- SQL query performance tracking with slow query identification
|
|
56
64
|
- Route-specific metrics with configurable performance thresholds
|
|
65
|
+
- **Background job monitoring** with execution tracking and failure analysis
|
|
57
66
|
- Week-over-week trend analysis with visual indicators
|
|
58
67
|
|
|
68
|
+
### Background Job Tracking
|
|
69
|
+
- **Universal job tracking** compatible with all ActiveJob adapters
|
|
70
|
+
- Monitor job performance, failures, and retries
|
|
71
|
+
- Track individual job executions with detailed metrics
|
|
72
|
+
- View operations and SQL queries executed during jobs
|
|
73
|
+
- Configurable privacy controls for job arguments
|
|
74
|
+
|
|
59
75
|
### Developer Experience
|
|
60
76
|
- Zero configuration setup with sensible defaults
|
|
61
77
|
- Beautiful responsive interface with dark/light mode
|
|
@@ -63,7 +79,7 @@ Rails Pulse is a comprehensive performance monitoring and debugging gem that pro
|
|
|
63
79
|
- Multiple database support (SQLite, PostgreSQL, MySQL)
|
|
64
80
|
|
|
65
81
|
### Organization & Filtering
|
|
66
|
-
- Flexible tagging system for routes, requests, and
|
|
82
|
+
- Flexible tagging system for routes, requests, queries, and jobs
|
|
67
83
|
- Filter performance data by custom tags
|
|
68
84
|
- Organize monitoring data by environment, priority, or custom categories
|
|
69
85
|
|
|
@@ -172,10 +188,21 @@ RailsPulse.configure do |config|
|
|
|
172
188
|
critical: 1000
|
|
173
189
|
}
|
|
174
190
|
|
|
191
|
+
# Set performance thresholds for background jobs (in milliseconds)
|
|
192
|
+
config.job_thresholds = {
|
|
193
|
+
slow: 5_000, # 5 seconds
|
|
194
|
+
very_slow: 30_000, # 30 seconds
|
|
195
|
+
critical: 60_000 # 1 minute
|
|
196
|
+
}
|
|
197
|
+
|
|
175
198
|
# Asset tracking configuration
|
|
176
199
|
config.track_assets = false # Ignore asset requests by default
|
|
177
200
|
config.custom_asset_patterns = [] # Additional asset patterns to ignore
|
|
178
201
|
|
|
202
|
+
# Job tracking configuration
|
|
203
|
+
config.track_jobs = true # Enable background job tracking
|
|
204
|
+
config.capture_job_arguments = false # Disable argument capture for privacy
|
|
205
|
+
|
|
179
206
|
# Rails Pulse mount path (optional)
|
|
180
207
|
# Specify if Rails Pulse is mounted at a custom path to prevent self-tracking
|
|
181
208
|
config.mount_path = nil # e.g., "/admin/monitoring"
|
|
@@ -184,18 +211,22 @@ RailsPulse.configure do |config|
|
|
|
184
211
|
config.ignored_routes = [] # Array of strings or regex patterns
|
|
185
212
|
config.ignored_requests = [] # Array of request patterns to ignore
|
|
186
213
|
config.ignored_queries = [] # Array of query patterns to ignore
|
|
214
|
+
config.ignored_jobs = [] # Array of job class names to ignore
|
|
215
|
+
config.ignored_queues = [] # Array of queue names to ignore
|
|
187
216
|
|
|
188
217
|
# Tagging system - define available tags for categorizing performance data
|
|
189
218
|
config.tags = ["production", "staging", "critical", "needs-optimization"]
|
|
190
219
|
|
|
191
220
|
# Data cleanup
|
|
192
|
-
config.archiving_enabled = true
|
|
193
|
-
config.full_retention_period =
|
|
194
|
-
config.max_table_records = {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
rails_pulse_queries:
|
|
221
|
+
config.archiving_enabled = true # Enable automatic cleanup
|
|
222
|
+
config.full_retention_period = 30.days # Delete records older than this
|
|
223
|
+
config.max_table_records = { # Maximum records per table
|
|
224
|
+
rails_pulse_operations: 100_000,
|
|
225
|
+
rails_pulse_requests: 50_000,
|
|
226
|
+
rails_pulse_job_runs: 50_000,
|
|
227
|
+
rails_pulse_queries: 10_000,
|
|
228
|
+
rails_pulse_routes: 1_000,
|
|
229
|
+
rails_pulse_jobs: 1_000
|
|
199
230
|
}
|
|
200
231
|
|
|
201
232
|
# Multiple database support (optional)
|
|
@@ -206,6 +237,117 @@ RailsPulse.configure do |config|
|
|
|
206
237
|
end
|
|
207
238
|
```
|
|
208
239
|
|
|
240
|
+
## Background Job Monitoring
|
|
241
|
+
|
|
242
|
+
Rails Pulse provides comprehensive monitoring for ActiveJob background jobs, tracking performance, failures, and execution details across all major job adapters.
|
|
243
|
+
|
|
244
|
+
### Overview
|
|
245
|
+
|
|
246
|
+
Background job monitoring is **enabled by default** and works automatically with any ActiveJob adapter. Rails Pulse captures:
|
|
247
|
+
|
|
248
|
+
- **Job execution metrics**: Duration, status, retry attempts
|
|
249
|
+
- **Failure tracking**: Error class, error message, failure rates
|
|
250
|
+
- **Performance analysis**: Slow jobs, aggregate metrics by job class
|
|
251
|
+
- **Operation timeline**: SQL queries and operations during job execution
|
|
252
|
+
- **Job arguments**: Optional capture for debugging (disabled by default for privacy)
|
|
253
|
+
|
|
254
|
+
Access the jobs dashboard at `/rails_pulse/jobs` to view:
|
|
255
|
+
- All job classes with aggregate metrics (total runs, failure rate, average duration)
|
|
256
|
+
- Individual job executions with detailed performance data
|
|
257
|
+
- Filtering by time range, status, queue, and performance thresholds
|
|
258
|
+
- Tagging support for organizing jobs by team, priority, or category
|
|
259
|
+
|
|
260
|
+
### Supported Adapters
|
|
261
|
+
|
|
262
|
+
Rails Pulse works with all ActiveJob adapters through universal tracking:
|
|
263
|
+
|
|
264
|
+
- **Sidekiq** - Enhanced tracking via custom middleware
|
|
265
|
+
- **Solid Queue** - Universal ActiveJob tracking
|
|
266
|
+
- **Good Job** - Universal ActiveJob tracking
|
|
267
|
+
- **Delayed Job** - Enhanced tracking via custom plugin
|
|
268
|
+
- **Resque** - Universal ActiveJob tracking
|
|
269
|
+
- **Any ActiveJob adapter** - Falls back to universal tracking
|
|
270
|
+
|
|
271
|
+
No additional configuration needed - job tracking works out of the box with your existing setup.
|
|
272
|
+
|
|
273
|
+
### Job Tracking Configuration
|
|
274
|
+
|
|
275
|
+
Customize job tracking in your Rails Pulse initializer:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
RailsPulse.configure do |config|
|
|
279
|
+
# Enable or disable job tracking (default: true)
|
|
280
|
+
config.track_jobs = true
|
|
281
|
+
|
|
282
|
+
# Set performance thresholds for jobs (in milliseconds)
|
|
283
|
+
config.job_thresholds = {
|
|
284
|
+
slow: 5_000, # 5 seconds
|
|
285
|
+
very_slow: 30_000, # 30 seconds
|
|
286
|
+
critical: 60_000 # 1 minute
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# Ignore specific job classes from tracking
|
|
290
|
+
config.ignored_jobs = [
|
|
291
|
+
"ActiveStorage::AnalyzeJob",
|
|
292
|
+
"ActiveStorage::PurgeJob"
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
# Ignore specific queues from tracking
|
|
296
|
+
config.ignored_queues = ["low_priority", "mailers"]
|
|
297
|
+
|
|
298
|
+
# Capture job arguments for debugging (default: false)
|
|
299
|
+
# WARNING: May expose sensitive data - use with caution
|
|
300
|
+
config.capture_job_arguments = false
|
|
301
|
+
|
|
302
|
+
# Configure adapter-specific settings
|
|
303
|
+
config.job_adapters = {
|
|
304
|
+
sidekiq: { enabled: true, track_queue_depth: false },
|
|
305
|
+
solid_queue: { enabled: true, track_recurring: false },
|
|
306
|
+
good_job: { enabled: true, track_cron: false },
|
|
307
|
+
delayed_job: { enabled: true },
|
|
308
|
+
resque: { enabled: true }
|
|
309
|
+
}
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Disabling job tracking for specific jobs:**
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
class MyBackgroundJob < ApplicationJob
|
|
317
|
+
# Skip Rails Pulse tracking for this job
|
|
318
|
+
def perform(*args)
|
|
319
|
+
RailsPulse.with_tracking_disabled do
|
|
320
|
+
# Job logic here
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Privacy & Security
|
|
327
|
+
|
|
328
|
+
**Job argument capture is disabled by default** to protect sensitive information. Job arguments may contain:
|
|
329
|
+
- User credentials or tokens
|
|
330
|
+
- Personal identifiable information (PII)
|
|
331
|
+
- API keys or secrets
|
|
332
|
+
- Sensitive business data
|
|
333
|
+
|
|
334
|
+
Only enable `capture_job_arguments` in development or when explicitly needed for debugging. Consider using parameter filtering if you need to capture arguments:
|
|
335
|
+
|
|
336
|
+
```ruby
|
|
337
|
+
# In your job class
|
|
338
|
+
class SensitiveJob < ApplicationJob
|
|
339
|
+
def perform(user_id:, api_key:)
|
|
340
|
+
# Rails Pulse will track execution but not arguments by default
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Performance impact:**
|
|
346
|
+
- Minimal overhead: ~1-2ms per job execution
|
|
347
|
+
- No blocking of job processing
|
|
348
|
+
- Configurable cleanup prevents database growth
|
|
349
|
+
- Can be disabled per-job or globally
|
|
350
|
+
|
|
209
351
|
## Authentication
|
|
210
352
|
|
|
211
353
|
Rails Pulse supports flexible authentication to secure access to your monitoring dashboard.
|
|
@@ -257,7 +399,7 @@ config.authentication_method = proc {
|
|
|
257
399
|
|
|
258
400
|
## Tagging System
|
|
259
401
|
|
|
260
|
-
Rails Pulse includes a flexible tagging system that allows you to categorize and organize your performance data. Tag routes, requests, and
|
|
402
|
+
Rails Pulse includes a flexible tagging system that allows you to categorize and organize your performance data. Tag routes, requests, queries, jobs, and job runs with custom labels to better organize and filter your monitoring data.
|
|
261
403
|
|
|
262
404
|
### Configuring Tags
|
|
263
405
|
|
|
@@ -280,7 +422,7 @@ end
|
|
|
280
422
|
|
|
281
423
|
**Tag from the UI:**
|
|
282
424
|
|
|
283
|
-
1. Navigate to any route, request, or
|
|
425
|
+
1. Navigate to any route, request, query, job, or job run detail page
|
|
284
426
|
2. Click the "+ tag" button next to the record
|
|
285
427
|
3. Select from your configured tags
|
|
286
428
|
4. Remove tags by clicking the Ć button on any tag badge
|
|
@@ -297,6 +439,15 @@ route.add_tag("high-traffic")
|
|
|
297
439
|
query = RailsPulse::Query.find_by(normalized_sql: "SELECT * FROM users WHERE id = ?")
|
|
298
440
|
query.add_tag("needs-optimization")
|
|
299
441
|
|
|
442
|
+
# Tag a job
|
|
443
|
+
job = RailsPulse::Job.find_by(name: "UserNotificationJob")
|
|
444
|
+
job.add_tag("high-priority")
|
|
445
|
+
job.add_tag("user-facing")
|
|
446
|
+
|
|
447
|
+
# Tag a specific job run
|
|
448
|
+
job_run = RailsPulse::JobRun.find_by(run_id: "abc123")
|
|
449
|
+
job_run.add_tag("investigated")
|
|
450
|
+
|
|
300
451
|
# Remove a tag
|
|
301
452
|
route.remove_tag("critical")
|
|
302
453
|
|
|
@@ -486,6 +637,113 @@ The schema file `db/rails_pulse_schema.rb` serves as your single source of truth
|
|
|
486
637
|
- Should not be deleted or modified
|
|
487
638
|
- Future updates will provide migrations in `db/rails_pulse_migrate/`
|
|
488
639
|
|
|
640
|
+
## Performance Impact
|
|
641
|
+
|
|
642
|
+
Rails Pulse uses **fiber-based async tracking** for minimal performance overhead:
|
|
643
|
+
|
|
644
|
+
- **Request overhead:** ~0.1ms per request (data collection only)
|
|
645
|
+
- **Database writes:** Non-blocking, handled in background fibers
|
|
646
|
+
- **Memory allocation:** Minimal, ~200 KB per request (temporary)
|
|
647
|
+
- **Thread safety:** Proper connection pooling with isolated database connections
|
|
648
|
+
|
|
649
|
+
This is handled automatically and requires no configuration.
|
|
650
|
+
|
|
651
|
+
### Running Performance Benchmarks
|
|
652
|
+
|
|
653
|
+
Rails Pulse includes built-in benchmarking tools. To use them:
|
|
654
|
+
|
|
655
|
+
```ruby
|
|
656
|
+
# Add to your Gemfile (development/test group)
|
|
657
|
+
gem 'benchmark-ips'
|
|
658
|
+
gem 'memory_profiler'
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
```bash
|
|
662
|
+
bundle install
|
|
663
|
+
|
|
664
|
+
# Run all benchmarks
|
|
665
|
+
bundle exec rake rails_pulse:benchmark:all
|
|
666
|
+
|
|
667
|
+
# Run specific benchmarks
|
|
668
|
+
bundle exec rake rails_pulse:benchmark:memory
|
|
669
|
+
bundle exec rake rails_pulse:benchmark:request_overhead
|
|
670
|
+
bundle exec rake rails_pulse:benchmark:middleware
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
## Standalone Dashboard Deployment
|
|
674
|
+
|
|
675
|
+
For production environments, you can run the Rails Pulse dashboard as a standalone application, separate from your main Rails app. This provides several benefits:
|
|
676
|
+
|
|
677
|
+
- **Dashboard remains accessible when main app is under heavy load**
|
|
678
|
+
- **Separate resource allocation** for monitoring vs application
|
|
679
|
+
- **Enhanced security** - isolate dashboard access from public app
|
|
680
|
+
- **Independent scaling** - dashboard doesn't scale with app instances
|
|
681
|
+
|
|
682
|
+
**Quick Setup:**
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
# Option 1: Set DATABASE_URL environment variable (recommended for production)
|
|
686
|
+
export DATABASE_URL="postgresql://user:pass@host/db"
|
|
687
|
+
bundle exec rackup lib/rails_pulse_server.ru -p 3001
|
|
688
|
+
|
|
689
|
+
# Option 2: Use config/database.yml (recommended for development)
|
|
690
|
+
# Looks for 'rails_pulse' connection, falls back to primary
|
|
691
|
+
# No environment variable needed - automatically reads from config/database.yml
|
|
692
|
+
bundle exec rackup lib/rails_pulse_server.ru -p 3001
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Healthcheck Endpoint:**
|
|
696
|
+
|
|
697
|
+
The standalone server includes a `/health` endpoint that verifies database connectivity:
|
|
698
|
+
|
|
699
|
+
```bash
|
|
700
|
+
curl http://localhost:3001/health
|
|
701
|
+
# Returns: {"status":"ok","mode":"dashboard","database":"connected","timestamp":"..."}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**Deployment Options:**
|
|
705
|
+
|
|
706
|
+
1. **Separate subdomain (recommended):**
|
|
707
|
+
```nginx
|
|
708
|
+
server {
|
|
709
|
+
server_name pulse.myapp.com;
|
|
710
|
+
location / {
|
|
711
|
+
proxy_pass http://localhost:3001;
|
|
712
|
+
proxy_set_header Host $host;
|
|
713
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
714
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
715
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
2. **Kamal Deployment:**
|
|
721
|
+
Deploy the dashboard as an accessory
|
|
722
|
+
|
|
723
|
+
```yaml
|
|
724
|
+
# config/deploy.yml
|
|
725
|
+
accessories:
|
|
726
|
+
rails_pulse:
|
|
727
|
+
image: your-app-image # Same image as your main app
|
|
728
|
+
host: your-server
|
|
729
|
+
cmd: bundle exec rackup lib/rails_pulse_server.ru -p 3001
|
|
730
|
+
env:
|
|
731
|
+
clear:
|
|
732
|
+
DATABASE_URL: "postgresql://user:pass@host/db"
|
|
733
|
+
RAILS_ENV: production
|
|
734
|
+
SECRET_KEY_BASE: <%= ENV.fetch("SECRET_KEY_BASE") %>
|
|
735
|
+
port: "3001:3001"
|
|
736
|
+
healthcheck:
|
|
737
|
+
path: /health
|
|
738
|
+
port: 3001
|
|
739
|
+
interval: 10s
|
|
740
|
+
timeout: 5s
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
**Note:** When running standalone, the dashboard is read-only and doesn't track its own requests (tracking is automatically disabled).
|
|
744
|
+
|
|
745
|
+
For detailed deployment instructions, see [docs/deployment-modes.md](docs/deployment-modes.md).
|
|
746
|
+
|
|
489
747
|
## Testing
|
|
490
748
|
|
|
491
749
|
Rails Pulse includes a comprehensive test suite designed for speed and reliability across multiple databases and Rails versions.
|
|
@@ -652,7 +910,6 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
652
910
|
<div align="center">
|
|
653
911
|
<strong>Built with ā¤ļø for the Rails community</strong>
|
|
654
912
|
|
|
655
|
-
[Documentation](https://github.com/railspulse/rails_pulse/wiki) ā¢
|
|
656
913
|
[Issues](https://github.com/railspulse/rails_pulse/issues) ā¢
|
|
657
|
-
[
|
|
914
|
+
[Discussions](https://github.com/railspulse/rails_pulse/discussions)
|
|
658
915
|
</div>
|
data/Rakefile
CHANGED
|
@@ -7,6 +7,57 @@ require "dotenv/load" if File.exist?(".env")
|
|
|
7
7
|
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
|
8
8
|
load "rails/tasks/engine.rake"
|
|
9
9
|
|
|
10
|
+
desc "Verify dummy app migrations are in sync with gem migrations"
|
|
11
|
+
task :verify_dummy_migrations do
|
|
12
|
+
# Check if db/rails_pulse_migrate directory exists (separate database setup)
|
|
13
|
+
if Dir.exist?("db/rails_pulse_migrate")
|
|
14
|
+
gem_migrations = Dir["db/rails_pulse_migrate/*.rb"].map { |f| File.basename(f) }.sort
|
|
15
|
+
|
|
16
|
+
# Get all RailsPulse migrations from dummy app (exclude dummy app's own migrations)
|
|
17
|
+
all_dummy_migrations = Dir["test/dummy/db/migrate/*.rb"].map { |f| File.basename(f) }
|
|
18
|
+
|
|
19
|
+
# Filter to only RailsPulse migrations (contain "rails_pulse" in name or match known patterns)
|
|
20
|
+
dummy_migrations = all_dummy_migrations.select do |m|
|
|
21
|
+
m.include?("rails_pulse") ||
|
|
22
|
+
m.include?("jobs") ||
|
|
23
|
+
m.include?("query") ||
|
|
24
|
+
m.include?("request_uuid")
|
|
25
|
+
end.sort
|
|
26
|
+
|
|
27
|
+
missing = gem_migrations - dummy_migrations
|
|
28
|
+
|
|
29
|
+
if missing.any?
|
|
30
|
+
puts "\nā Dummy app is missing Rails Pulse migrations:"
|
|
31
|
+
missing.each { |m| puts " ⢠#{m}" }
|
|
32
|
+
puts "\nTo fix this, run:"
|
|
33
|
+
puts " cd test/dummy"
|
|
34
|
+
puts " rails generate rails_pulse:upgrade"
|
|
35
|
+
puts " rails db:migrate RAILS_ENV=test"
|
|
36
|
+
puts "\nThen commit the new migration files."
|
|
37
|
+
exit 1
|
|
38
|
+
else
|
|
39
|
+
puts "ā
Dummy app migrations are in sync with gem migrations"
|
|
40
|
+
end
|
|
41
|
+
else
|
|
42
|
+
puts "ā
Dummy app migrations check skipped (single database setup)"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "Sync Rails Pulse schema to test/dummy app"
|
|
47
|
+
task :sync_test_schema do
|
|
48
|
+
require "fileutils"
|
|
49
|
+
|
|
50
|
+
source = "db/rails_pulse_schema.rb"
|
|
51
|
+
dest = "test/dummy/db/rails_pulse_schema.rb"
|
|
52
|
+
|
|
53
|
+
if File.exist?(source)
|
|
54
|
+
FileUtils.cp(source, dest)
|
|
55
|
+
puts "ā
Synced schema: #{source} ā #{dest}"
|
|
56
|
+
else
|
|
57
|
+
puts "ā ļø Source schema not found: #{source}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
10
61
|
desc "Setup database for testing"
|
|
11
62
|
task :test_setup do
|
|
12
63
|
database = ENV['DB'] || 'sqlite3'
|
|
@@ -19,6 +70,9 @@ task :test_setup do
|
|
|
19
70
|
puts
|
|
20
71
|
|
|
21
72
|
begin
|
|
73
|
+
# Sync schema file from gem to test/dummy
|
|
74
|
+
Rake::Task[:sync_test_schema].invoke
|
|
75
|
+
|
|
22
76
|
# Remove schema.rb to ensure clean migration
|
|
23
77
|
schema_file = "test/dummy/db/schema.rb"
|
|
24
78
|
if File.exist?(schema_file)
|
|
@@ -102,6 +156,10 @@ task :test_setup_for_version, [ :database, :rails_version ] do |t, args|
|
|
|
102
156
|
puts
|
|
103
157
|
|
|
104
158
|
begin
|
|
159
|
+
# Sync schema file from gem to test/dummy
|
|
160
|
+
Rake::Task[:sync_test_schema].reenable
|
|
161
|
+
Rake::Task[:sync_test_schema].invoke
|
|
162
|
+
|
|
105
163
|
# Remove schema.rb to ensure clean migration
|
|
106
164
|
schema_file = "test/dummy/db/schema.rb"
|
|
107
165
|
if File.exist?(schema_file)
|
|
@@ -203,9 +261,33 @@ task :test_release do
|
|
|
203
261
|
|
|
204
262
|
failed_tasks = []
|
|
205
263
|
current_step = 0
|
|
206
|
-
total_steps =
|
|
264
|
+
total_steps = 11
|
|
265
|
+
|
|
266
|
+
# Step 1: Sync test schema
|
|
267
|
+
current_step += 1
|
|
268
|
+
begin
|
|
269
|
+
puts "\n[#{current_step}/#{total_steps}] Syncing test schema..."
|
|
270
|
+
puts "-" * 70
|
|
271
|
+
Rake::Task[:sync_test_schema].invoke
|
|
272
|
+
rescue => e
|
|
273
|
+
puts "ā Schema sync failed!"
|
|
274
|
+
puts " Error: #{e.message}"
|
|
275
|
+
failed_tasks << "sync_test_schema"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Step 2: Verify dummy migrations
|
|
279
|
+
current_step += 1
|
|
280
|
+
begin
|
|
281
|
+
puts "\n[#{current_step}/#{total_steps}] Verifying dummy app migrations..."
|
|
282
|
+
puts "-" * 70
|
|
283
|
+
Rake::Task[:verify_dummy_migrations].invoke
|
|
284
|
+
rescue => e
|
|
285
|
+
puts "ā Dummy app migration verification failed!"
|
|
286
|
+
puts " Error: #{e.message}"
|
|
287
|
+
failed_tasks << "verify_dummy_migrations"
|
|
288
|
+
end
|
|
207
289
|
|
|
208
|
-
# Step
|
|
290
|
+
# Step 3: Git status check
|
|
209
291
|
current_step += 1
|
|
210
292
|
begin
|
|
211
293
|
puts "\n[#{current_step}/#{total_steps}] Checking git status..."
|
|
@@ -225,7 +307,7 @@ task :test_release do
|
|
|
225
307
|
puts "ā ļø Warning: Could not check git status (#{e.message})"
|
|
226
308
|
end
|
|
227
309
|
|
|
228
|
-
# Step
|
|
310
|
+
# Step 5: RuboCop linting
|
|
229
311
|
current_step += 1
|
|
230
312
|
begin
|
|
231
313
|
puts "\n[#{current_step}/#{total_steps}] Running RuboCop linting..."
|
|
@@ -238,7 +320,19 @@ task :test_release do
|
|
|
238
320
|
failed_tasks << "rubocop"
|
|
239
321
|
end
|
|
240
322
|
|
|
241
|
-
# Step
|
|
323
|
+
# Step 6: Brakeman security scan
|
|
324
|
+
current_step += 1
|
|
325
|
+
begin
|
|
326
|
+
puts "\n[#{current_step}/#{total_steps}] Running Brakeman security scanner..."
|
|
327
|
+
puts "-" * 70
|
|
328
|
+
Rake::Task[:brakeman].invoke
|
|
329
|
+
rescue => e
|
|
330
|
+
puts "ā Brakeman security scan failed!"
|
|
331
|
+
puts " Error: #{e.message}"
|
|
332
|
+
failed_tasks << "brakeman"
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Step 7: Install Node dependencies
|
|
242
336
|
current_step += 1
|
|
243
337
|
begin
|
|
244
338
|
puts "\n[#{current_step}/#{total_steps}] Installing Node dependencies..."
|
|
@@ -251,7 +345,7 @@ task :test_release do
|
|
|
251
345
|
failed_tasks << "npm_install"
|
|
252
346
|
end
|
|
253
347
|
|
|
254
|
-
# Step
|
|
348
|
+
# Step 8: Build and verify assets
|
|
255
349
|
current_step += 1
|
|
256
350
|
begin
|
|
257
351
|
puts "\n[#{current_step}/#{total_steps}] Building production assets..."
|
|
@@ -273,7 +367,7 @@ task :test_release do
|
|
|
273
367
|
failed_tasks << "npm_build"
|
|
274
368
|
end
|
|
275
369
|
|
|
276
|
-
# Step
|
|
370
|
+
# Step 9: Verify gem builds
|
|
277
371
|
current_step += 1
|
|
278
372
|
begin
|
|
279
373
|
puts "\n[#{current_step}/#{total_steps}] Verifying gem builds correctly..."
|
|
@@ -291,7 +385,7 @@ task :test_release do
|
|
|
291
385
|
failed_tasks << "gem_build"
|
|
292
386
|
end
|
|
293
387
|
|
|
294
|
-
# Step
|
|
388
|
+
# Step 10: Run generator tests
|
|
295
389
|
current_step += 1
|
|
296
390
|
begin
|
|
297
391
|
puts "\n[#{current_step}/#{total_steps}] Running generator tests..."
|
|
@@ -304,7 +398,7 @@ task :test_release do
|
|
|
304
398
|
failed_tasks << "test_generators"
|
|
305
399
|
end
|
|
306
400
|
|
|
307
|
-
# Step
|
|
401
|
+
# Step 11: Run full test matrix with system tests
|
|
308
402
|
current_step += 1
|
|
309
403
|
begin
|
|
310
404
|
puts "\n[#{current_step}/#{total_steps}] Running full test matrix with system tests..."
|
|
@@ -337,5 +431,45 @@ task :test_release do
|
|
|
337
431
|
end
|
|
338
432
|
end
|
|
339
433
|
|
|
434
|
+
desc "Run Brakeman security scanner"
|
|
435
|
+
task :brakeman do
|
|
436
|
+
require "brakeman"
|
|
437
|
+
|
|
438
|
+
puts "\n" + "=" * 50
|
|
439
|
+
puts "š Running Brakeman Security Scanner"
|
|
440
|
+
puts "=" * 50
|
|
441
|
+
puts
|
|
442
|
+
|
|
443
|
+
begin
|
|
444
|
+
# Run Brakeman with the configuration file
|
|
445
|
+
result = Brakeman.run(
|
|
446
|
+
app_path: ".",
|
|
447
|
+
config_file: "config/brakeman.yml",
|
|
448
|
+
print_report: true,
|
|
449
|
+
pager: false
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Check if any unignored warnings were found
|
|
453
|
+
# result.filtered_warnings only includes warnings that aren't ignored
|
|
454
|
+
unignored_warnings = result.filtered_warnings
|
|
455
|
+
total_warnings = result.warnings.count
|
|
456
|
+
ignored_count = total_warnings - unignored_warnings.count
|
|
457
|
+
|
|
458
|
+
if unignored_warnings.any? || result.errors.any?
|
|
459
|
+
puts "\nā Security issues found!"
|
|
460
|
+
puts " Warnings: #{unignored_warnings.count}"
|
|
461
|
+
puts " Ignored: #{ignored_count}" if ignored_count > 0
|
|
462
|
+
puts " Errors: #{result.errors.count}"
|
|
463
|
+
exit 1
|
|
464
|
+
else
|
|
465
|
+
puts "\nā
No security issues found!"
|
|
466
|
+
puts " (#{ignored_count} warnings reviewed and ignored)" if ignored_count > 0
|
|
467
|
+
end
|
|
468
|
+
rescue => e
|
|
469
|
+
puts "\nā Brakeman scan failed!"
|
|
470
|
+
puts " Error: #{e.message}"
|
|
471
|
+
exit 1
|
|
472
|
+
end
|
|
473
|
+
end
|
|
340
474
|
|
|
341
475
|
task default: :test
|
|
@@ -25,10 +25,25 @@
|
|
|
25
25
|
text-align: start;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
th,
|
|
28
|
+
th,
|
|
29
|
+
td {
|
|
29
30
|
padding: var(--size-2);
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/* Truncated cell styles */
|
|
34
|
+
td.truncate-cell {
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
text-overflow: ellipsis;
|
|
37
|
+
white-space: nowrap;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
td.truncate-cell a {
|
|
41
|
+
display: block;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
text-overflow: ellipsis;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
tfoot {
|
|
33
48
|
background-color: rgb(from var(--color-border-light) r g b / .5);
|
|
34
49
|
border-block-start-width: var(--border);
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
flex-wrap: wrap;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
.tag-list span {
|
|
30
|
+
padding-right: 3px !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
/* Individual Tag */
|
|
30
34
|
.tag {
|
|
31
35
|
display: inline-flex;
|
|
@@ -63,9 +67,10 @@
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
.tag-remove span {
|
|
66
|
-
font-size: 1.25rem;
|
|
67
70
|
line-height: 1;
|
|
68
|
-
font-weight:
|
|
71
|
+
font-weight: inherit;
|
|
72
|
+
margin-left: 6px;
|
|
73
|
+
font-size: 17px;
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
/* Add Tag Container */
|
|
@@ -51,7 +51,8 @@ module ChartTableConcern
|
|
|
51
51
|
table_results = build_table_results
|
|
52
52
|
handle_pagination
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
# Use pagy_options for version compatibility
|
|
55
|
+
@pagy, @table_data = pagy(table_results, **pagy_options(session_pagination_limit))
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def setup_zoom_range_data
|