rails_pulse 0.1.1 → 0.1.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 +72 -176
- data/Rakefile +77 -2
- data/app/assets/stylesheets/rails_pulse/application.css +0 -12
- data/app/controllers/concerns/chart_table_concern.rb +21 -4
- data/app/controllers/concerns/response_range_concern.rb +6 -3
- data/app/controllers/concerns/time_range_concern.rb +5 -10
- data/app/controllers/concerns/zoom_range_concern.rb +1 -1
- data/app/controllers/rails_pulse/application_controller.rb +8 -4
- data/app/controllers/rails_pulse/dashboard_controller.rb +12 -0
- data/app/controllers/rails_pulse/queries_controller.rb +65 -50
- data/app/controllers/rails_pulse/requests_controller.rb +24 -12
- data/app/controllers/rails_pulse/routes_controller.rb +59 -24
- data/app/helpers/rails_pulse/application_helper.rb +0 -1
- data/app/helpers/rails_pulse/chart_formatters.rb +3 -3
- data/app/helpers/rails_pulse/chart_helper.rb +6 -2
- data/app/helpers/rails_pulse/status_helper.rb +10 -4
- data/app/javascript/rails_pulse/controllers/index_controller.js +117 -33
- data/app/javascript/rails_pulse/controllers/pagination_controller.js +17 -27
- data/app/jobs/rails_pulse/backfill_summaries_job.rb +41 -0
- data/app/jobs/rails_pulse/summary_job.rb +53 -0
- data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +28 -7
- data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +18 -22
- data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +18 -7
- data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +34 -41
- data/app/models/rails_pulse/operation.rb +1 -1
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +47 -23
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +33 -26
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +34 -45
- data/app/models/rails_pulse/queries/charts/average_query_times.rb +23 -97
- data/app/models/rails_pulse/queries/tables/index.rb +74 -0
- data/app/models/rails_pulse/query.rb +1 -0
- data/app/models/rails_pulse/requests/charts/average_response_times.rb +23 -84
- data/app/models/rails_pulse/route.rb +1 -6
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +45 -23
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +38 -45
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +34 -47
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +30 -25
- data/app/models/rails_pulse/routes/charts/average_response_times.rb +23 -100
- data/app/models/rails_pulse/routes/tables/index.rb +57 -40
- data/app/models/rails_pulse/summary.rb +143 -0
- data/app/services/rails_pulse/summary_service.rb +199 -0
- data/app/views/layouts/rails_pulse/application.html.erb +4 -4
- data/app/views/rails_pulse/components/_empty_state.html.erb +11 -0
- data/app/views/rails_pulse/components/_metric_card.html.erb +10 -24
- data/app/views/rails_pulse/dashboard/index.html.erb +54 -36
- data/app/views/rails_pulse/queries/_show_table.html.erb +1 -1
- data/app/views/rails_pulse/queries/_table.html.erb +10 -12
- data/app/views/rails_pulse/queries/index.html.erb +41 -34
- data/app/views/rails_pulse/queries/show.html.erb +38 -31
- data/app/views/rails_pulse/requests/_operations.html.erb +32 -26
- data/app/views/rails_pulse/requests/_table.html.erb +1 -3
- data/app/views/rails_pulse/requests/index.html.erb +42 -34
- data/app/views/rails_pulse/routes/_table.html.erb +13 -13
- data/app/views/rails_pulse/routes/index.html.erb +43 -35
- data/app/views/rails_pulse/routes/show.html.erb +42 -35
- data/config/initializers/rails_pulse.rb +0 -12
- data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +54 -0
- data/db/rails_pulse_schema.rb +121 -0
- data/lib/generators/rails_pulse/install_generator.rb +41 -4
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +60 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +0 -12
- data/lib/rails_pulse/configuration.rb +0 -11
- data/lib/rails_pulse/engine.rb +0 -1
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/tasks/rails_pulse.rake +58 -0
- 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
- data/public/rails-pulse-assets/search.svg +43 -0
- metadata +27 -11
- data/app/controllers/rails_pulse/caches_controller.rb +0 -115
- data/app/helpers/rails_pulse/cached_component_helper.rb +0 -73
- data/app/models/rails_pulse/component_cache_key.rb +0 -33
- data/app/views/rails_pulse/caches/show.html.erb +0 -9
- data/db/migrate/20250227235904_create_routes.rb +0 -12
- data/db/migrate/20250227235915_create_requests.rb +0 -19
- data/db/migrate/20250228000000_create_queries.rb +0 -14
- data/db/migrate/20250228000056_create_operations.rb +0 -24
- data/lib/rails_pulse/migration.rb +0 -29
@@ -7,39 +7,61 @@ module RailsPulse
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_metric_card
|
10
|
-
|
11
|
-
|
12
|
-
else
|
13
|
-
RailsPulse::Request.all
|
14
|
-
end
|
10
|
+
last_7_days = 7.days.ago.beginning_of_day
|
11
|
+
previous_7_days = 14.days.ago.beginning_of_day
|
15
12
|
|
16
|
-
|
13
|
+
# Single query to get all aggregated metrics with conditional sums
|
14
|
+
base_query = RailsPulse::Summary.where(
|
15
|
+
summarizable_type: "RailsPulse::Route",
|
16
|
+
period_type: "day",
|
17
|
+
period_start: 2.weeks.ago.beginning_of_day..Time.current
|
18
|
+
)
|
19
|
+
base_query = base_query.where(summarizable_id: @route.id) if @route
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
metrics = base_query.select(
|
22
|
+
"SUM(avg_duration * count) AS total_weighted_duration",
|
23
|
+
"SUM(count) AS total_requests",
|
24
|
+
"SUM(CASE WHEN period_start >= '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN avg_duration * count ELSE 0 END) AS current_weighted_duration",
|
25
|
+
"SUM(CASE WHEN period_start >= '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN count ELSE 0 END) AS current_requests",
|
26
|
+
"SUM(CASE WHEN period_start >= '#{previous_7_days.strftime('%Y-%m-%d %H:%M:%S')}' AND period_start < '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN avg_duration * count ELSE 0 END) AS previous_weighted_duration",
|
27
|
+
"SUM(CASE WHEN period_start >= '#{previous_7_days.strftime('%Y-%m-%d %H:%M:%S')}' AND period_start < '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN count ELSE 0 END) AS previous_requests"
|
28
|
+
).take
|
20
29
|
|
21
|
-
# Calculate
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
previous_period_avg = requests.where("occurred_at >= ? AND occurred_at < ?", previous_7_days, last_7_days).average(:duration) || 0
|
30
|
+
# Calculate metrics from single query result
|
31
|
+
average_response_time = metrics.total_requests.to_i > 0 ? (metrics.total_weighted_duration / metrics.total_requests).round(0) : 0
|
32
|
+
current_period_avg = metrics.current_requests.to_i > 0 ? (metrics.current_weighted_duration / metrics.current_requests) : 0
|
33
|
+
previous_period_avg = metrics.previous_requests.to_i > 0 ? (metrics.previous_weighted_duration / metrics.previous_requests) : 0
|
26
34
|
|
27
|
-
percentage = previous_period_avg.zero? ?
|
28
|
-
trend_icon = percentage < 0.1 ?
|
35
|
+
percentage = previous_period_avg.zero? ? 0 : ((previous_period_avg - current_period_avg) / previous_period_avg * 100).abs.round(1)
|
36
|
+
trend_icon = percentage < 0.1 ? "move-right" : current_period_avg < previous_period_avg ? "trending-down" : "trending-up"
|
29
37
|
trend_amount = previous_period_avg.zero? ? "0%" : "#{percentage}%"
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
# Separate query for sparkline data - manually calculate weighted averages by week
|
40
|
+
sparkline_data = {}
|
41
|
+
base_query.each do |summary|
|
42
|
+
week_start = summary.period_start.beginning_of_week
|
43
|
+
formatted_date = week_start.strftime("%b %-d")
|
44
|
+
|
45
|
+
if sparkline_data[formatted_date]
|
46
|
+
sparkline_data[formatted_date][:total_weighted] += (summary.avg_duration || 0) * (summary.count || 0)
|
47
|
+
sparkline_data[formatted_date][:total_count] += (summary.count || 0)
|
48
|
+
else
|
49
|
+
sparkline_data[formatted_date] = {
|
50
|
+
total_weighted: (summary.avg_duration || 0) * (summary.count || 0),
|
51
|
+
total_count: (summary.count || 0)
|
39
52
|
}
|
40
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Convert to final format
|
57
|
+
sparkline_data = sparkline_data.transform_values do |data|
|
58
|
+
weighted_avg = data[:total_count] > 0 ? (data[:total_weighted] / data[:total_count]).round(0) : 0
|
59
|
+
{ value: weighted_avg }
|
60
|
+
end
|
41
61
|
|
42
62
|
{
|
63
|
+
id: "average_response_times",
|
64
|
+
context: "routes",
|
43
65
|
title: "Average Response Time",
|
44
66
|
summary: "#{average_response_time} ms",
|
45
67
|
line_chart_data: sparkline_data,
|
@@ -7,60 +7,53 @@ module RailsPulse
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_metric_card
|
10
|
-
|
11
|
-
|
12
|
-
RailsPulse::Route.where(id: @route)
|
13
|
-
else
|
14
|
-
RailsPulse::Route.all
|
15
|
-
end
|
10
|
+
last_7_days = 7.days.ago.beginning_of_day
|
11
|
+
previous_7_days = 14.days.ago.beginning_of_day
|
16
12
|
|
17
|
-
|
13
|
+
# Single query to get all error metrics with conditional aggregation
|
14
|
+
base_query = RailsPulse::Summary.where(
|
15
|
+
summarizable_type: "RailsPulse::Route",
|
16
|
+
period_type: "day",
|
17
|
+
period_start: 2.weeks.ago.beginning_of_day..Time.current
|
18
|
+
)
|
19
|
+
base_query = base_query.where(summarizable_id: @route.id) if @route
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
.
|
23
|
-
|
24
|
-
|
25
|
-
path: route.path,
|
26
|
-
error_rate: error_rate.round(2)
|
27
|
-
}
|
28
|
-
end
|
21
|
+
metrics = base_query.select(
|
22
|
+
"SUM(error_count) AS total_errors",
|
23
|
+
"SUM(count) AS total_requests",
|
24
|
+
"SUM(CASE WHEN period_start >= '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN error_count ELSE 0 END) AS current_errors",
|
25
|
+
"SUM(CASE WHEN period_start >= '#{previous_7_days.strftime('%Y-%m-%d %H:%M:%S')}' AND period_start < '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN error_count ELSE 0 END) AS previous_errors"
|
26
|
+
).take
|
29
27
|
|
30
|
-
# Calculate
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
total_days = min_time && max_time && min_time != max_time ? (max_time - min_time) / 1.day : 1
|
36
|
-
errors_per_day = total_errors / total_days
|
37
|
-
error_rate_summary = "#{errors_per_day.round(2)} / day"
|
28
|
+
# Calculate metrics from single query result
|
29
|
+
total_errors = metrics.total_errors || 0
|
30
|
+
total_requests = metrics.total_requests || 0
|
31
|
+
current_period_errors = metrics.current_errors || 0
|
32
|
+
previous_period_errors = metrics.previous_errors || 0
|
38
33
|
|
39
|
-
#
|
40
|
-
|
41
|
-
.where(is_error: true)
|
42
|
-
.group_by_week(:occurred_at, time_zone: "UTC")
|
43
|
-
.count
|
44
|
-
.each_with_object({}) do |(date, count), hash|
|
45
|
-
formatted_date = date.strftime("%b %-d")
|
46
|
-
hash[formatted_date] = {
|
47
|
-
value: count
|
48
|
-
}
|
49
|
-
end
|
34
|
+
# Calculate overall error rate percentage
|
35
|
+
overall_error_rate = total_requests > 0 ? (total_errors.to_f / total_requests * 100).round(2) : 0
|
50
36
|
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
previous_period_errors = requests.where("occurred_at >= ? AND occurred_at < ? AND is_error = ?", previous_7_days, last_7_days, true).count
|
37
|
+
# Calculate trend
|
38
|
+
percentage = previous_period_errors.zero? ? 0 : ((previous_period_errors - current_period_errors) / previous_period_errors.to_f * 100).abs.round(1)
|
39
|
+
trend_icon = percentage < 0.1 ? "move-right" : current_period_errors < previous_period_errors ? "trending-down" : "trending-up"
|
40
|
+
trend_amount = previous_period_errors.zero? ? "0%" : "#{percentage}%"
|
56
41
|
|
57
|
-
|
58
|
-
|
42
|
+
# Separate query for sparkline data - group by week using Rails
|
43
|
+
sparkline_data = base_query
|
44
|
+
.group_by_week(:period_start, time_zone: "UTC")
|
45
|
+
.sum(:error_count)
|
46
|
+
.each_with_object({}) do |(week_start, total_errors), hash|
|
47
|
+
formatted_date = week_start.strftime("%b %-d")
|
48
|
+
value = total_errors || 0
|
49
|
+
hash[formatted_date] = { value: value }
|
50
|
+
end
|
59
51
|
|
60
52
|
{
|
53
|
+
id: "error_rate_per_route",
|
54
|
+
context: "routes",
|
61
55
|
title: "Error Rate Per Route",
|
62
|
-
|
63
|
-
summary: error_rate_summary,
|
56
|
+
summary: "#{overall_error_rate}%",
|
64
57
|
line_chart_data: sparkline_data,
|
65
58
|
trend_icon: trend_icon,
|
66
59
|
trend_amount: trend_amount,
|
@@ -7,60 +7,47 @@ module RailsPulse
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_metric_card
|
10
|
-
requests = if @route
|
11
|
-
RailsPulse::Request.where(route: @route)
|
12
|
-
else
|
13
|
-
RailsPulse::Request.all
|
14
|
-
end
|
15
|
-
|
16
|
-
requests = requests.where("occurred_at >= ?", 2.weeks.ago.beginning_of_day)
|
17
|
-
|
18
|
-
# Calculate overall 95th percentile response time
|
19
|
-
count = requests.count
|
20
|
-
percentile_95th = if count > 0
|
21
|
-
requests.select("duration").order("duration").limit(1).offset((count * 0.95).floor).pluck(:duration).first.round(0) || 0
|
22
|
-
else
|
23
|
-
0
|
24
|
-
end
|
25
|
-
|
26
|
-
# Calculate trend by comparing last 7 days vs previous 7 days for 95th percentile
|
27
10
|
last_7_days = 7.days.ago.beginning_of_day
|
28
11
|
previous_7_days = 14.days.ago.beginning_of_day
|
29
12
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
13
|
+
# Single query to get all P95 metrics with conditional aggregation
|
14
|
+
base_query = RailsPulse::Summary.where(
|
15
|
+
summarizable_type: "RailsPulse::Route",
|
16
|
+
period_type: "day",
|
17
|
+
period_start: 2.weeks.ago.beginning_of_day..Time.current
|
18
|
+
)
|
19
|
+
base_query = base_query.where(summarizable_id: @route.id) if @route
|
20
|
+
|
21
|
+
metrics = base_query.select(
|
22
|
+
"AVG(p95_duration) AS overall_p95",
|
23
|
+
"AVG(CASE WHEN period_start >= '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN p95_duration ELSE NULL END) AS current_p95",
|
24
|
+
"AVG(CASE WHEN period_start >= '#{previous_7_days.strftime('%Y-%m-%d %H:%M:%S')}' AND period_start < '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN p95_duration ELSE NULL END) AS previous_p95"
|
25
|
+
).take
|
26
|
+
|
27
|
+
# Calculate metrics from single query result
|
28
|
+
p95_response_time = (metrics.overall_p95 || 0).round(0)
|
29
|
+
current_period_p95 = metrics.current_p95 || 0
|
30
|
+
previous_period_p95 = metrics.previous_p95 || 0
|
31
|
+
|
32
|
+
percentage = previous_period_p95.zero? ? 0 : ((previous_period_p95 - current_period_p95) / previous_period_p95 * 100).abs.round(1)
|
33
|
+
trend_icon = percentage < 0.1 ? "move-right" : current_period_p95 < previous_period_p95 ? "trending-down" : "trending-up"
|
34
|
+
trend_amount = previous_period_p95.zero? ? "0%" : "#{percentage}%"
|
35
|
+
|
36
|
+
# Separate query for sparkline data - group by week using Rails
|
37
|
+
sparkline_data = base_query
|
38
|
+
.group_by_week(:period_start, time_zone: "UTC")
|
39
|
+
.average(:p95_duration)
|
40
|
+
.each_with_object({}) do |(week_start, avg_p95), hash|
|
41
|
+
formatted_date = week_start.strftime("%b %-d")
|
42
|
+
value = (avg_p95 || 0).round(0)
|
43
|
+
hash[formatted_date] = { value: value }
|
59
44
|
end
|
60
45
|
|
61
46
|
{
|
47
|
+
id: "percentile_response_times",
|
48
|
+
context: "routes",
|
62
49
|
title: "95th Percentile Response Time",
|
63
|
-
summary: "#{
|
50
|
+
summary: "#{p95_response_time} ms",
|
64
51
|
line_chart_data: sparkline_data,
|
65
52
|
trend_icon: trend_icon,
|
66
53
|
trend_amount: trend_amount,
|
@@ -7,44 +7,49 @@ module RailsPulse
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_metric_card
|
10
|
-
|
11
|
-
|
12
|
-
else
|
13
|
-
RailsPulse::Request.all
|
14
|
-
end
|
10
|
+
last_7_days = 7.days.ago.beginning_of_day
|
11
|
+
previous_7_days = 14.days.ago.beginning_of_day
|
15
12
|
|
16
|
-
|
13
|
+
# Single query to get all count metrics with conditional aggregation
|
14
|
+
base_query = RailsPulse::Summary.where(
|
15
|
+
summarizable_type: "RailsPulse::Route",
|
16
|
+
period_type: "day",
|
17
|
+
period_start: 2.weeks.ago.beginning_of_day..Time.current
|
18
|
+
)
|
19
|
+
base_query = base_query.where(summarizable_id: @route.id) if @route
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
metrics = base_query.select(
|
22
|
+
"SUM(count) AS total_count",
|
23
|
+
"SUM(CASE WHEN period_start >= '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN count ELSE 0 END) AS current_count",
|
24
|
+
"SUM(CASE WHEN period_start >= '#{previous_7_days.strftime('%Y-%m-%d %H:%M:%S')}' AND period_start < '#{last_7_days.strftime('%Y-%m-%d %H:%M:%S')}' THEN count ELSE 0 END) AS previous_count"
|
25
|
+
).take
|
20
26
|
|
21
|
-
# Calculate
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
previous_period_count = requests.where("occurred_at >= ? AND occurred_at < ?", previous_7_days, last_7_days).count
|
27
|
+
# Calculate metrics from single query result
|
28
|
+
total_request_count = metrics.total_count || 0
|
29
|
+
current_period_count = metrics.current_count || 0
|
30
|
+
previous_period_count = metrics.previous_count || 0
|
26
31
|
|
27
32
|
percentage = previous_period_count.zero? ? 0 : ((previous_period_count - current_period_count) / previous_period_count.to_f * 100).abs.round(1)
|
28
33
|
trend_icon = percentage < 0.1 ? "move-right" : current_period_count < previous_period_count ? "trending-down" : "trending-up"
|
29
34
|
trend_amount = previous_period_count.zero? ? "0%" : "#{percentage}%"
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
.
|
34
|
-
.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
}
|
36
|
+
# Separate query for sparkline data - group by week using Rails
|
37
|
+
sparkline_data = base_query
|
38
|
+
.group_by_week(:period_start, time_zone: "UTC")
|
39
|
+
.sum(:count)
|
40
|
+
.each_with_object({}) do |(week_start, total_count), hash|
|
41
|
+
formatted_date = week_start.strftime("%b %-d")
|
42
|
+
value = total_count || 0
|
43
|
+
hash[formatted_date] = { value: value }
|
39
44
|
end
|
40
45
|
|
41
|
-
# Calculate average requests per minute
|
42
|
-
|
43
|
-
max_time = requests.maximum(:occurred_at)
|
44
|
-
total_minutes = min_time && max_time && min_time != max_time ? (max_time - min_time) / 60.0 : 1
|
46
|
+
# Calculate average requests per minute over 2-week period
|
47
|
+
total_minutes = 2.weeks / 1.minute
|
45
48
|
average_requests_per_minute = total_request_count / total_minutes
|
46
49
|
|
47
50
|
{
|
51
|
+
id: "request_count_totals",
|
52
|
+
context: "routes",
|
48
53
|
title: "Request Count Total",
|
49
54
|
summary: "#{average_requests_per_minute.round(2)} / min",
|
50
55
|
line_chart_data: sparkline_data,
|
@@ -2,112 +2,35 @@ module RailsPulse
|
|
2
2
|
module Routes
|
3
3
|
module Charts
|
4
4
|
class AverageResponseTimes
|
5
|
-
def initialize(ransack_query:,
|
5
|
+
def initialize(ransack_query:, period_type: nil, route: nil, start_time: nil, end_time: nil, start_duration: nil)
|
6
6
|
@ransack_query = ransack_query
|
7
|
-
@
|
7
|
+
@period_type = period_type
|
8
8
|
@route = route
|
9
|
+
@start_time = start_time
|
10
|
+
@end_time = end_time
|
11
|
+
@start_duration = start_duration
|
9
12
|
end
|
10
13
|
|
11
14
|
def to_rails_chart
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
.average("rails_pulse_requests.duration")
|
30
|
-
end
|
31
|
-
|
32
|
-
# Create full time range and fill in missing periods
|
33
|
-
fill_missing_periods(actual_data)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def fill_missing_periods(actual_data)
|
39
|
-
# Extract actual time range from ransack query conditions
|
40
|
-
start_time, end_time = extract_time_range_from_ransack
|
41
|
-
|
42
|
-
# Create time range based on grouping type
|
43
|
-
case @group_by
|
44
|
-
when :group_by_hour
|
45
|
-
time_range = generate_hour_range(start_time, end_time)
|
46
|
-
else # :group_by_day
|
47
|
-
time_range = generate_day_range(start_time, end_time)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Fill in all periods with zero values for missing periods
|
51
|
-
time_range.each_with_object({}) do |period, result|
|
52
|
-
occurred_at = period.is_a?(String) ? Time.parse(period) : period
|
53
|
-
occurred_at = (occurred_at.is_a?(Time) || occurred_at.is_a?(Date)) ? occurred_at : Time.current
|
54
|
-
|
55
|
-
normalized_occurred_at =
|
56
|
-
case @group_by
|
57
|
-
when :group_by_hour
|
58
|
-
occurred_at&.beginning_of_hour || occurred_at
|
59
|
-
when :group_by_day
|
60
|
-
occurred_at&.beginning_of_day || occurred_at
|
61
|
-
else
|
62
|
-
occurred_at
|
63
|
-
end
|
64
|
-
|
65
|
-
# Use actual data if available, otherwise default to 0
|
66
|
-
average_duration = actual_data[period] || 0
|
67
|
-
result[normalized_occurred_at.to_i] = {
|
68
|
-
value: average_duration.to_f
|
69
|
-
}
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def generate_day_range(start_time, end_time)
|
74
|
-
(start_time.to_date..end_time.to_date).map(&:beginning_of_day)
|
75
|
-
end
|
76
|
-
|
77
|
-
def generate_hour_range(start_time, end_time)
|
78
|
-
current = start_time
|
79
|
-
hours = []
|
80
|
-
while current <= end_time
|
81
|
-
hours << current
|
82
|
-
current += 1.hour
|
83
|
-
end
|
84
|
-
hours
|
85
|
-
end
|
86
|
-
|
87
|
-
def extract_time_range_from_ransack
|
88
|
-
# Extract time range from ransack conditions
|
89
|
-
conditions = @ransack_query.conditions
|
90
|
-
|
91
|
-
if @route
|
92
|
-
# For specific route queries, look for occurred_at conditions
|
93
|
-
start_condition = conditions.find { |c| c.a.first == "occurred_at" && c.p == "gteq" }
|
94
|
-
end_condition = conditions.find { |c| c.a.first == "occurred_at" && c.p == "lt" }
|
95
|
-
else
|
96
|
-
# For general route queries, look for requests_occurred_at conditions
|
97
|
-
start_condition = conditions.find { |c| c.a.first == "requests_occurred_at" && c.p == "gteq" }
|
98
|
-
end_condition = conditions.find { |c| c.a.first == "requests_occurred_at" && c.p == "lt" }
|
99
|
-
end
|
100
|
-
|
101
|
-
start_time = start_condition&.v || 2.weeks.ago
|
102
|
-
end_time = end_condition&.v || Time.current
|
103
|
-
|
104
|
-
# Normalize time boundaries based on grouping
|
105
|
-
case @group_by
|
106
|
-
when :group_by_hour
|
107
|
-
[ start_time.beginning_of_hour, end_time.beginning_of_hour ]
|
108
|
-
else
|
109
|
-
[ start_time.beginning_of_day, end_time.beginning_of_day ]
|
15
|
+
summaries = @ransack_query.result(distinct: false).where(
|
16
|
+
summarizable_type: "RailsPulse::Route",
|
17
|
+
period_type: @period_type
|
18
|
+
)
|
19
|
+
|
20
|
+
summaries = summaries.where(summarizable_id: @route.id) if @route
|
21
|
+
summaries = summaries
|
22
|
+
.group(:period_start)
|
23
|
+
.having("AVG(avg_duration) > ?", @start_duration || 0)
|
24
|
+
.average(:avg_duration)
|
25
|
+
.transform_keys(&:to_i)
|
26
|
+
|
27
|
+
# Pad missing data points with zeros
|
28
|
+
step = @period_type == :hour ? 1.hour : 1.day
|
29
|
+
data = {}
|
30
|
+
(@start_time.to_i..@end_time.to_i).step(step) do |timestamp|
|
31
|
+
data[timestamp.to_i] = summaries[timestamp.to_i].to_f.round(2)
|
110
32
|
end
|
33
|
+
data
|
111
34
|
end
|
112
35
|
end
|
113
36
|
end
|
@@ -2,60 +2,77 @@ module RailsPulse
|
|
2
2
|
module Routes
|
3
3
|
module Tables
|
4
4
|
class Index
|
5
|
-
def initialize(ransack_query:, start_time:, params:)
|
5
|
+
def initialize(ransack_query:, period_type: nil, start_time:, params:)
|
6
6
|
@ransack_query = ransack_query
|
7
|
+
@period_type = period_type
|
7
8
|
@start_time = start_time
|
8
9
|
@params = params
|
9
10
|
end
|
10
11
|
|
11
12
|
def to_table
|
12
|
-
#
|
13
|
-
|
13
|
+
# Check if we have explicit ransack sorts
|
14
|
+
has_sorts = @ransack_query.sorts.any?
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
base_query = @ransack_query.result(distinct: false)
|
17
|
+
.joins("INNER JOIN rails_pulse_routes ON rails_pulse_routes.id = rails_pulse_summaries.summarizable_id")
|
18
|
+
.where(
|
19
|
+
summarizable_type: "RailsPulse::Route",
|
20
|
+
period_type: @period_type
|
21
|
+
)
|
20
22
|
|
21
|
-
|
23
|
+
base_query = base_query.where(summarizable_id: @route.id) if @route
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
.group(
|
25
|
+
# Apply grouping and aggregation
|
26
|
+
grouped_query = base_query
|
27
|
+
.group(
|
28
|
+
"rails_pulse_summaries.summarizable_id",
|
29
|
+
"rails_pulse_summaries.summarizable_type",
|
30
|
+
"rails_pulse_routes.id",
|
31
|
+
"rails_pulse_routes.path",
|
32
|
+
"rails_pulse_routes.method"
|
33
|
+
)
|
26
34
|
.select(
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"
|
31
|
-
"
|
32
|
-
"
|
33
|
-
"
|
34
|
-
"
|
35
|
+
"rails_pulse_summaries.summarizable_id",
|
36
|
+
"rails_pulse_summaries.summarizable_type",
|
37
|
+
"rails_pulse_routes.id as route_id",
|
38
|
+
"rails_pulse_routes.path",
|
39
|
+
"rails_pulse_routes.method as route_method",
|
40
|
+
"AVG(rails_pulse_summaries.avg_duration) as avg_duration",
|
41
|
+
"MAX(rails_pulse_summaries.max_duration) as max_duration",
|
42
|
+
"SUM(rails_pulse_summaries.count) as count",
|
43
|
+
"SUM(rails_pulse_summaries.error_count) as error_count",
|
44
|
+
"SUM(rails_pulse_summaries.success_count) as success_count"
|
35
45
|
)
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
46
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
# Apply sorting based on ransack sorts or use default
|
48
|
+
if has_sorts
|
49
|
+
# Apply custom sorting based on ransack parameters
|
50
|
+
sort = @ransack_query.sorts.first
|
51
|
+
direction = sort.dir == "desc" ? :desc : :asc
|
44
52
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
case sort.name
|
54
|
+
when "avg_duration_sort"
|
55
|
+
grouped_query = grouped_query.order(Arel.sql("AVG(rails_pulse_summaries.avg_duration)").send(direction))
|
56
|
+
when "max_duration_sort"
|
57
|
+
grouped_query = grouped_query.order(Arel.sql("MAX(rails_pulse_summaries.max_duration)").send(direction))
|
58
|
+
when "count_sort"
|
59
|
+
grouped_query = grouped_query.order(Arel.sql("SUM(rails_pulse_summaries.count)").send(direction))
|
60
|
+
when "requests_per_minute"
|
61
|
+
grouped_query = grouped_query.order(Arel.sql("SUM(rails_pulse_summaries.count) / 60.0").send(direction))
|
62
|
+
when "error_rate_percentage"
|
63
|
+
grouped_query = grouped_query.order(Arel.sql("(SUM(rails_pulse_summaries.error_count) * 100.0) / SUM(rails_pulse_summaries.count)").send(direction))
|
64
|
+
when "route_path"
|
65
|
+
grouped_query = grouped_query.order(Arel.sql("rails_pulse_routes.path").send(direction))
|
66
|
+
else
|
67
|
+
# Unknown sort field, fallback to default
|
68
|
+
grouped_query = grouped_query.order(Arel.sql("AVG(rails_pulse_summaries.avg_duration)").desc)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
# Apply default sort when no explicit sort is provided (matches controller default_table_sort)
|
72
|
+
grouped_query = grouped_query.order(Arel.sql("AVG(rails_pulse_summaries.avg_duration)").desc)
|
73
|
+
end
|
50
74
|
|
51
|
-
|
52
|
-
CASE
|
53
|
-
WHEN COALESCE(AVG(rails_pulse_requests.duration), 0) >= #{critical} THEN 3
|
54
|
-
WHEN COALESCE(AVG(rails_pulse_requests.duration), 0) >= #{very_slow} THEN 2
|
55
|
-
WHEN COALESCE(AVG(rails_pulse_requests.duration), 0) >= #{slow} THEN 1
|
56
|
-
ELSE 0
|
57
|
-
END
|
58
|
-
SQL
|
75
|
+
grouped_query
|
59
76
|
end
|
60
77
|
end
|
61
78
|
end
|