railswatch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +485 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/railswatch_manifest.js +0 -0
  6. data/app/assets/images/activity.svg +13 -0
  7. data/app/assets/images/bot.svg +1 -0
  8. data/app/assets/images/close.svg +13 -0
  9. data/app/assets/images/details.svg +3 -0
  10. data/app/assets/images/download.svg +3 -0
  11. data/app/assets/images/export.svg +13 -0
  12. data/app/assets/images/external.svg +1 -0
  13. data/app/assets/images/git.svg +1 -0
  14. data/app/assets/images/github.svg +1 -0
  15. data/app/assets/images/home.svg +16 -0
  16. data/app/assets/images/import.svg +13 -0
  17. data/app/assets/images/menu.svg +16 -0
  18. data/app/assets/images/moon.svg +3 -0
  19. data/app/assets/images/stat.svg +1 -0
  20. data/app/assets/images/sun.svg +4 -0
  21. data/app/assets/images/user.svg +1 -0
  22. data/app/controllers/railswatch/base_controller.rb +35 -0
  23. data/app/controllers/railswatch/concerns/csv_exportable.rb +31 -0
  24. data/app/controllers/railswatch/railswatch_controller.rb +183 -0
  25. data/app/engine_assets/javascripts/apex_ext.js +30 -0
  26. data/app/engine_assets/javascripts/application.js +9 -0
  27. data/app/engine_assets/javascripts/autoupdate.js +79 -0
  28. data/app/engine_assets/javascripts/charts.js +279 -0
  29. data/app/engine_assets/javascripts/navbar.js +11 -0
  30. data/app/engine_assets/javascripts/panel.js +43 -0
  31. data/app/engine_assets/javascripts/table.js +12 -0
  32. data/app/engine_assets/javascripts/theme.js +43 -0
  33. data/app/engine_assets/stylesheets/panel.css +111 -0
  34. data/app/engine_assets/stylesheets/responsive.css +102 -0
  35. data/app/engine_assets/stylesheets/style.css +960 -0
  36. data/app/helpers/railswatch/railswatch_helper.rb +338 -0
  37. data/app/views/railswatch/_panel.html.erb +15 -0
  38. data/app/views/railswatch/layouts/railswatch.html.erb +81 -0
  39. data/app/views/railswatch/railswatch/_card.html.erb +7 -0
  40. data/app/views/railswatch/railswatch/_chart.html.erb +13 -0
  41. data/app/views/railswatch/railswatch/_crashes_table_content.html.erb +62 -0
  42. data/app/views/railswatch/railswatch/_custom_events_table_content.html.erb +27 -0
  43. data/app/views/railswatch/railswatch/_delayed_job_table_content.html.erb +52 -0
  44. data/app/views/railswatch/railswatch/_export.html.erb +4 -0
  45. data/app/views/railswatch/railswatch/_grape_requests_table_content.html.erb +31 -0
  46. data/app/views/railswatch/railswatch/_overview.html.erb +124 -0
  47. data/app/views/railswatch/railswatch/_rake_tasks_table_content.html.erb +25 -0
  48. data/app/views/railswatch/railswatch/_recent_requests_table_content.html.erb +28 -0
  49. data/app/views/railswatch/railswatch/_recent_row.html.erb +41 -0
  50. data/app/views/railswatch/railswatch/_requests_table_content.html.erb +51 -0
  51. data/app/views/railswatch/railswatch/_sidekiq_jobs_table_content.html.erb +50 -0
  52. data/app/views/railswatch/railswatch/_summary.html.erb +50 -0
  53. data/app/views/railswatch/railswatch/_table.html.erb +30 -0
  54. data/app/views/railswatch/railswatch/_trace.html.erb +78 -0
  55. data/app/views/railswatch/railswatch/crashes.html.erb +2 -0
  56. data/app/views/railswatch/railswatch/custom.html.erb +6 -0
  57. data/app/views/railswatch/railswatch/delayed_job.html.erb +6 -0
  58. data/app/views/railswatch/railswatch/grape.html.erb +6 -0
  59. data/app/views/railswatch/railswatch/index.html.erb +9 -0
  60. data/app/views/railswatch/railswatch/rake.html.erb +6 -0
  61. data/app/views/railswatch/railswatch/recent.html.erb +2 -0
  62. data/app/views/railswatch/railswatch/requests.html.erb +2 -0
  63. data/app/views/railswatch/railswatch/resources.html.erb +28 -0
  64. data/app/views/railswatch/railswatch/sidekiq.html.erb +6 -0
  65. data/app/views/railswatch/railswatch/slow.html.erb +2 -0
  66. data/app/views/railswatch/railswatch/summary.js.erb +3 -0
  67. data/app/views/railswatch/railswatch/trace.js.erb +9 -0
  68. data/app/views/railswatch/shared/_header.html.erb +39 -0
  69. data/app/views/railswatch/shared/_page_header.html.erb +23 -0
  70. data/config/routes.rb +27 -0
  71. data/lib/generators/railswatch/install/USAGE +19 -0
  72. data/lib/generators/railswatch/install/install_generator.rb +46 -0
  73. data/lib/generators/railswatch/install/templates/create_railswatch_tables.rb +140 -0
  74. data/lib/generators/railswatch/install/templates/initializer.rb +87 -0
  75. data/lib/railswatch/data_source.rb +106 -0
  76. data/lib/railswatch/engine.rb +103 -0
  77. data/lib/railswatch/events/record.rb +63 -0
  78. data/lib/railswatch/extensions/trace.rb +14 -0
  79. data/lib/railswatch/extensions/trace_db.rb +21 -0
  80. data/lib/railswatch/gems/custom_ext.rb +38 -0
  81. data/lib/railswatch/gems/delayed_job_ext.rb +70 -0
  82. data/lib/railswatch/gems/grape_ext.rb +64 -0
  83. data/lib/railswatch/gems/rake_ext.rb +69 -0
  84. data/lib/railswatch/gems/sidekiq_ext.rb +55 -0
  85. data/lib/railswatch/instrument/metrics_collector.rb +70 -0
  86. data/lib/railswatch/interface.rb +9 -0
  87. data/lib/railswatch/models/application_record.rb +31 -0
  88. data/lib/railswatch/models/base_record.rb +59 -0
  89. data/lib/railswatch/models/collection.rb +37 -0
  90. data/lib/railswatch/models/custom_record.rb +32 -0
  91. data/lib/railswatch/models/delayed_job_record.rb +39 -0
  92. data/lib/railswatch/models/event_record.rb +11 -0
  93. data/lib/railswatch/models/grape_record.rb +61 -0
  94. data/lib/railswatch/models/rake_record.rb +33 -0
  95. data/lib/railswatch/models/request_record.rb +105 -0
  96. data/lib/railswatch/models/resource_record.rb +33 -0
  97. data/lib/railswatch/models/sidekiq_record.rb +41 -0
  98. data/lib/railswatch/models/trace_record.rb +21 -0
  99. data/lib/railswatch/pruner.rb +47 -0
  100. data/lib/railswatch/rails/middleware.rb +117 -0
  101. data/lib/railswatch/rails/query_builder.rb +20 -0
  102. data/lib/railswatch/reports/annotations_report.rb +13 -0
  103. data/lib/railswatch/reports/base_report.rb +48 -0
  104. data/lib/railswatch/reports/breakdown_report.rb +11 -0
  105. data/lib/railswatch/reports/crash_report.rb +11 -0
  106. data/lib/railswatch/reports/overview_report.rb +88 -0
  107. data/lib/railswatch/reports/percentile_report.rb +16 -0
  108. data/lib/railswatch/reports/recent_requests_report.rb +21 -0
  109. data/lib/railswatch/reports/requests_report.rb +75 -0
  110. data/lib/railswatch/reports/resources_report.rb +42 -0
  111. data/lib/railswatch/reports/response_time_report.rb +27 -0
  112. data/lib/railswatch/reports/slow_requests_report.rb +21 -0
  113. data/lib/railswatch/reports/throughput_report.rb +16 -0
  114. data/lib/railswatch/reports/trace_report.rb +17 -0
  115. data/lib/railswatch/system_monitor/resources_monitor.rb +88 -0
  116. data/lib/railswatch/thread/current_request.rb +37 -0
  117. data/lib/railswatch/utils.rb +58 -0
  118. data/lib/railswatch/version.rb +7 -0
  119. data/lib/railswatch/widgets/base.rb +17 -0
  120. data/lib/railswatch/widgets/card.rb +19 -0
  121. data/lib/railswatch/widgets/chart.rb +33 -0
  122. data/lib/railswatch/widgets/crashes_table.rb +27 -0
  123. data/lib/railswatch/widgets/custom_events_table.rb +48 -0
  124. data/lib/railswatch/widgets/delayed_job_table.rb +31 -0
  125. data/lib/railswatch/widgets/grape_requests_table.rb +31 -0
  126. data/lib/railswatch/widgets/percentile_card.rb +23 -0
  127. data/lib/railswatch/widgets/rake_tasks_table.rb +31 -0
  128. data/lib/railswatch/widgets/recent_requests_table.rb +35 -0
  129. data/lib/railswatch/widgets/requests_table.rb +27 -0
  130. data/lib/railswatch/widgets/resource_chart.rb +116 -0
  131. data/lib/railswatch/widgets/response_time_chart.rb +29 -0
  132. data/lib/railswatch/widgets/sidekiq_jobs_table.rb +31 -0
  133. data/lib/railswatch/widgets/slow_requests_table.rb +33 -0
  134. data/lib/railswatch/widgets/table.rb +43 -0
  135. data/lib/railswatch/widgets/throughput_chart.rb +29 -0
  136. data/lib/railswatch.rb +184 -0
  137. data/lib/tasks/railswatch.rake +9 -0
  138. metadata +445 -0
