rails_pulse 0.4.2 → 0.5.0.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/CHANGELOG.md +58 -1
- data/README.md +15 -4
- data/app/assets/stylesheets/rails_pulse/components/chart.css +11 -0
- data/app/assets/stylesheets/rails_pulse/components/descriptive_list.css +19 -1
- data/app/assets/stylesheets/rails_pulse/components/row.css +3 -17
- data/app/controllers/concerns/chart_table_concern.rb +28 -69
- data/app/controllers/concerns/deployment_markers_concern.rb +3 -1
- data/app/controllers/concerns/metric_card_concern.rb +6 -7
- data/app/controllers/rails_pulse/api/v1/base_controller.rb +133 -0
- data/app/controllers/rails_pulse/api/v1/capabilities_controller.rb +27 -0
- data/app/controllers/rails_pulse/api/v1/coverage_controller.rb +158 -0
- data/app/controllers/rails_pulse/api/v1/deployments_controller.rb +40 -0
- data/app/controllers/rails_pulse/api/v1/exceptions_controller.rb +77 -0
- data/app/controllers/rails_pulse/api/v1/insights_controller.rb +62 -0
- data/app/controllers/rails_pulse/api/v1/job_runs_controller.rb +34 -0
- data/app/controllers/rails_pulse/api/v1/jobs_controller.rb +172 -0
- data/app/controllers/rails_pulse/api/v1/queries_controller.rb +123 -0
- data/app/controllers/rails_pulse/api/v1/requests_controller.rb +74 -0
- data/app/controllers/rails_pulse/api/v1/routes_controller.rb +103 -0
- data/app/controllers/rails_pulse/application_controller.rb +1 -1
- data/app/controllers/rails_pulse/dashboard_controller.rb +25 -21
- data/app/controllers/rails_pulse/deployments_controller.rb +23 -13
- data/app/controllers/rails_pulse/exceptions_controller.rb +10 -12
- data/app/controllers/rails_pulse/jobs_controller.rb +1 -2
- data/app/controllers/rails_pulse/queries_controller.rb +6 -5
- data/app/controllers/rails_pulse/requests_controller.rb +15 -14
- data/app/controllers/rails_pulse/routes_controller.rb +4 -8
- data/app/helpers/rails_pulse/application_helper.rb +15 -0
- data/app/helpers/rails_pulse/chart_helper.rb +9 -2
- data/app/helpers/rails_pulse/formatting_helper.rb +11 -12
- data/app/javascript/rails_pulse/controllers/chart_controller.js +80 -43
- data/app/jobs/rails_pulse/backfill_summaries_job.rb +4 -2
- data/app/jobs/rails_pulse/cloud_health_job.rb +11 -0
- data/app/jobs/rails_pulse/cloud_sync_job.rb +11 -0
- data/app/jobs/rails_pulse/summary_job.rb +13 -0
- data/app/models/concerns/rails_pulse/has_metadata.rb +15 -0
- data/app/models/concerns/rails_pulse/taggable.rb +5 -12
- data/app/models/rails_pulse/cards/base.rb +20 -11
- data/app/models/rails_pulse/charts/base.rb +5 -18
- data/app/models/rails_pulse/charts/percentile_chart_base.rb +3 -5
- data/app/models/rails_pulse/cleanup_run.rb +32 -0
- data/app/models/rails_pulse/cloud/buffered_batch.rb +55 -0
- data/app/models/rails_pulse/cloud/installation.rb +55 -0
- data/app/models/rails_pulse/dashboard/charts/response_time_percentiles.rb +4 -5
- data/app/models/rails_pulse/dashboard/charts/throughput_and_errors.rb +4 -5
- data/app/models/rails_pulse/dashboard/concerns/attention_classification.rb +104 -0
- data/app/models/rails_pulse/dashboard/concerns/time_range_helper.rb +25 -2
- data/app/models/rails_pulse/dashboard/health_summary.rb +38 -13
- data/app/models/rails_pulse/dashboard/needs_attention.rb +31 -98
- data/app/models/rails_pulse/dashboard/storage_pressure.rb +39 -63
- data/app/models/rails_pulse/dashboard/storage_status.rb +130 -39
- data/app/models/rails_pulse/deployment.rb +2 -7
- data/app/models/rails_pulse/event.rb +41 -0
- data/app/models/rails_pulse/exception_occurrence.rb +1 -1
- data/app/models/rails_pulse/exceptions/cards/exception_rate.rb +3 -6
- data/app/models/rails_pulse/exceptions/cards/open_groups.rb +1 -4
- data/app/models/rails_pulse/exceptions/cards/total_occurrences.rb +3 -6
- data/app/models/rails_pulse/exceptions/charts/occurrence_volume.rb +4 -7
- data/app/models/rails_pulse/job_run.rb +1 -1
- data/app/models/rails_pulse/jobs/cards/failure_rate.rb +8 -14
- data/app/models/rails_pulse/jobs/cards/p95_duration.rb +8 -14
- data/app/models/rails_pulse/jobs/cards/total_runs.rb +5 -11
- data/app/models/rails_pulse/jobs/charts/duration.rb +1 -2
- data/app/models/rails_pulse/jobs/charts/execution_volume.rb +1 -1
- data/app/models/rails_pulse/jobs/charts/failure_rate.rb +1 -2
- data/app/models/rails_pulse/like_pattern.rb +35 -0
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +6 -12
- data/app/models/rails_pulse/queries/cards/database_load.rb +0 -9
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +5 -11
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +6 -12
- data/app/models/rails_pulse/queries/charts/database_load.rb +5 -8
- data/app/models/rails_pulse/queries/charts/execution_volume.rb +1 -1
- data/app/models/rails_pulse/request.rb +1 -1
- data/app/models/rails_pulse/routes/cards/error_rates.rb +5 -15
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +5 -17
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +5 -17
- data/app/models/rails_pulse/routes/charts/error_rate.rb +1 -1
- data/app/models/rails_pulse/routes/charts/request_volume.rb +1 -1
- data/app/models/rails_pulse/summary.rb +5 -0
- data/app/models/rails_pulse/tables/base.rb +5 -6
- data/app/models/rails_pulse/time_range.rb +309 -0
- data/app/models/rails_pulse/time_window.rb +15 -0
- data/app/models/rails_pulse/writer_heartbeat.rb +90 -0
- data/app/serializers/rails_pulse/api/v1/deployment_serializer.rb +23 -0
- data/app/serializers/rails_pulse/api/v1/exception_group_serializer.rb +23 -0
- data/app/serializers/rails_pulse/api/v1/exception_occurrence_serializer.rb +22 -0
- data/app/serializers/rails_pulse/api/v1/job_run_serializer.rb +28 -0
- data/app/serializers/rails_pulse/api/v1/job_serializer.rb +26 -0
- data/app/serializers/rails_pulse/api/v1/query_serializer.rb +25 -0
- data/app/serializers/rails_pulse/api/v1/request_serializer.rb +22 -0
- data/app/serializers/rails_pulse/api/v1/route_serializer.rb +19 -0
- data/app/services/rails_pulse/cloud/batch.rb +83 -0
- data/app/services/rails_pulse/cloud/client.rb +68 -0
- data/app/services/rails_pulse/cloud/combined_metrics.rb +49 -0
- data/app/services/rails_pulse/cloud/health_item.rb +77 -0
- data/app/services/rails_pulse/cloud/path_prefix.rb +50 -0
- data/app/services/rails_pulse/cloud/preview.rb +83 -0
- data/app/services/rails_pulse/cloud/query_shape.rb +195 -0
- data/app/services/rails_pulse/cloud/record_items.rb +60 -0
- data/app/services/rails_pulse/cloud/route_pattern.rb +111 -0
- data/app/services/rails_pulse/cloud/summary_items.rb +188 -0
- data/app/services/rails_pulse/cloud/sync.rb +275 -0
- data/app/services/rails_pulse/config_recommendations.rb +132 -0
- data/app/services/rails_pulse/deployment_comparison.rb +177 -0
- data/app/services/rails_pulse/exception_capture_service.rb +1 -1
- data/app/services/rails_pulse/operations/change_point.rb +1 -1
- data/app/services/rails_pulse/operations/comparison.rb +1 -1
- data/app/services/rails_pulse/period_insights.rb +131 -0
- data/app/services/rails_pulse/sql_query_normalizer.rb +222 -122
- data/app/services/rails_pulse/summary_service/from_child_periods.rb +161 -0
- data/app/services/rails_pulse/summary_service/from_raw_rows.rb +115 -0
- data/app/services/rails_pulse/summary_service/metrics.rb +38 -0
- data/app/services/rails_pulse/summary_service.rb +121 -133
- data/app/services/rails_pulse/tag_filter_service.rb +5 -5
- data/app/views/layouts/rails_pulse/_time_range_selector.html.erb +4 -1
- data/app/views/rails_pulse/components/_operation_details_popover.html.erb +1 -1
- data/app/views/rails_pulse/components/_table_head.html.erb +2 -2
- data/app/views/rails_pulse/dashboard/_chart_panel.html.erb +6 -1
- data/app/views/rails_pulse/dashboard/_deployments_panel.html.erb +2 -2
- data/app/views/rails_pulse/dashboard/_health_badge.html.erb +29 -20
- data/app/views/rails_pulse/dashboard/_storage_panel.html.erb +13 -3
- data/app/views/rails_pulse/dashboard/index.html.erb +16 -2
- data/app/views/rails_pulse/jobs/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/jobs/index.html.erb +1 -1
- data/app/views/rails_pulse/queries/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/queries/index.html.erb +1 -1
- data/app/views/rails_pulse/queries/show.html.erb +1 -1
- data/app/views/rails_pulse/requests/index.html.erb +1 -1
- data/app/views/rails_pulse/routes/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/routes/index.html.erb +1 -1
- data/app/views/rails_pulse/routes/show.html.erb +1 -1
- data/app/views/rails_pulse/shared/_aggregation_zone_badge.html.erb +4 -0
- data/app/views/rails_pulse/storage/show.html.erb +39 -2
- data/config/routes.rb +23 -0
- data/db/rails_pulse_cloud_schema.rb +58 -0
- data/db/rails_pulse_migrate/20260506000001_create_rails_pulse_exceptions.rb +1 -1
- data/db/rails_pulse_migrate/20260921000001_create_rails_pulse_events.rb +21 -0
- data/db/rails_pulse_schema.rb +20 -2
- data/exe/rails-pulse +7 -0
- data/lib/generators/rails_pulse/base_methods.rb +19 -0
- data/lib/generators/rails_pulse/install_cloud_generator.rb +84 -0
- data/lib/generators/rails_pulse/templates/db/rails_pulse_cloud_schema.rb +58 -0
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +20 -2
- data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_cloud_tables.rb +30 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +71 -19
- data/lib/generators/rails_pulse/upgrade_generator.rb +9 -16
- data/lib/rails_pulse/cleanup_service.rb +37 -20
- data/lib/rails_pulse/cli/agent_files/agents.md +89 -0
- data/lib/rails_pulse/cli/agent_files/claude_skill.md +182 -0
- data/lib/rails_pulse/cli/base_command.rb +37 -0
- data/lib/rails_pulse/cli/client.rb +64 -0
- data/lib/rails_pulse/cli/config.rb +87 -0
- data/lib/rails_pulse/cli/configure.rb +75 -0
- data/lib/rails_pulse/cli/coverage.rb +91 -0
- data/lib/rails_pulse/cli/deployments.rb +54 -0
- data/lib/rails_pulse/cli/exceptions.rb +116 -0
- data/lib/rails_pulse/cli/formatter.rb +31 -0
- data/lib/rails_pulse/cli/insights.rb +70 -0
- data/lib/rails_pulse/cli/install.rb +145 -0
- data/lib/rails_pulse/cli/job_runs.rb +53 -0
- data/lib/rails_pulse/cli/jobs.rb +71 -0
- data/lib/rails_pulse/cli/main.rb +76 -0
- data/lib/rails_pulse/cli/mcp.rb +47 -0
- data/lib/rails_pulse/cli/queries.rb +67 -0
- data/lib/rails_pulse/cli/requests.rb +57 -0
- data/lib/rails_pulse/cli/routes.rb +73 -0
- data/lib/rails_pulse/configuration.rb +100 -2
- data/lib/rails_pulse/current.rb +11 -0
- data/lib/rails_pulse/engine.rb +12 -3
- data/lib/rails_pulse/installers/config_updater.rb +9 -2
- data/lib/rails_pulse/job_run_collector.rb +15 -15
- data/lib/rails_pulse/mcp/server.rb +78 -0
- data/lib/rails_pulse/mcp/tools/coverage.rb +96 -0
- data/lib/rails_pulse/mcp/tools/deployments.rb +117 -0
- data/lib/rails_pulse/mcp/tools/endpoint.rb +167 -0
- data/lib/rails_pulse/mcp/tools/errors.rb +104 -0
- data/lib/rails_pulse/mcp/tools/exception_detail.rb +118 -0
- data/lib/rails_pulse/mcp/tools/exceptions.rb +128 -0
- data/lib/rails_pulse/mcp/tools/helpers.rb +162 -0
- data/lib/rails_pulse/mcp/tools/insights.rb +99 -0
- data/lib/rails_pulse/mcp/tools/jobs.rb +155 -0
- data/lib/rails_pulse/mcp/tools/queries.rb +142 -0
- data/lib/rails_pulse/mcp/tools/routes.rb +108 -0
- data/lib/rails_pulse/mcp/tools/slow_requests.rb +118 -0
- data/lib/rails_pulse/middleware/request_collector.rb +9 -9
- data/lib/rails_pulse/scoped_inflector.rb +35 -0
- data/lib/rails_pulse/statistics.rb +27 -0
- data/lib/rails_pulse/subscribers/exception_subscriber.rb +2 -2
- data/lib/rails_pulse/subscribers/operation_subscriber.rb +15 -29
- data/lib/rails_pulse/tasks/status_reporter.rb +201 -0
- data/lib/rails_pulse/tracker.rb +76 -14
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/rails_pulse.rb +0 -4
- data/lib/tasks/rails_pulse.rake +6 -0
- data/lib/tasks/rails_pulse_tasks.rake +7 -0
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +13 -13
- metadata +123 -24
- data/app/controllers/concerns/page_timings.rb +0 -10
- data/app/controllers/concerns/response_range_concern.rb +0 -45
- data/app/controllers/concerns/time_range_concern.rb +0 -152
- data/app/controllers/concerns/zoom_range_concern.rb +0 -86
|
@@ -2,14 +2,8 @@ module RailsPulse
|
|
|
2
2
|
module Queries
|
|
3
3
|
module Cards
|
|
4
4
|
class AverageQueryTimes < RailsPulse::Cards::Base
|
|
5
|
-
def initialize(query: nil,
|
|
6
|
-
|
|
7
|
-
@disabled_tags = disabled_tags
|
|
8
|
-
@show_non_tagged = show_non_tagged
|
|
9
|
-
@period = period
|
|
10
|
-
@period_type = period_type
|
|
11
|
-
@start_time = start_time
|
|
12
|
-
@end_time = end_time
|
|
5
|
+
def initialize(query: nil, **kwargs)
|
|
6
|
+
super(subject: query, **kwargs)
|
|
13
7
|
end
|
|
14
8
|
|
|
15
9
|
def to_metric_card
|
|
@@ -19,10 +13,10 @@ module RailsPulse
|
|
|
19
13
|
metrics = base_query.select(
|
|
20
14
|
"SUM(avg_duration * count) AS total_weighted_duration",
|
|
21
15
|
"SUM(count) AS total_requests",
|
|
22
|
-
"SUM(CASE WHEN period_start >=
|
|
23
|
-
"SUM(CASE WHEN period_start >=
|
|
24
|
-
"SUM(CASE WHEN period_start >=
|
|
25
|
-
"SUM(CASE WHEN period_start >=
|
|
16
|
+
window_sql("SUM(CASE WHEN period_start >= :current_start THEN avg_duration * count ELSE 0 END) AS current_weighted_duration"),
|
|
17
|
+
window_sql("SUM(CASE WHEN period_start >= :current_start THEN count ELSE 0 END) AS current_requests"),
|
|
18
|
+
window_sql("SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN avg_duration * count ELSE 0 END) AS previous_weighted_duration"),
|
|
19
|
+
window_sql("SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN count ELSE 0 END) AS previous_requests")
|
|
26
20
|
).take
|
|
27
21
|
|
|
28
22
|
# Calculate metrics from single query result
|
|
@@ -2,15 +2,6 @@ module RailsPulse
|
|
|
2
2
|
module Queries
|
|
3
3
|
module Cards
|
|
4
4
|
class DatabaseLoad < RailsPulse::Cards::Base
|
|
5
|
-
def initialize(disabled_tags: [], show_non_tagged: true, period: 7, period_type: "day", start_time: nil, end_time: nil)
|
|
6
|
-
@disabled_tags = disabled_tags
|
|
7
|
-
@show_non_tagged = show_non_tagged
|
|
8
|
-
@period = period
|
|
9
|
-
@period_type = period_type
|
|
10
|
-
@start_time = start_time
|
|
11
|
-
@end_time = end_time
|
|
12
|
-
end
|
|
13
|
-
|
|
14
5
|
def to_metric_card
|
|
15
6
|
# Use base class time period helpers
|
|
16
7
|
last_n_units = current_window_start
|
|
@@ -2,14 +2,8 @@ module RailsPulse
|
|
|
2
2
|
module Queries
|
|
3
3
|
module Cards
|
|
4
4
|
class ExecutionRate < RailsPulse::Cards::Base
|
|
5
|
-
def initialize(query: nil,
|
|
6
|
-
|
|
7
|
-
@disabled_tags = disabled_tags
|
|
8
|
-
@show_non_tagged = show_non_tagged
|
|
9
|
-
@period = period
|
|
10
|
-
@period_type = period_type
|
|
11
|
-
@start_time = start_time
|
|
12
|
-
@end_time = end_time
|
|
5
|
+
def initialize(query: nil, **kwargs)
|
|
6
|
+
super(subject: query, **kwargs)
|
|
13
7
|
end
|
|
14
8
|
|
|
15
9
|
def to_metric_card
|
|
@@ -18,8 +12,8 @@ module RailsPulse
|
|
|
18
12
|
|
|
19
13
|
metrics = base_query.select(
|
|
20
14
|
"SUM(count) AS total_count",
|
|
21
|
-
"SUM(CASE WHEN period_start >=
|
|
22
|
-
"SUM(CASE WHEN period_start >=
|
|
15
|
+
window_sql("SUM(CASE WHEN period_start >= :current_start THEN count ELSE 0 END) AS current_count"),
|
|
16
|
+
window_sql("SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN count ELSE 0 END) AS previous_count")
|
|
23
17
|
).take
|
|
24
18
|
|
|
25
19
|
# Calculate metrics from single query result
|
|
@@ -38,7 +32,7 @@ module RailsPulse
|
|
|
38
32
|
period_type: @period_type,
|
|
39
33
|
period_start: current_window_start..now
|
|
40
34
|
)
|
|
41
|
-
sparkline_query = sparkline_query.where(summarizable_id: @
|
|
35
|
+
sparkline_query = sparkline_query.where(summarizable_id: @subject.id) if @subject
|
|
42
36
|
|
|
43
37
|
grouped_data = bucket_by_period(sparkline_query) { |relation| relation.sum(:count) }
|
|
44
38
|
|
|
@@ -2,14 +2,8 @@ module RailsPulse
|
|
|
2
2
|
module Queries
|
|
3
3
|
module Cards
|
|
4
4
|
class PercentileQueryTimes < RailsPulse::Cards::Base
|
|
5
|
-
def initialize(query: nil,
|
|
6
|
-
|
|
7
|
-
@disabled_tags = disabled_tags
|
|
8
|
-
@show_non_tagged = show_non_tagged
|
|
9
|
-
@period = period
|
|
10
|
-
@period_type = period_type
|
|
11
|
-
@start_time = start_time
|
|
12
|
-
@end_time = end_time
|
|
5
|
+
def initialize(query: nil, **kwargs)
|
|
6
|
+
super(subject: query, **kwargs)
|
|
13
7
|
end
|
|
14
8
|
|
|
15
9
|
def to_metric_card
|
|
@@ -19,8 +13,8 @@ module RailsPulse
|
|
|
19
13
|
metrics = base_query.select(
|
|
20
14
|
"SUM(p95_duration * count) / NULLIF(SUM(count), 0) AS overall_p95",
|
|
21
15
|
"SUM(count) AS total_count",
|
|
22
|
-
"SUM(CASE WHEN period_start >=
|
|
23
|
-
"SUM(CASE WHEN period_start >=
|
|
16
|
+
window_sql("SUM(CASE WHEN period_start >= :current_start THEN p95_duration * count ELSE 0 END) / NULLIF(SUM(CASE WHEN period_start >= :current_start THEN count ELSE 0 END), 0) AS current_p95"),
|
|
17
|
+
window_sql("SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN p95_duration * count ELSE 0 END) / NULLIF(SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN count ELSE 0 END), 0) AS previous_p95")
|
|
24
18
|
).take
|
|
25
19
|
|
|
26
20
|
# Calculate metrics from single query result
|
|
@@ -28,7 +22,7 @@ module RailsPulse
|
|
|
28
22
|
current_period_p95 = metrics.current_p95 || 0
|
|
29
23
|
previous_period_p95 = metrics.previous_p95 || 0
|
|
30
24
|
|
|
31
|
-
baseline = baseline_trend_for(@
|
|
25
|
+
baseline = baseline_trend_for(@subject, metric: :p95)
|
|
32
26
|
|
|
33
27
|
if baseline
|
|
34
28
|
trend_icon, trend_amount, trend_caption = baseline
|
|
@@ -44,7 +38,7 @@ module RailsPulse
|
|
|
44
38
|
period_type: @period_type,
|
|
45
39
|
period_start: current_window_start..now
|
|
46
40
|
)
|
|
47
|
-
sparkline_query = sparkline_query.where(summarizable_id: @
|
|
41
|
+
sparkline_query = sparkline_query.where(summarizable_id: @subject.id) if @subject
|
|
48
42
|
|
|
49
43
|
weighted_sums = bucket_by_period(sparkline_query) { |relation| relation.sum("p95_duration * count") }
|
|
50
44
|
period_counts = bucket_by_period(sparkline_query) { |relation| relation.sum(:count) }
|
|
@@ -2,24 +2,21 @@ module RailsPulse
|
|
|
2
2
|
module Queries
|
|
3
3
|
module Charts
|
|
4
4
|
class DatabaseLoad
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
7
|
-
@end_time = end_time
|
|
5
|
+
def initialize(window:, period_type: :day, disabled_tags: [], show_non_tagged: true)
|
|
6
|
+
@window = window
|
|
8
7
|
@period_type = period_type
|
|
9
8
|
@disabled_tags = disabled_tags
|
|
10
9
|
@show_non_tagged = show_non_tagged
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def to_chart_data
|
|
14
|
-
step = @period_type.to_s == "hour" ? 3600 : 86400
|
|
15
|
-
|
|
16
13
|
# Get query summaries (DB time)
|
|
17
14
|
query_summaries = RailsPulse::Summary
|
|
18
15
|
.with_tag_filters(@disabled_tags, @show_non_tagged)
|
|
19
16
|
.where(
|
|
20
17
|
summarizable_type: "RailsPulse::Query",
|
|
21
18
|
period_type: @period_type,
|
|
22
|
-
period_start:
|
|
19
|
+
period_start: @window.start_time..@window.end_time
|
|
23
20
|
)
|
|
24
21
|
|
|
25
22
|
# Get route summaries (total request time)
|
|
@@ -28,7 +25,7 @@ module RailsPulse
|
|
|
28
25
|
.where(
|
|
29
26
|
summarizable_type: "RailsPulse::Route",
|
|
30
27
|
period_type: @period_type,
|
|
31
|
-
period_start:
|
|
28
|
+
period_start: @window.start_time..@window.end_time
|
|
32
29
|
)
|
|
33
30
|
|
|
34
31
|
return nil if query_summaries.empty? || route_summaries.empty?
|
|
@@ -52,7 +49,7 @@ module RailsPulse
|
|
|
52
49
|
# <25% = green (healthy), 25-40% = yellow (watch), >40% = red (bottleneck)
|
|
53
50
|
bar_data = []
|
|
54
51
|
|
|
55
|
-
(@
|
|
52
|
+
@window.bucket_timestamps(@period_type).each do |timestamp|
|
|
56
53
|
query_time = query_time_by_period[timestamp] || 0
|
|
57
54
|
request_time = request_time_by_period[timestamp] || 0
|
|
58
55
|
percentage = request_time > 0 ? (query_time.to_f / request_time * 100).round(1) : 0
|
|
@@ -18,7 +18,7 @@ module RailsPulse
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# Pad missing data with zeros using base class helper
|
|
21
|
-
execution_data = pad_data_with_zeros(raw_data
|
|
21
|
+
execution_data = pad_data_with_zeros(raw_data)
|
|
22
22
|
|
|
23
23
|
# Build series data as [timestamp_ms, value] pairs for ECharts time axis
|
|
24
24
|
series = [ {
|
|
@@ -2,14 +2,8 @@ module RailsPulse
|
|
|
2
2
|
module Routes
|
|
3
3
|
module Cards
|
|
4
4
|
class ErrorRates < RailsPulse::Cards::Base
|
|
5
|
-
def initialize(route: nil,
|
|
6
|
-
|
|
7
|
-
@disabled_tags = disabled_tags
|
|
8
|
-
@show_non_tagged = show_non_tagged
|
|
9
|
-
@period = period
|
|
10
|
-
@period_type = period_type
|
|
11
|
-
@start_time = start_time
|
|
12
|
-
@end_time = end_time
|
|
5
|
+
def initialize(route: nil, **kwargs)
|
|
6
|
+
super(subject: route, **kwargs)
|
|
13
7
|
end
|
|
14
8
|
|
|
15
9
|
def to_metric_card
|
|
@@ -20,8 +14,8 @@ module RailsPulse
|
|
|
20
14
|
"SUM(error_count) AS total_errors",
|
|
21
15
|
"SUM(status_4xx) AS total_4xx",
|
|
22
16
|
"SUM(count) AS total_requests",
|
|
23
|
-
"SUM(CASE WHEN period_start >=
|
|
24
|
-
"SUM(CASE WHEN period_start >=
|
|
17
|
+
window_sql("SUM(CASE WHEN period_start >= :current_start THEN error_count + status_4xx ELSE 0 END) AS current_total_errors"),
|
|
18
|
+
window_sql("SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN error_count + status_4xx ELSE 0 END) AS previous_total_errors")
|
|
25
19
|
).take
|
|
26
20
|
|
|
27
21
|
total_errors = metrics.total_errors || 0
|
|
@@ -35,7 +29,7 @@ module RailsPulse
|
|
|
35
29
|
server_rate = has_data ? (total_errors.to_f / total_requests * 100).round(2) : 0
|
|
36
30
|
client_rate = has_data ? (total_4xx.to_f / total_requests * 100).round(2) : 0
|
|
37
31
|
|
|
38
|
-
baseline = baseline_trend_for(@
|
|
32
|
+
baseline = baseline_trend_for(@subject, metric: :error_rate)
|
|
39
33
|
|
|
40
34
|
if baseline
|
|
41
35
|
trend_icon, trend_amount, trend_caption = baseline
|
|
@@ -72,10 +66,6 @@ module RailsPulse
|
|
|
72
66
|
|
|
73
67
|
private
|
|
74
68
|
|
|
75
|
-
def subject_id
|
|
76
|
-
@route&.id
|
|
77
|
-
end
|
|
78
|
-
|
|
79
69
|
def sparkline_from_error_rates(errors_by_period, client_errors_by_period, counts_by_period)
|
|
80
70
|
if @period_type == "hour"
|
|
81
71
|
sparkline_hours.each_with_object({}) do |current_time, result|
|
|
@@ -2,14 +2,8 @@ module RailsPulse
|
|
|
2
2
|
module Routes
|
|
3
3
|
module Cards
|
|
4
4
|
class PercentileResponseTimes < RailsPulse::Cards::Base
|
|
5
|
-
def initialize(route: nil,
|
|
6
|
-
|
|
7
|
-
@disabled_tags = disabled_tags
|
|
8
|
-
@show_non_tagged = show_non_tagged
|
|
9
|
-
@period = period
|
|
10
|
-
@period_type = period_type
|
|
11
|
-
@start_time = start_time
|
|
12
|
-
@end_time = end_time
|
|
5
|
+
def initialize(route: nil, **kwargs)
|
|
6
|
+
super(subject: route, **kwargs)
|
|
13
7
|
end
|
|
14
8
|
|
|
15
9
|
def to_metric_card
|
|
@@ -18,8 +12,8 @@ module RailsPulse
|
|
|
18
12
|
|
|
19
13
|
metrics = base_query.select(
|
|
20
14
|
"SUM(p95_duration * count) / NULLIF(SUM(count), 0) AS overall_p95",
|
|
21
|
-
"SUM(CASE WHEN period_start >=
|
|
22
|
-
"SUM(CASE WHEN period_start >=
|
|
15
|
+
window_sql("SUM(CASE WHEN period_start >= :current_start THEN p95_duration * count ELSE 0 END) / NULLIF(SUM(CASE WHEN period_start >= :current_start THEN count ELSE 0 END), 0) AS current_p95"),
|
|
16
|
+
window_sql("SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN p95_duration * count ELSE 0 END) / NULLIF(SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN count ELSE 0 END), 0) AS previous_p95"),
|
|
23
17
|
"SUM(count) AS total_count"
|
|
24
18
|
).take
|
|
25
19
|
|
|
@@ -32,7 +26,7 @@ module RailsPulse
|
|
|
32
26
|
|
|
33
27
|
# Prefer a comparison against this route's own history; fall back to
|
|
34
28
|
# period-over-period on the index, where there is no single subject.
|
|
35
|
-
baseline = baseline_trend_for(@
|
|
29
|
+
baseline = baseline_trend_for(@subject, metric: :p95)
|
|
36
30
|
|
|
37
31
|
if baseline
|
|
38
32
|
trend_icon, trend_amount, trend_caption = baseline
|
|
@@ -69,12 +63,6 @@ module RailsPulse
|
|
|
69
63
|
help_text: "The 95th percentile response time — 95% of requests are faster than this. Weighted by request volume across all routes. A rising P95 indicates increasing slowness affecting your users."
|
|
70
64
|
}
|
|
71
65
|
end
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
def subject_id
|
|
76
|
-
@route&.id
|
|
77
|
-
end
|
|
78
66
|
end
|
|
79
67
|
end
|
|
80
68
|
end
|
|
@@ -2,14 +2,8 @@ module RailsPulse
|
|
|
2
2
|
module Routes
|
|
3
3
|
module Cards
|
|
4
4
|
class RequestCountTotals < RailsPulse::Cards::Base
|
|
5
|
-
def initialize(route: nil,
|
|
6
|
-
|
|
7
|
-
@disabled_tags = disabled_tags
|
|
8
|
-
@show_non_tagged = show_non_tagged
|
|
9
|
-
@period = period
|
|
10
|
-
@period_type = period_type
|
|
11
|
-
@start_time = start_time
|
|
12
|
-
@end_time = end_time
|
|
5
|
+
def initialize(route: nil, **kwargs)
|
|
6
|
+
super(subject: route, **kwargs)
|
|
13
7
|
end
|
|
14
8
|
|
|
15
9
|
def to_metric_card
|
|
@@ -18,8 +12,8 @@ module RailsPulse
|
|
|
18
12
|
|
|
19
13
|
metrics = base_query.select(
|
|
20
14
|
"SUM(count) AS total_count",
|
|
21
|
-
"SUM(CASE WHEN period_start >=
|
|
22
|
-
"SUM(CASE WHEN period_start >=
|
|
15
|
+
window_sql("SUM(CASE WHEN period_start >= :current_start THEN count ELSE 0 END) AS current_count"),
|
|
16
|
+
window_sql("SUM(CASE WHEN period_start >= :range_start AND period_start < :current_start THEN count ELSE 0 END) AS previous_count")
|
|
23
17
|
).take
|
|
24
18
|
|
|
25
19
|
# Calculate metrics from single query result
|
|
@@ -42,7 +36,7 @@ module RailsPulse
|
|
|
42
36
|
period_type: @period_type,
|
|
43
37
|
period_start: current_window_start..now
|
|
44
38
|
)
|
|
45
|
-
sparkline_query = sparkline_query.where(summarizable_id: @
|
|
39
|
+
sparkline_query = sparkline_query.where(summarizable_id: @subject.id) if @subject
|
|
46
40
|
|
|
47
41
|
grouped_data = bucket_by_period(sparkline_query) { |relation| relation.sum(:count) }
|
|
48
42
|
|
|
@@ -83,12 +77,6 @@ module RailsPulse
|
|
|
83
77
|
help_text: "Total HTTP requests served over the last 14 days, expressed as an average rate. Use this to understand traffic patterns and capacity planning needs."
|
|
84
78
|
}
|
|
85
79
|
end
|
|
86
|
-
|
|
87
|
-
private
|
|
88
|
-
|
|
89
|
-
def subject_id
|
|
90
|
-
@route&.id
|
|
91
|
-
end
|
|
92
80
|
end
|
|
93
81
|
end
|
|
94
82
|
end
|
|
@@ -30,7 +30,7 @@ module RailsPulse
|
|
|
30
30
|
# Pad missing data with defaults
|
|
31
31
|
default_value = { error_rate: nil, client_error_rate: nil }
|
|
32
32
|
daily_data = {}
|
|
33
|
-
(@
|
|
33
|
+
@window.bucket_timestamps(@period_type).each do |timestamp|
|
|
34
34
|
daily_data[timestamp] = raw_data[timestamp] || default_value
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -22,7 +22,7 @@ module RailsPulse
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Pad missing data with zeros
|
|
25
|
-
daily_data = pad_data_with_zeros(raw_data
|
|
25
|
+
daily_data = pad_data_with_zeros(raw_data)
|
|
26
26
|
|
|
27
27
|
# Build series data as [timestamp_ms, value] pairs for ECharts time axis
|
|
28
28
|
series = [ {
|
|
@@ -171,7 +171,11 @@ module RailsPulse
|
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
class << self
|
|
174
|
+
# A Date is read as midnight in Time.zone, the aggregation zone, so
|
|
175
|
+
# boundaries are always times: Date#end_of_week is the last day itself,
|
|
176
|
+
# and a period ending there would drop that whole day.
|
|
174
177
|
def calculate_period_end(period_type, start_time)
|
|
178
|
+
start_time = start_time.in_time_zone if start_time.is_a?(Date)
|
|
175
179
|
case period_type
|
|
176
180
|
when "hour" then start_time.end_of_hour
|
|
177
181
|
when "day" then start_time.end_of_day
|
|
@@ -181,6 +185,7 @@ module RailsPulse
|
|
|
181
185
|
end
|
|
182
186
|
|
|
183
187
|
def normalize_period_start(period_type, time)
|
|
188
|
+
time = time.in_time_zone if time.is_a?(Date)
|
|
184
189
|
case period_type
|
|
185
190
|
when "hour" then time.beginning_of_hour
|
|
186
191
|
when "day" then time.beginning_of_day
|
|
@@ -4,10 +4,9 @@ module RailsPulse
|
|
|
4
4
|
WEIGHTED_P95 = "SUM(rails_pulse_summaries.p95_duration * rails_pulse_summaries.count) / NULLIF(SUM(rails_pulse_summaries.count), 0)"
|
|
5
5
|
WEIGHTED_P99 = "SUM(rails_pulse_summaries.p99_duration * rails_pulse_summaries.count) / NULLIF(SUM(rails_pulse_summaries.count), 0)"
|
|
6
6
|
|
|
7
|
-
def initialize(ransack_query:, period_type: nil,
|
|
7
|
+
def initialize(ransack_query:, period_type: nil, params:, disabled_tags: [], show_non_tagged: true)
|
|
8
8
|
@ransack_query = ransack_query
|
|
9
9
|
@period_type = period_type
|
|
10
|
-
@start_time = start_time
|
|
11
10
|
@params = params
|
|
12
11
|
@disabled_tags = disabled_tags
|
|
13
12
|
@show_non_tagged = show_non_tagged
|
|
@@ -41,11 +40,11 @@ module RailsPulse
|
|
|
41
40
|
def apply_tag_filters(query)
|
|
42
41
|
actual_disabled_tags = @disabled_tags.reject { |tag| tag == "non_tagged" }
|
|
43
42
|
|
|
44
|
-
# Explicit ESCAPE: SQLite has no default escape character, so without
|
|
45
|
-
# it an underscore in a tag is never matched literally there.
|
|
46
43
|
actual_disabled_tags.each do |tag|
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
query = query.where.not(
|
|
45
|
+
"#{model_table}.tags LIKE ? #{RailsPulse::LikePattern::CLAUSE}",
|
|
46
|
+
RailsPulse::LikePattern.containing(tag)
|
|
47
|
+
)
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
unless @show_non_tagged
|