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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<%= render 'rails_pulse/components/page_header', taggable: @run %>
|
|
2
|
+
|
|
3
|
+
<div class="row">
|
|
4
|
+
<div class="grid-item">
|
|
5
|
+
<%= render 'rails_pulse/components/panel', title: 'Job Run Details' do %>
|
|
6
|
+
<dl class="descriptive-list mbs-4">
|
|
7
|
+
<dt>Job Class</dt>
|
|
8
|
+
<dd><%= link_to @job.name, job_path(@job) %></dd>
|
|
9
|
+
<dt>Status</dt>
|
|
10
|
+
<dd>
|
|
11
|
+
<%= @run.status.humanize %>
|
|
12
|
+
</dd>
|
|
13
|
+
<dt>Occurred At</dt>
|
|
14
|
+
<dd><%= @run.occurred_at.strftime("%b %d, %Y %l:%M:%S %p") %></dd>
|
|
15
|
+
<% if @run.enqueued_at %>
|
|
16
|
+
<dt>Enqueued At</dt>
|
|
17
|
+
<dd><%= @run.enqueued_at.strftime("%b %d, %Y %l:%M:%S %p") %></dd>
|
|
18
|
+
<% end %>
|
|
19
|
+
<dt>Duration</dt>
|
|
20
|
+
<dd><%= number_to_human(@run.duration || 0, units: { unit: "ms", thousand: "s" }, precision: 2) %></dd>
|
|
21
|
+
<dt>Attempts</dt>
|
|
22
|
+
<dd><%= @run.attempts %></dd>
|
|
23
|
+
<dt>Queue</dt>
|
|
24
|
+
<dd><%= @job.queue_name || 'default' %></dd>
|
|
25
|
+
</dl>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<% if @run.error_class.present? %>
|
|
30
|
+
<div class="grid-item">
|
|
31
|
+
<%= render 'rails_pulse/components/panel', title: 'Error Details' do %>
|
|
32
|
+
<dl class="descriptive-list mbs-4">
|
|
33
|
+
<dt>Error Class</dt>
|
|
34
|
+
<dd><%= @run.error_class %></dd>
|
|
35
|
+
<% if @run.error_message.present? %>
|
|
36
|
+
<dt>Error Message</dt>
|
|
37
|
+
<dd><%= @run.error_message %></dd>
|
|
38
|
+
<% end %>
|
|
39
|
+
</dl>
|
|
40
|
+
<% end %>
|
|
41
|
+
</div>
|
|
42
|
+
<% elsif @run.arguments.present? %>
|
|
43
|
+
<div class="grid-item">
|
|
44
|
+
<%= render 'rails_pulse/components/panel', title: 'Arguments' do %>
|
|
45
|
+
<pre class="mbs-4"><%= JSON.pretty_generate(JSON.parse(@run.arguments)) rescue @run.arguments %></pre>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<%= render 'operations', operations: @operations %>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<% columns = [
|
|
2
|
+
{ field: :occurred_at, label: 'Occurred At' },
|
|
3
|
+
{ field: :status, label: 'Status', class: 'w-28' },
|
|
4
|
+
{ field: :duration, label: 'Duration', class: 'w-28' },
|
|
5
|
+
{ field: :attempts, label: 'Attempts', class: 'w-24' },
|
|
6
|
+
{ field: nil, label: 'Tags', class: 'w-32', sortable: false }
|
|
7
|
+
] %>
|
|
8
|
+
|
|
9
|
+
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
|
10
|
+
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
11
|
+
|
|
12
|
+
<tbody>
|
|
13
|
+
<% @table_data.each do |run| %>
|
|
14
|
+
<tr>
|
|
15
|
+
<td>
|
|
16
|
+
<%= link_to run.occurred_at.strftime("%b %d, %Y %l:%M %p"), job_run_path(@job, run), data: { turbo_frame: '_top' } %>
|
|
17
|
+
</td>
|
|
18
|
+
<td>
|
|
19
|
+
<%= run.status.humanize %>
|
|
20
|
+
</td>
|
|
21
|
+
<td>
|
|
22
|
+
<%= number_to_human(run.duration || 0, units: { unit: "ms", thousand: "s" }, precision: 2) %>
|
|
23
|
+
</td>
|
|
24
|
+
<td>
|
|
25
|
+
<%= run.attempts %>
|
|
26
|
+
</td>
|
|
27
|
+
<td>
|
|
28
|
+
<%= display_tag_badges(run) %>
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
31
|
+
<% end %>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
|
|
35
|
+
<%= render "rails_pulse/components/table_pagination" %>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<% columns = [
|
|
2
|
+
{ field: :name, label: 'Job Class', class: 'w-48', cell_class: 'truncate-cell' },
|
|
3
|
+
{ field: :queue_name, label: 'Queue', class: 'w-32' },
|
|
4
|
+
{ field: :runs_count, label: 'Total Runs', class: 'w-28' },
|
|
5
|
+
{ field: :failures_count, label: 'Failures', class: 'w-24' },
|
|
6
|
+
{ field: :retries_count, label: 'Retries', class: 'w-24' },
|
|
7
|
+
{ field: :avg_duration, label: 'Avg Duration', class: 'w-32' },
|
|
8
|
+
{ field: nil, label: 'Tags', class: 'w-32', sortable: false }
|
|
9
|
+
] %>
|
|
10
|
+
|
|
11
|
+
<table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
|
|
12
|
+
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
13
|
+
|
|
14
|
+
<tbody>
|
|
15
|
+
<% @table_data.each do |job| %>
|
|
16
|
+
<tr>
|
|
17
|
+
<td class="truncate-cell">
|
|
18
|
+
<%= link_to job.name, job_path(job), data: { turbo_frame: '_top' } %>
|
|
19
|
+
</td>
|
|
20
|
+
<td>
|
|
21
|
+
<%= job.queue_name || 'default' %>
|
|
22
|
+
</td>
|
|
23
|
+
<td>
|
|
24
|
+
<%= number_with_delimiter(job.runs_count) %>
|
|
25
|
+
</td>
|
|
26
|
+
<td>
|
|
27
|
+
<%= number_with_delimiter(job.failures_count) %>
|
|
28
|
+
</td>
|
|
29
|
+
<td>
|
|
30
|
+
<%= number_with_delimiter(job.retries_count) %>
|
|
31
|
+
</td>
|
|
32
|
+
<td>
|
|
33
|
+
<%= number_to_human(job.avg_duration || 0, units: { unit: "ms", thousand: "s" }, precision: 2) %>
|
|
34
|
+
</td>
|
|
35
|
+
<td>
|
|
36
|
+
<%= display_tag_badges(job) %>
|
|
37
|
+
</td>
|
|
38
|
+
</tr>
|
|
39
|
+
<% end %>
|
|
40
|
+
</tbody>
|
|
41
|
+
</table>
|
|
42
|
+
|
|
43
|
+
<%= render "rails_pulse/components/table_pagination" %>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<%= render 'rails_pulse/components/page_header', show_active_filters: true, show_global_filters: true %>
|
|
2
|
+
|
|
3
|
+
<% unless turbo_frame_request? %>
|
|
4
|
+
<div class="row">
|
|
5
|
+
<%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @total_runs_metric_card } %>
|
|
6
|
+
<%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @failure_rate_metric_card } %>
|
|
7
|
+
<%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_duration_metric_card } %>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<%= render 'rails_pulse/components/panel', { title: 'Job Classes' } do %>
|
|
12
|
+
<%= search_form_for @ransack_query, url: jobs_path, class: "flex items-center justify-between gap mb-4" do |form| %>
|
|
13
|
+
<div class="flex items-center grow gap">
|
|
14
|
+
<%= form.search_field :name_cont, placeholder: "Filter by job name", autocomplete: "off", class: "input" %>
|
|
15
|
+
<%= form.select :queue_name_eq,
|
|
16
|
+
options_for_select([["All queues", ""]] + @available_queues.map { |q| [q || "default", q] }, params.dig(:q, :queue_name_eq)),
|
|
17
|
+
{},
|
|
18
|
+
{ class: "input" }
|
|
19
|
+
%>
|
|
20
|
+
<%= link_to "Reset", jobs_path, class: "btn btn--borderless show@md" if params.has_key?(:q) %>
|
|
21
|
+
<%= form.submit "Search", class: "btn show@sm" %>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<% if @jobs.any? %>
|
|
26
|
+
<%= turbo_frame_tag :index_table do %>
|
|
27
|
+
<%= render 'rails_pulse/jobs/table' %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% else %>
|
|
30
|
+
<%= render 'rails_pulse/components/empty_state',
|
|
31
|
+
title: 'No jobs found',
|
|
32
|
+
description: 'No background jobs have been executed yet.' %>
|
|
33
|
+
<% end %>
|
|
34
|
+
<% end %>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<%= render 'rails_pulse/components/page_header', taggable: @job, show_active_filters: true %>
|
|
2
|
+
|
|
3
|
+
<% unless turbo_frame_request? %>
|
|
4
|
+
<div class="row">
|
|
5
|
+
<%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @total_runs_metric_card } %>
|
|
6
|
+
<%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @failure_rate_metric_card } %>
|
|
7
|
+
<%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_duration_metric_card } %>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<%= render 'rails_pulse/components/panel', { title: 'Job Runs' } do %>
|
|
12
|
+
<%= search_form_for @ransack_query, url: job_path(@job), class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
|
|
13
|
+
<div class="flex items-center grow gap">
|
|
14
|
+
<%= time_range_selector(form,
|
|
15
|
+
time_range_options: RailsPulse::JobsController::TIME_RANGE_OPTIONS,
|
|
16
|
+
selected_time_range: @selected_time_range,
|
|
17
|
+
mode: :recent_custom
|
|
18
|
+
) %>
|
|
19
|
+
|
|
20
|
+
<%= form.select :status_eq,
|
|
21
|
+
options_for_select(
|
|
22
|
+
[["All Statuses", ""]] + RailsPulse::JobRun::STATUSES.map { |s| [s.humanize, s] },
|
|
23
|
+
params.dig(:q, :status_eq)
|
|
24
|
+
),
|
|
25
|
+
{},
|
|
26
|
+
{ class: "input" }
|
|
27
|
+
%>
|
|
28
|
+
|
|
29
|
+
<%= form.select :duration_gteq,
|
|
30
|
+
duration_threshold_filter_options(:job),
|
|
31
|
+
{ selected: params.dig(:q, :duration_gteq) },
|
|
32
|
+
{ class: "input" }
|
|
33
|
+
%>
|
|
34
|
+
|
|
35
|
+
<%= link_to "Reset", job_path(@job), class: "btn btn--borderless show@md" if params.has_key?(:q) %>
|
|
36
|
+
<%= form.submit "Search", class: "btn show@sm" %>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
|
|
40
|
+
<% if @recent_runs.any? %>
|
|
41
|
+
<%= turbo_frame_tag :index_table do %>
|
|
42
|
+
<%= render 'rails_pulse/jobs/job_runs_table' %>
|
|
43
|
+
<% end %>
|
|
44
|
+
<% else %>
|
|
45
|
+
<%= render 'rails_pulse/components/empty_state',
|
|
46
|
+
title: 'No runs found',
|
|
47
|
+
description: 'This job has not been executed yet or no runs match the selected filters.' %>
|
|
48
|
+
<% end %>
|
|
49
|
+
<% end %>
|
|
@@ -6,35 +6,37 @@
|
|
|
6
6
|
<dd><%= html_escape(action) %></dd>
|
|
7
7
|
<% end %>
|
|
8
8
|
|
|
9
|
-
<%
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
<% if parent %>
|
|
10
|
+
<% total_db_time = parent.operations
|
|
11
|
+
.where(operation_type: ["sql"])
|
|
12
|
+
.sum(:duration) %>
|
|
13
|
+
<% total_view_time = parent.operations
|
|
14
|
+
.where(operation_type: ["template", "partial", "layout", "collection"])
|
|
15
|
+
.sum(:duration) %>
|
|
16
|
+
<% pure_controller_time = operation.duration - total_db_time - total_view_time %>
|
|
16
17
|
|
|
17
|
-
<% if pure_controller_time > 0 %>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<% end %>
|
|
18
|
+
<% if pure_controller_time > 0 %>
|
|
19
|
+
<dt>Pure Logic Time</dt>
|
|
20
|
+
<dd>
|
|
21
|
+
<%= pure_controller_time.round(1) %>ms
|
|
22
|
+
<% if pure_controller_time > 100 %>
|
|
23
|
+
- consider optimization
|
|
24
|
+
<% end %>
|
|
25
|
+
</dd>
|
|
26
|
+
<% end %>
|
|
26
27
|
|
|
27
|
-
<% db_operations_count =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<% if db_operations_count > 0 %>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
<% db_operations_count = parent.operations
|
|
29
|
+
.where(operation_type: ["sql"])
|
|
30
|
+
.count %>
|
|
31
|
+
<% if db_operations_count > 0 %>
|
|
32
|
+
<dt>Database Queries</dt>
|
|
33
|
+
<dd>
|
|
34
|
+
<%= db_operations_count %>
|
|
35
|
+
<% if db_operations_count > 10 %>
|
|
36
|
+
- potential N+1 queries
|
|
37
|
+
<% end %>
|
|
38
|
+
</dd>
|
|
39
|
+
<% end %>
|
|
38
40
|
<% end %>
|
|
39
41
|
|
|
40
42
|
<% if operation.codebase_location.present? %>
|
|
@@ -21,16 +21,18 @@
|
|
|
21
21
|
<dd>Partial template - ensure data is pre-loaded in controller</dd>
|
|
22
22
|
<% end %>
|
|
23
23
|
|
|
24
|
-
<%
|
|
25
|
-
<%
|
|
26
|
-
<%
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
<% if parent %>
|
|
25
|
+
<% view_start = operation.occurred_at %>
|
|
26
|
+
<% view_end = operation.occurred_at + operation.duration %>
|
|
27
|
+
<% concurrent_db_ops = parent.operations
|
|
28
|
+
.where(operation_type: ["sql"])
|
|
29
|
+
.where("occurred_at >= ? AND occurred_at <= ?", view_start, view_end)
|
|
30
|
+
.count %>
|
|
30
31
|
|
|
31
|
-
<% if concurrent_db_ops > 0 %>
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
<% if concurrent_db_ops > 0 %>
|
|
33
|
+
<dt>Queries During Rendering</dt>
|
|
34
|
+
<dd><%= concurrent_db_ops %> database <%= 'query'.pluralize(concurrent_db_ops) %></dd>
|
|
35
|
+
<% end %>
|
|
34
36
|
<% end %>
|
|
35
37
|
|
|
36
38
|
<% if operation.duration > 50 %>
|
|
@@ -32,19 +32,21 @@
|
|
|
32
32
|
<%= @operation.duration.round(2) %> ms
|
|
33
33
|
</dd>
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
<% if @parent %>
|
|
36
|
+
<dt><%= @request ? 'Request' : 'Job Run' %> Impact</dt>
|
|
37
|
+
<dd>
|
|
38
|
+
<% impact = (@operation.duration / @parent.duration * 100).round(1) %>
|
|
39
|
+
<%= impact %>% of total <%= @request ? 'request' : 'job run' %> time
|
|
40
|
+
</dd>
|
|
41
|
+
<% end %>
|
|
40
42
|
|
|
41
43
|
<dt>Occurred At</dt>
|
|
42
44
|
<dd><%= human_readable_occurred_at @operation.occurred_at %></dd>
|
|
43
45
|
|
|
44
46
|
<% begin %>
|
|
45
|
-
<%= render partial: "operation_analysis_#{categorize_operation(@operation.operation_type)}", locals: { operation: @operation } %>
|
|
47
|
+
<%= render partial: "operation_analysis_#{categorize_operation(@operation.operation_type)}", locals: { operation: @operation, parent: @parent } %>
|
|
46
48
|
<% rescue ActionView::MissingTemplate %>
|
|
47
|
-
<%= render partial: "operation_analysis_generic", locals: { operation: @operation } %>
|
|
49
|
+
<%= render partial: "operation_analysis_generic", locals: { operation: @operation, parent: @parent } %>
|
|
48
50
|
<% end %>
|
|
49
51
|
|
|
50
52
|
<% if @performance_context.any? %>
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
<% if @related_operations.any? %>
|
|
60
62
|
<dt>Related Operations</dt>
|
|
61
63
|
<dd>
|
|
62
|
-
<%= pluralize(@related_operations.count, 'similar operation') %> in this request
|
|
64
|
+
<%= pluralize(@related_operations.count, 'similar operation') %> in this <%= @request ? 'request' : 'job run' %>
|
|
63
65
|
<% if @related_operations.count > 2 %>
|
|
64
66
|
<br>Potential N+1 query pattern detected
|
|
65
67
|
<% end %>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
<% columns = [
|
|
2
|
-
{ field: :normalized_sql, label: 'Query', class: 'w-
|
|
2
|
+
{ field: :normalized_sql, label: 'Query', class: 'w-48', cell_class: 'truncate-cell' },
|
|
3
3
|
{ field: :avg_duration_sort, label: 'Average Query Time', class: 'w-44' },
|
|
4
4
|
{ field: :execution_count_sort, label: 'Executions', class: 'w-24' },
|
|
5
5
|
{ field: nil, label: 'Tags', class: 'w-32' }
|
|
6
6
|
] %>
|
|
7
7
|
|
|
8
|
-
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
|
8
|
+
<table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
|
|
9
9
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
10
10
|
|
|
11
11
|
<tbody>
|
|
12
12
|
<% @table_data.each do |summary| %>
|
|
13
13
|
<tr>
|
|
14
|
-
<td class="
|
|
14
|
+
<td class="truncate-cell">
|
|
15
15
|
<div>
|
|
16
16
|
<%= link_to html_escape(truncate_sql(summary.normalized_sql)), query_path(summary.query_id), data: { turbo_frame: '_top', } %>
|
|
17
17
|
</div>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<% columns = [
|
|
2
|
-
{ field: :
|
|
3
|
-
{ field: :
|
|
2
|
+
{ field: :route_path, label: 'Route', cell_class: 'truncate-cell' },
|
|
3
|
+
{ field: :occurred_at, label: 'Occurred At', class: 'w-48' },
|
|
4
4
|
{ field: :duration, label: 'Response Time', class: 'w-36' },
|
|
5
5
|
{ field: :status, label: 'Status', class: 'w-20' },
|
|
6
6
|
{ field: nil, label: 'Tags', class: 'w-32' }
|
|
7
7
|
] %>
|
|
8
8
|
|
|
9
|
-
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
|
9
|
+
<table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
|
|
10
10
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
11
11
|
|
|
12
12
|
<tbody>
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
end
|
|
22
22
|
%>
|
|
23
23
|
<tr>
|
|
24
|
-
<td class="
|
|
25
|
-
<%= link_to
|
|
24
|
+
<td class="truncate-cell">
|
|
25
|
+
<%= link_to "#{request.route.path} #{request.route.method}", route_path(request.route), data: { turbo_frame: '_top' } %>
|
|
26
26
|
</td>
|
|
27
27
|
<td class="whitespace-nowrap">
|
|
28
|
-
<%= link_to
|
|
28
|
+
<%= link_to human_readable_occurred_at(request.occurred_at), request_path(request), data: { turbo_frame: '_top' } %>
|
|
29
29
|
</td>
|
|
30
30
|
<td class="whitespace-nowrap">
|
|
31
31
|
<span class="<%= performance_class %> font-medium">
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<% columns = [
|
|
2
|
-
{ field: :route_path, label: 'Route', class: 'w-
|
|
2
|
+
{ field: :route_path, label: 'Route', class: 'w-48', cell_class: 'truncate-cell' },
|
|
3
3
|
{ field: :avg_duration_sort, label: 'Average Response Time', class: 'w-48' },
|
|
4
4
|
{ field: :max_duration_sort, label: 'Max Response Time', class: 'w-44' },
|
|
5
5
|
{ field: :count_sort, label: 'Requests', class: 'w-24' },
|
|
6
6
|
{ field: nil, label: 'Tags', class: 'w-32' }
|
|
7
7
|
] %>
|
|
8
8
|
|
|
9
|
-
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
|
9
|
+
<table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
|
|
10
10
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
11
11
|
|
|
12
12
|
<tbody>
|
|
13
13
|
<% @table_data.each do |summary| %>
|
|
14
14
|
<tr>
|
|
15
|
-
<td class="
|
|
15
|
+
<td class="truncate-cell"><%= link_to "#{summary.path} #{summary.route_method}", route_path(summary.route_id), data: { turbo_frame: '_top' } %></td>
|
|
16
16
|
<td class="whitespace-nowrap"><%= summary.avg_duration.to_i %> ms</td>
|
|
17
17
|
<td class="whitespace-nowrap"><%= summary.max_duration.to_i %> ms</td>
|
|
18
18
|
<td class="whitespace-nowrap"><%= number_with_delimiter summary.count %></td>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
data-rails-pulse--index-chart-id-value="route_responses_chart"
|
|
16
16
|
>
|
|
17
17
|
<div class="grid-item">
|
|
18
|
-
<%= render 'rails_pulse/components/panel', { title: 'Route
|
|
18
|
+
<%= render 'rails_pulse/components/panel', { title: 'Route Requests', } do %>
|
|
19
19
|
<%= search_form_for @ransack_query, url: route_path(@route), class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
|
|
20
20
|
<div class="flex items-center grow gap">
|
|
21
21
|
<%= time_range_selector(form, time_range_options: RailsPulse::RoutesController::TIME_RANGE_OPTIONS, selected_time_range: @selected_time_range) %>
|
|
@@ -7,21 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
<div
|
|
9
9
|
id="tag_manager_<%= taggable_type %>_<%= taggable.id %>"
|
|
10
|
-
class="tag-manager"
|
|
10
|
+
class="tag-manager card pi-3 pb-2 text-sm shadow-xs"
|
|
11
11
|
>
|
|
12
|
+
<% if current_tags.present? %>
|
|
13
|
+
Tags:
|
|
14
|
+
<% end %>
|
|
12
15
|
<div class="tag-list">
|
|
13
16
|
<% current_tags.each do |tag| %>
|
|
14
|
-
|
|
15
|
-
<%= tag %>
|
|
16
|
-
<%= button_to remove_tag_path(taggable_type, taggable.id, tag: tag),
|
|
17
|
-
method: :delete,
|
|
18
|
-
class: "tag-remove",
|
|
19
|
-
data: {
|
|
20
|
-
turbo_frame: "_top"
|
|
21
|
-
} do %>
|
|
22
|
-
<span aria-hidden="true">×</span>
|
|
23
|
-
<% end %>
|
|
24
|
-
</span>
|
|
17
|
+
<%= render_tag_badge(tag, variant: :secondary, removable: true, taggable_type: taggable_type, taggable_id: taggable.id) %>
|
|
25
18
|
<% end %>
|
|
26
19
|
|
|
27
20
|
<% if available_to_add.any? %>
|
|
@@ -35,7 +28,7 @@
|
|
|
35
28
|
aria-haspopup="true"
|
|
36
29
|
aria-controls="tag_menu_<%= taggable_type %>_<%= taggable.id %>"
|
|
37
30
|
>
|
|
38
|
-
+
|
|
31
|
+
tag +
|
|
39
32
|
</button>
|
|
40
33
|
|
|
41
34
|
<div
|
|
@@ -62,7 +55,7 @@
|
|
|
62
55
|
},
|
|
63
56
|
style: "cursor: pointer;",
|
|
64
57
|
role: "menuitem" do %>
|
|
65
|
-
<%= tag %>
|
|
58
|
+
<%= tag.humanize %>
|
|
66
59
|
<% end %>
|
|
67
60
|
<% end %>
|
|
68
61
|
</div>
|