@@ -0,0 +1,124 @@
1
+ <section class="rm-overview-grid">
2
+ <div class="rm-surface rm-overview-panel rm-overview-panel--metrics">
3
+ <div class="rm-section-heading">
4
+ <div>
5
+ <p class="rm-section-heading__eyebrow">Snapshot</p>
6
+ <h2 class="rm-section-heading__title">Health at a glance</h2>
7
+ </div>
8
+ <p class="rm-section-heading__meta">
9
+ <% if @overview[:latest_request_at].present? %>
10
+ Last sample <%= format_rm_datetime(@overview[:latest_request_at]) %>
11
+ <% else %>
12
+ Waiting for incoming traffic
13
+ <% end %>
14
+ </p>
15
+ </div>
16
+
17
+ <div class="rm-kpi-grid">
18
+ <article class="rm-kpi-card" data-tone="neutral">
19
+ <span class="rm-kpi-card__label">Total requests</span>
20
+ <strong class="rm-kpi-card__value"><%= compact_number(@overview[:total_requests]) %></strong>
21
+ <span class="rm-kpi-card__meta">Requests captured in scope</span>
22
+ </article>
23
+
24
+ <article class="rm-kpi-card" data-tone="healthy">
25
+ <span class="rm-kpi-card__label">Average response</span>
26
+ <strong class="rm-kpi-card__value"><%= ms(@overview[:average_duration]) %></strong>
27
+ <span class="rm-kpi-card__meta">Mean request runtime</span>
28
+ </article>
29
+
30
+ <article class="rm-kpi-card" data-tone="<%= health_tone(@overview[:error_rate]) %>">
31
+ <span class="rm-kpi-card__label">Error rate</span>
32
+ <strong class="rm-kpi-card__value"><%= percentage(@overview[:error_rate]) %></strong>
33
+ <span class="rm-kpi-card__meta">5xx share of observed traffic</span>
34
+ </article>
35
+
36
+ <article class="rm-kpi-card" data-tone="<%= @overview[:slow_requests].positive? ? 'warning' : 'healthy' %>">
37
+ <span class="rm-kpi-card__label">Slow requests</span>
38
+ <strong class="rm-kpi-card__value"><%= compact_number(@overview[:slow_requests]) %></strong>
39
+ <span class="rm-kpi-card__meta">Above <%= Railswatch.slow_requests_threshold %> ms</span>
40
+ </article>
41
+
42
+ <article class="rm-kpi-card" data-tone="neutral">
43
+ <span class="rm-kpi-card__label">Unique endpoints</span>
44
+ <strong class="rm-kpi-card__value"><%= compact_number(@overview[:unique_endpoints]) %></strong>
45
+ <span class="rm-kpi-card__meta">Controllers and actions in window</span>
46
+ </article>
47
+ </div>
48
+ </div>
49
+
50
+ <div class="rm-surface rm-overview-panel">
51
+ <div class="rm-section-heading">
52
+ <div>
53
+ <p class="rm-section-heading__eyebrow">Insights</p>
54
+ <h2 class="rm-section-heading__title">Primary signals</h2>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="rm-insight-list">
59
+ <article class="rm-insight-item">
60
+ <span class="rm-insight-item__label">Busiest endpoint</span>
61
+ <strong class="rm-insight-item__value"><%= @overview.dig(:busiest_endpoint, :name) || 'No data yet' %></strong>
62
+ <span class="rm-insight-item__meta"><%= compact_number(@overview.dig(:busiest_endpoint, :count)) %> requests</span>
63
+ </article>
64
+
65
+ <article class="rm-insight-item">
66
+ <span class="rm-insight-item__label">Slowest p95 endpoint</span>
67
+ <strong class="rm-insight-item__value"><%= @overview.dig(:slowest_endpoint, :name) || 'No data yet' %></strong>
68
+ <span class="rm-insight-item__meta"><%= ms(@overview.dig(:slowest_endpoint, :p95_duration)) %> at p95</span>
69
+ </article>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="rm-surface rm-overview-panel">
74
+ <div class="rm-section-heading">
75
+ <div>
76
+ <p class="rm-section-heading__eyebrow">Traffic leaders</p>
77
+ <h2 class="rm-section-heading__title">Most active endpoints</h2>
78
+ </div>
79
+ </div>
80
+
81
+ <div class="rm-ranking-list">
82
+ <% if @traffic_leaders.empty? %>
83
+ <p class="rm-empty-state">No request volume yet.</p>
84
+ <% else %>
85
+ <% @traffic_leaders.each_with_index do |row, index| %>
86
+ <article class="rm-ranking-item">
87
+ <span class="rm-ranking-item__index"><%= index + 1 %></span>
88
+ <div class="rm-ranking-item__body">
89
+ <strong class="rm-ranking-item__title"><%= row[:group] %></strong>
90
+ <span class="rm-ranking-item__meta"><%= compact_number(row[:count]) %> requests</span>
91
+ </div>
92
+ <span class="rm-ranking-item__value"><%= ms(row[:p95_duration]) %></span>
93
+ </article>
94
+ <% end %>
95
+ <% end %>
96
+ </div>
97
+ </div>
98
+
99
+ <div class="rm-surface rm-overview-panel">
100
+ <div class="rm-section-heading">
101
+ <div>
102
+ <p class="rm-section-heading__eyebrow">Latency leaders</p>
103
+ <h2 class="rm-section-heading__title">Highest p95 endpoints</h2>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="rm-ranking-list">
108
+ <% if @latency_leaders.empty? %>
109
+ <p class="rm-empty-state">No latency data yet.</p>
110
+ <% else %>
111
+ <% @latency_leaders.each_with_index do |row, index| %>
112
+ <article class="rm-ranking-item">
113
+ <span class="rm-ranking-item__index"><%= index + 1 %></span>
114
+ <div class="rm-ranking-item__body">
115
+ <strong class="rm-ranking-item__title"><%= row[:group] %></strong>
116
+ <span class="rm-ranking-item__meta"><%= compact_number(row[:count]) %> requests</span>
117
+ </div>
118
+ <span class="rm-ranking-item__value"><%= ms(row[:p95_duration]) %></span>
119
+ </article>
120
+ <% end %>
121
+ <% end %>
122
+ </div>
123
+ </div>
124
+ </section>
@@ -0,0 +1,25 @@
1
+ <table class="<%= table.table_classes %>">
2
+ <thead>
3
+ <tr>
4
+ <th data-sort="string">Datetime</th>
5
+ <th data-sort="string">Details</th>
6
+ <th data-sort="float">Duration</th>
7
+ <th data-sort="string">Status</th>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <% if table.data.empty? %>
12
+ <tr>
13
+ <td colspan="4"><%= table.empty_message %></td>
14
+ </tr>
15
+ <% end %>
16
+ <% table.data.each do |e| %>
17
+ <tr>
18
+ <td><%= format_rm_datetime e[:datetime] %></td>
19
+ <td>[<%= e[:task].join(" ") %>]</td>
20
+ <td class="nowrap"><%= ms e[:duration] %></td>
21
+ <td><%= status_tag e[:status] %></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
@@ -0,0 +1,28 @@
1
+ <table id="<%= table.table_id %>" class="<%= table.table_classes %>">
2
+ <thead>
3
+ <tr>
4
+ <th data-sort="string"></th>
5
+ <th data-sort="string">Datetime</th>
6
+ <th data-sort="string">Controller#action</th>
7
+ <th data-sort="string">Method</th>
8
+ <th data-sort="string">Format</th>
9
+ <th></th>
10
+ <th data-sort="string">Path</th>
11
+ <th data-sort="string">Status</th>
12
+ <th data-sort="float">Duration</th>
13
+ <th data-sort="float">Views</th>
14
+ <th data-sort="float">DB</th>
15
+ <th></th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <% if table.data.empty? %>
20
+ <tr>
21
+ <td colspan="12"><%= table.empty_message %></td>
22
+ </tr>
23
+ <% end %>
24
+ <% table.data.each do |e| %>
25
+ <%= render 'recent_row', e: e %>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
@@ -0,0 +1,41 @@
1
+ <tr from_timei="<%= e[:datetimei] %>">
2
+ <td>
3
+ <% if e[:request_id].present? %>
4
+ <%= link_to railswatch.railswatch_trace_path(id: e[:request_id]), remote: true do %>
5
+ <span class="stats_icon_max"><%= icon 'activity' %></span>
6
+ <% end %>
7
+ <% end %>
8
+ </td>
9
+ <td><%= format_rm_datetime e[:datetime] %></td>
10
+ <td>
11
+ <% controller_action_info = e[:controller] + '#' + e[:action]%>
12
+ <%= link_to railswatch.railswatch_summary_path({controller_eq: e[:controller], action_eq: e[:action]}), remote: true, title: controller_action_info do %>
13
+ <span class="details_icon">
14
+ <%= icon 'details' %>
15
+ </span>
16
+ <%= truncate(controller_action_info, length: 40) %>
17
+ <% end %>
18
+ </td>
19
+ <td><%= e[:method] %></td>
20
+ <td><%= e[:format] %></td>
21
+ <td><%= bot_icon e["user_agent"] %></td>
22
+ <td>
23
+ <span class="with_external_icon">
24
+ <%= link_to_path(e) %>
25
+ <span class="icon external_icon">
26
+ <%= icon('external') %>
27
+ </span>
28
+ </span>
29
+ </td>
30
+ <td><%= status_tag e[:status] %></td>
31
+ <td class="nowrap"><%= ms e[:duration] %></td>
32
+ <td class="nowrap"><%= ms e[:view_runtime] %></td>
33
+ <td class="nowrap"><%= ms e[:db_runtime] %></td>
34
+ <td>
35
+ <% if e[:request_id].present? %>
36
+ <%= link_to railswatch.railswatch_trace_path(id: e[:request_id]), remote: true do %>
37
+ <span class="stats_icon_max"><%= icon 'activity' %></span>
38
+ <% end %>
39
+ <% end %>
40
+ </td>
41
+ </tr>
@@ -0,0 +1,51 @@
1
+ <table class="<%= table.table_classes %>">
2
+ <thead>
3
+ <tr>
4
+ <th colspan='3'>Name</th>
5
+ <th colspan='3' class="attention">Percentile</th>
6
+ <th colspan='3'>Average</th>
7
+ <th colspan='3'>Slowest</th>
8
+ </tr>
9
+ <tr>
10
+ <th data-sort="string">Controller#action</th>
11
+ <th data-sort="string">Format</th>
12
+ <th data-sort="int">Requests</th>
13
+ <th data-sort="float" class="attention">P50</th>
14
+ <th data-sort="float" class="attention">P95</th>
15
+ <th data-sort="float" class="attention">P99</th>
16
+ <th data-sort="float">Duration</th>
17
+ <th data-sort="float">Views</th>
18
+ <th data-sort="float">DB</th>
19
+ <th data-sort="float">Duration</th>
20
+ <th data-sort="float">Views</th>
21
+ <th data-sort="float">DB</th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <% table.data.each do |e| %>
26
+ <% groups = e[:group].split("|") %>
27
+ <% c, a = groups[0].split("#") %>
28
+ <tr>
29
+ <td>
30
+ <%= link_to railswatch.railswatch_summary_path({controller_eq: c, action_eq: a}), remote: true do %>
31
+ <span class="details_icon">
32
+ <%= icon 'details' %>
33
+ </span>
34
+ <%= groups[0] %>
35
+ <% end %>
36
+ </td>
37
+ <td><%= link_to groups[1].try(:upcase), railswatch.railswatch_summary_path({controller_eq: c, action_eq: a, format_eq: groups[1]}), remote: true %></td>
38
+ <td><%= e[:count] %></td>
39
+ <td class="nowrap attention"><%= ms e[:p50_duration] %></td>
40
+ <td class="nowrap attention"><%= ms e[:p95_duration] %></td>
41
+ <td class="nowrap attention"><%= ms e[:p99_duration] %></td>
42
+ <td class="nowrap"><%= ms e[:duration_average] %></td>
43
+ <td class="nowrap"><%= ms e[:view_runtime_average] %></td>
44
+ <td class="nowrap"><%= ms e[:db_runtime_average] %></td>
45
+ <td class="nowrap"><%= ms e[:duration_slowest] %></td>
46
+ <td class="nowrap"><%= ms e[:view_runtime_slowest] %></td>
47
+ <td class="nowrap"><%= ms e[:db_runtime_slowest] %></td>
48
+ </tr>
49
+ <% end %>
50
+ </tbody>
51
+ </table>
@@ -0,0 +1,50 @@
1
+ <table class="<%= table.table_classes %>">
2
+ <thead>
3
+ <tr>
4
+ <th data-sort="string">Datetime</th>
5
+ <th data-sort="string">Queue</th>
6
+ <th data-sort="string">Worker</th>
7
+ <th data-sort="string">Job ID</th>
8
+ <th data-sort="string">Status</th>
9
+ <th data-sort="float">Duration</th>
10
+ <th>Args</th>
11
+ <th>Message / Backtrace</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <% if table.data.empty? %>
16
+ <tr>
17
+ <td colspan="8"><%= table.empty_message %></td>
18
+ </tr>
19
+ <% end %>
20
+ <% table.data.each do |e| %>
21
+ <tr>
22
+ <td><%= format_rm_datetime e[:datetime] %></td>
23
+ <td><%= e[:queue] %></td>
24
+ <td><%= e[:worker] %></td>
25
+ <td><%= e[:jid] %></td>
26
+ <td><%= status_tag e[:status] %></td>
27
+ <td class="nowrap"><%= ms e[:duration] %></td>
28
+ <td class="very-small-text">
29
+ <% args = e[:job_args] %>
30
+ <% if args.present? %>
31
+ <%= args.is_a?(Array) ? args.map(&:inspect).join(', ') : args.inspect %>
32
+ <% else %>
33
+ -
34
+ <% end %>
35
+ </td>
36
+ <td>
37
+ <% if e[:message].present? %>
38
+ <div><%= e[:message] %></div>
39
+ <% end %>
40
+ <% if e[:error_backtrace].present? %>
41
+ <pre class="rm-backtrace rm-backtrace-inline"><%= e[:error_backtrace] %></pre>
42
+ <% end %>
43
+ <% if e[:message].blank? && e[:error_backtrace].blank? %>
44
+ -
45
+ <% end %>
46
+ </td>
47
+ </tr>
48
+ <% end %>
49
+ </tbody>
50
+ </table>
@@ -0,0 +1,50 @@
1
+ <h2 class="rm-panel-section-title">Throughput</h2>
2
+ <railswatch-chart id="throughput_report_chart_mini" type="TIR" legend="RPM" units="rpm" class="chart_mini">
3
+ <%= raw @throughput_report_data.to_json %>
4
+ </railswatch-chart>
5
+
6
+ <h2 class="rm-panel-section-title">Average Response Time</h2>
7
+ <railswatch-chart id="response_time_report_chart_mini" type="RT" legend="Response Time" class="chart_mini">
8
+ <%= raw @response_time_report_data.to_json %>
9
+ </railswatch-chart>
10
+
11
+ <h2 class="rm-panel-section-title"><%= title %></h2>
12
+ <div class="rm-table-shell">
13
+ <table class="table is-fullwidth is-hoverable is-narrow is-size-8">
14
+ <thead>
15
+ <tr>
16
+ <th data-sort="string">Datetime</th>
17
+ <th data-sort="string">Method</th>
18
+ <th></th>
19
+ <th data-sort="string">Path</th>
20
+ <th data-sort="string">Format</th>
21
+ <th data-sort="int">Status</th>
22
+ <th data-sort="float">Duration</th>
23
+ <th data-sort="float">Views</th>
24
+ <th data-sort="float">DB</th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <% @data.each do |e| %>
29
+ <tr>
30
+ <td><%= format_rm_datetime e[:datetime] %></td>
31
+ <td><%= e[:method] %></td>
32
+ <td><%= bot_icon e["user_agent"] %></td>
33
+ <td>
34
+ <span class="with_external_icon">
35
+ <%= link_to_path(e) %>
36
+ <span class="icon external_icon">
37
+ <%= icon('external') %>
38
+ </span>
39
+ </span>
40
+ </td>
41
+ <td><%= e[:format] %></td>
42
+ <td><%= status_tag e[:status] %></td>
43
+ <td class="nowrap"><%= ms e[:duration] %></td>
44
+ <td class="nowrap"><%= ms e[:view_runtime] %></td>
45
+ <td class="nowrap"><%= ms e[:db_runtime] %></td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
50
+ </div>
@@ -0,0 +1,30 @@
1
+ <section class="rm-surface rm-widget-card rm-widget-card--table">
2
+ <header class="rm-widget-card__header">
3
+ <div>
4
+ <h2 class="rm-widget-card__title"><%= table.subtitle %></h2>
5
+ </div>
6
+
7
+ <div class="rm-widget-card__actions">
8
+ <% if table.show_export? %>
9
+ <%= render "export" %>
10
+ <% end %>
11
+ </div>
12
+ </header>
13
+
14
+ <div class="rm-widget-card__body">
15
+ <% if table.auto_update_interval %>
16
+ <div class="rm-widget-card__toolbar">
17
+ <railswatch-autoupdate interval="<%= table.auto_update_interval %>">
18
+ <label class="rm-toggle">
19
+ <input type="checkbox" />
20
+ <span>Auto-update</span>
21
+ </label>
22
+ </railswatch-autoupdate>
23
+ </div>
24
+ <% end %>
25
+
26
+ <div class="rm-table-shell">
27
+ <%= render table.content_partial_path, table: table %>
28
+ </div>
29
+ </div>
30
+ </section>
@@ -0,0 +1,78 @@
1
+ <% ctx = @record&.request_context.presence %>
2
+ <% bt = @record&.backtrace.presence %>
3
+
4
+ <% if ctx.present? %>
5
+ <h2 class="rm-panel-section-title">Request Context</h2>
6
+ <div class="rm-context-grid">
7
+ <% if ctx['ip'].present? %>
8
+ <div class="rm-context-row">
9
+ <span class="rm-context-label">IP</span>
10
+ <span class="rm-context-value"><%= ctx['ip'] %></span>
11
+ </div>
12
+ <% end %>
13
+ <% if ctx['user_agent'].present? %>
14
+ <div class="rm-context-row">
15
+ <span class="rm-context-label">User Agent</span>
16
+ <span class="rm-context-value rm-context-ua"><%= ctx['user_agent'] %></span>
17
+ </div>
18
+ <% end %>
19
+ <% if ctx['user'].present? %>
20
+ <div class="rm-context-row">
21
+ <span class="rm-context-label">User</span>
22
+ <span class="rm-context-value">
23
+ <% ctx['user'].each do |k, v| %>
24
+ <span class="rm-context-kv"><strong><%= k %>:</strong> <%= v %></span>
25
+ <% end %>
26
+ </span>
27
+ </div>
28
+ <% end %>
29
+ <% if ctx['params'].present? %>
30
+ <div class="rm-context-row">
31
+ <span class="rm-context-label">Params</span>
32
+ <span class="rm-context-value">
33
+ <% ctx['params'].each do |k, v| %>
34
+ <span class="rm-context-kv"><strong><%= k %>:</strong> <%= v %></span>
35
+ <% end %>
36
+ </span>
37
+ </div>
38
+ <% end %>
39
+ </div>
40
+ <% end %>
41
+
42
+ <% if bt.present? %>
43
+ <h2 class="rm-panel-section-title">Exception Backtrace</h2>
44
+ <pre class="rm-backtrace"><%= bt.join("\n") %></pre>
45
+ <% end %>
46
+
47
+ <h2 class="rm-panel-section-title">Trace</h2>
48
+ <div class="rm-table-shell">
49
+ <table class="table is-fullwidth is-hoverable is-narrow is-size-7">
50
+ <tbody>
51
+ <% @data.each do |e| %>
52
+ <tr>
53
+ <% if e["group"] == 'db' %>
54
+ <td class="nowrap has-text-right <%= duration_alert_class(e['duration']) %>">
55
+ <%= e["duration"] %> ms
56
+ </td>
57
+ <td>
58
+ <%= e["sql"] %>
59
+ </td>
60
+ <% elsif e["group"] == 'view' %>
61
+ <td class="nowrap has-text-right <%= duration_alert_class(extract_duration(e['message'])) %>">
62
+ <%= extract_duration(e["message"]) %>
63
+ </td>
64
+ <td>
65
+ <%= e["message"] %>
66
+ </td>
67
+ <% end %>
68
+ </tr>
69
+ <% end %>
70
+
71
+ <% if @data.empty? %>
72
+ <tr>
73
+ <td>Nothing to show here. Perhaps details about the request already expired. Setting "slow_requests_time_window" equal to "duration" helps keep traces available.</td>
74
+ </tr>
75
+ <% end %>
76
+ </tbody>
77
+ </table>
78
+ </div>
@@ -0,0 +1,2 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:crashes) %>
2
+ <%= render @table %>
@@ -0,0 +1,6 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:custom) %>
2
+ <div class="rm-widget-stack">
3
+ <% @widgets.each do |widget| %>
4
+ <%= render widget %>
5
+ <% end %>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:delayed_job) %>
2
+ <div class="rm-widget-stack">
3
+ <% @widgets.each do |widget| %>
4
+ <%= render widget %>
5
+ <% end %>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:grape) %>
2
+ <div class="rm-widget-stack">
3
+ <% @widgets.each do |widget| %>
4
+ <%= render widget %>
5
+ <% end %>
6
+ </div>
@@ -0,0 +1,9 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:index), autoupdate_interval: '10s' %>
2
+
3
+ <%= render 'overview' %>
4
+
5
+ <% @widgets.each do |row| %>
6
+ <div class="row rm-widget-row">
7
+ <%= render row %>
8
+ </div>
9
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:rake) %>
2
+ <div class="rm-widget-stack">
3
+ <% @widgets.each do |widget| %>
4
+ <%= render widget %>
5
+ <% end %>
6
+ </div>
@@ -0,0 +1,2 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:recent), autoupdate_interval: '10s' %>
2
+ <%= render @table %>
@@ -0,0 +1,2 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:requests) %>
2
+ <%= render @table %>
@@ -0,0 +1,28 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:resources), autoupdate_interval: '10s' %>
2
+
3
+ <% @resources_report.servers.each do |server| %>
4
+ <section class="rm-resource-group">
5
+ <div class="rm-resource-group__header">
6
+ <p class="rm-section-heading__eyebrow">Server</p>
7
+ <h2 class="rm-resource-group__title"><%= server.name %></h2>
8
+ </div>
9
+
10
+ <div class="rm-resource-grid">
11
+ <% server.charts.each do |chart| %>
12
+ <section class="rm-surface rm-widget-card">
13
+ <header class="rm-widget-card__header">
14
+ <div>
15
+ <h3 class="rm-widget-card__title"><%= chart.subtitle %></h3>
16
+ </div>
17
+ </header>
18
+ <div class="rm-widget-card__body">
19
+ <railswatch-chart id="<%= chart.id %>" type="<%= chart.type %>" legend="<%= chart.legend %>" class="chart">
20
+ <%= raw chart.data.to_json %>
21
+ </railswatch-chart>
22
+ <p class="rm-widget-card__description"><%= chart.description %></p>
23
+ </div>
24
+ </section>
25
+ <% end %>
26
+ </div>
27
+ </section>
28
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:sidekiq) %>
2
+ <div class="rm-widget-stack">
3
+ <% @widgets.each do |widget| %>
4
+ <%= render widget %>
5
+ <% end %>
6
+ </div>
@@ -0,0 +1,2 @@
1
+ <%= render 'railswatch/shared/page_header', **page_meta(:slow) %>
2
+ <%= render @table %>
@@ -0,0 +1,3 @@
1
+ window.panel.header.html('<%= j report_name(@datasource.query) %>' + window.panel.close);
2
+ window.panel.content.html("<%= j render '/railswatch/railswatch/summary', title: "Requests" %>");
3
+ showPanel();
@@ -0,0 +1,9 @@
1
+ <% if @record %>
2
+ window.panel.header.html("<%= j report_name(@record.record_hash.with_indifferent_access.except(*Railswatch.ignore_trace_headers)) %>" + window.panel.close);
3
+ <% else %>
4
+ window.panel.header.html(window.panel.close);
5
+ <% end %>
6
+
7
+ window.panel.content.html("<%= j render '/railswatch/railswatch/trace' %>");
8
+
9
+ showPanel();
@@ -0,0 +1,39 @@
1
+ <header class="rm-topbar" data-skip-autoupdate>
2
+ <div class="rm-topbar__brand">
3
+ <%= link_to railswatch.railswatch_url, class: "rm-brand" do %>
4
+ <span class="rm-brand__mark">RM</span>
5
+ <span class="rm-brand__copy">
6
+ <strong>Railswatch</strong>
7
+ <small>Observe performance without leaving your app</small>
8
+ </span>
9
+ <% end %>
10
+ </div>
11
+
12
+ <div class="rm-topbar__controls">
13
+ <button type="button" class="rm-icon-button rm-mobile-nav-toggle" data-nav-toggle aria-expanded="false" aria-controls="railswatch-nav">
14
+ <span class="rm-mobile-nav-toggle__line"></span>
15
+ <span class="rm-mobile-nav-toggle__line"></span>
16
+ <span class="rm-mobile-nav-toggle__line"></span>
17
+ <span class="is-sr-only">Toggle navigation</span>
18
+ </button>
19
+
20
+ <button type="button" class="rm-icon-button" data-theme-toggle aria-label="Toggle color theme">
21
+ <span class="rm-theme-toggle__icon rm-theme-toggle__icon--sun"><%= icon('sun') %></span>
22
+ <span class="rm-theme-toggle__icon rm-theme-toggle__icon--moon"><%= icon('moon') %></span>
23
+ <span class="rm-theme-toggle__label" data-theme-label>Theme</span>
24
+ </button>
25
+
26
+ <%= link_to Railswatch.home_link, class: "rm-icon-button", aria: { label: 'Application home' } do %>
27
+ <span class="home_icon"><%= icon('home') %></span>
28
+ <span>Home</span>
29
+ <% end %>
30
+ </div>
31
+
32
+ <nav id="railswatch-nav" class="rm-nav" aria-label="Primary">
33
+ <% navigation_items.each do |item| %>
34
+ <%= link_to item[:path], class: "rm-nav__link #{active?(item[:section])}" do %>
35
+ <span><%= item[:label] %></span>
36
+ <% end %>
37
+ <% end %>
38
+ </nav>
39
+ </header>