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
@@ -0,0 +1,43 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="748.974" height="457.275" viewBox="0 0 748.974 457.275" xmlns:xlink="http://www.w3.org/1999/xlink" role="img" artist="Katerina Limpitsouni" source="https://undraw.co/">
|
2
|
+
<g id="Group_201" data-name="Group 201" transform="translate(-382.003 -195.455)">
|
3
|
+
<g id="Group_200" data-name="Group 200" transform="translate(382.003 195.455)">
|
4
|
+
<path id="Path_3120-1325" data-name="Path 3120" d="M695.225,508.82,433.394,576.244a34.622,34.622,0,0,1-42.114-24.866L312.1,243.879a34.622,34.622,0,0,1,24.866-42.114l243.591-62.727L642.9,166.948l77.191,299.757A34.622,34.622,0,0,1,695.225,508.82Z" transform="translate(-311.003 -139.037)" fill="#f2f2f2"/>
|
5
|
+
<path id="Path_3121-1326" data-name="Path 3121" d="M338.989,210.925a24.655,24.655,0,0,0-17.708,29.99l79.185,307.5a24.655,24.655,0,0,0,29.99,17.708L692.287,498.7a24.655,24.655,0,0,0,17.708-29.99L634,173.595l-54.792-24.529Z" transform="translate(-310.548 -138.556)" fill="#fff"/>
|
6
|
+
<path id="Path_3122-1327" data-name="Path 3122" d="M629.927,168.5l-40.522,10.435a11.518,11.518,0,0,1-14.026-8.282l-7.707-29.929a.72.72,0,0,1,.989-.837l61.379,27.258a.72.72,0,0,1-.113,1.355Z" transform="translate(-298.695 -139)" fill="#f2f2f2"/>
|
7
|
+
<path id="Path_3123-1328" data-name="Path 3123" d="M612.519,418.284l-119.208,30.7a5.759,5.759,0,0,1-2.872-11.154l119.208-30.7a5.759,5.759,0,1,1,2.872,11.154Z" transform="translate(-302.605 -126.189)" fill="#ccc"/>
|
8
|
+
<path id="Path_3124-1329" data-name="Path 3124" d="M640.149,430.592,497.936,467.214a5.759,5.759,0,1,1-2.872-11.154l142.213-36.622a5.759,5.759,0,0,1,2.872,11.154Z" transform="translate(-302.384 -125.599)" fill="#ccc"/>
|
9
|
+
<circle id="Ellipse_44" data-name="Ellipse 44" cx="20.355" cy="20.355" r="20.355" transform="translate(121.697 319.055)" fill="#FFC91F"/>
|
10
|
+
<path id="Path_3125-1330" data-name="Path 3125" d="M604.421,374.437,446.1,415.191a17.835,17.835,0,0,1-21.694-12.812L391.229,273.49A17.835,17.835,0,0,1,404.041,251.8l158.32-40.754a17.835,17.835,0,0,1,21.694,12.812l33.178,128.889A17.835,17.835,0,0,1,604.421,374.437Z" transform="translate(-307.183 -135.611)" fill="#fff"/>
|
11
|
+
<path id="Path_3126-1331" data-name="Path 3126" d="M604.421,374.437,446.1,415.191a17.835,17.835,0,0,1-21.694-12.812L391.229,273.49A17.835,17.835,0,0,1,404.041,251.8l158.32-40.754a17.835,17.835,0,0,1,21.694,12.812l33.178,128.889A17.835,17.835,0,0,1,604.421,374.437ZM404.563,253.826a15.737,15.737,0,0,0-11.3,19.142l33.178,128.889a15.737,15.737,0,0,0,19.142,11.3L603.9,372.407a15.737,15.737,0,0,0,11.3-19.142L582.025,224.376a15.737,15.737,0,0,0-19.142-11.3Z" transform="translate(-307.183 -135.611)" fill="#e6e6e6"/>
|
12
|
+
<path id="Path_411-1332" data-name="Path 411" d="M550.66,252.63l-79.9,20.568a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l81.335-20.937c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-303.514 -133.861)" fill="#f2f2f2"/>
|
13
|
+
<path id="Path_412-1333" data-name="Path 412" d="M554.1,266l-79.9,20.568a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l81.335-20.937c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-303.349 -133.22)" fill="#f2f2f2"/>
|
14
|
+
<path id="Path_413-1334" data-name="Path 413" d="M461.146,298.825,436.761,305.1a3.1,3.1,0,0,1-3.776-2.23L425.577,274.1a3.1,3.1,0,0,1,2.23-3.776l24.385-6.277a3.105,3.105,0,0,1,3.776,2.23l7.408,28.777a3.1,3.1,0,0,1-2.23,3.776Z" transform="translate(-305.513 -133.047)" fill="#FFC91F"/>
|
15
|
+
<path id="Path_414-1335" data-name="Path 414" d="M562.854,293.445,440.909,324.835a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-304.946 -131.904)" fill="#f2f2f2"/>
|
16
|
+
<path id="Path_415-1336" data-name="Path 415" d="M566.3,306.822,444.353,338.213a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-304.781 -131.263)" fill="#f2f2f2"/>
|
17
|
+
<path id="Path_416-1337" data-name="Path 416" d="M569.739,320.192,447.794,351.582a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.379-31.76c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-304.616 -130.621)" fill="#f2f2f2"/>
|
18
|
+
<path id="Path_417-1338" data-name="Path 417" d="M573.183,333.569,451.237,364.959a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76C576.377,329.564,575.513,332.969,573.183,333.569Z" transform="translate(-304.45 -129.98)" fill="#f2f2f2"/>
|
19
|
+
<path id="Path_418-1339" data-name="Path 418" d="M576.624,346.939,454.679,378.329a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76C579.819,342.934,578.954,346.339,576.624,346.939Z" transform="translate(-304.285 -129.339)" fill="#f2f2f2"/>
|
20
|
+
<path id="Path_395-1340" data-name="Path 395" d="M448.363,470.511a2.111,2.111,0,0,1-1.335-.092l-.026-.011-5.545-2.351a2.126,2.126,0,1,1,1.664-3.913l3.593,1.528,4.708-11.076a2.125,2.125,0,0,1,2.787-1.124h0l-.028.072.029-.073a2.127,2.127,0,0,1,1.124,2.788l-5.539,13.023a2.126,2.126,0,0,1-1.431,1.224Z" transform="translate(-304.809 -123.966)" fill="#fff"/>
|
21
|
+
</g>
|
22
|
+
<g id="Group_199" data-name="Group 199" transform="translate(673.007 225.872) rotate(-8)">
|
23
|
+
<g id="Group_198" data-name="Group 198" transform="translate(125.896 0) rotate(19)">
|
24
|
+
<path id="Path_3127-1341" data-name="Path 3127" d="M304.956,386.7H34.583A34.622,34.622,0,0,1,0,352.114V34.583A34.622,34.622,0,0,1,34.583,0H286.121l53.418,42.577V352.114A34.622,34.622,0,0,1,304.956,386.7Z" transform="translate(0 0)" fill="#e6e6e6"/>
|
25
|
+
<path id="Path_3128-1342" data-name="Path 3128" d="M24.627,0A24.655,24.655,0,0,0,0,24.627V342.158a24.655,24.655,0,0,0,24.627,24.627H295a24.655,24.655,0,0,0,24.627-24.627V37.418L272.683,0Z" transform="translate(9.956 9.956)" fill="#fff"/>
|
26
|
+
<path id="Path_3129-1343" data-name="Path 3129" d="M128.856,11.518H5.759A5.759,5.759,0,0,1,5.759,0h123.1a5.759,5.759,0,0,1,0,11.518Z" transform="translate(123.512 90.767)" fill="#FFC91F"/>
|
27
|
+
<path id="Path_3130-1344" data-name="Path 3130" d="M152.612,11.518H5.759A5.759,5.759,0,0,1,5.759,0H152.612a5.759,5.759,0,1,1,0,11.518Z" transform="translate(123.512 110.204)" fill="#FFC91F"/>
|
28
|
+
<path id="Path_3131-1345" data-name="Path 3131" d="M128.852,0H5.758a5.758,5.758,0,1,0,0,11.517H128.852a5.759,5.759,0,0,0,0-11.517Z" transform="translate(123.517 177.868)" fill="#ccc"/>
|
29
|
+
<path id="Path_3132-1346" data-name="Path 3132" d="M152.609,0H5.758a5.759,5.759,0,1,0,0,11.517h146.85a5.759,5.759,0,1,0,0-11.517Z" transform="translate(123.517 197.307)" fill="#ccc"/>
|
30
|
+
<path id="Path_3133-1347" data-name="Path 3133" d="M128.856,11.518H5.759A5.759,5.759,0,0,1,5.759,0h123.1a5.759,5.759,0,0,1,0,11.518Z" transform="translate(123.512 264.975)" fill="#ccc"/>
|
31
|
+
<path id="Path_3134-1348" data-name="Path 3134" d="M152.612,11.518H5.759A5.759,5.759,0,0,1,5.759,0H152.612a5.759,5.759,0,1,1,0,11.518Z" transform="translate(123.512 284.411)" fill="#ccc"/>
|
32
|
+
<circle id="Ellipse_44-2" data-name="Ellipse 44" cx="20.355" cy="20.355" r="20.355" transform="translate(57.655 85.89)" fill="#FFC91F"/>
|
33
|
+
<path id="Path_395-2-1349" data-name="Path 395" d="M6.909,15.481a2.111,2.111,0,0,1-1.27-.422l-.023-.017L.832,11.382A2.126,2.126,0,0,1,3.419,8.008l3.1,2.376L13.839.832A2.125,2.125,0,0,1,16.819.439h0L16.774.5l.047-.063a2.127,2.127,0,0,1,.393,2.98L8.6,14.649a2.126,2.126,0,0,1-1.691.829Z" transform="translate(69.085 98.528)" fill="#fff"/>
|
34
|
+
<path id="Path_3135-1350" data-name="Path 3135" d="M40.707,20.359A20.354,20.354,0,0,1,20.356,40.721a4.372,4.372,0,0,1-.524-.021A20.353,20.353,0,1,1,40.707,20.359Z" transform="translate(59.75 172.987)" fill="#FFC91F"/>
|
35
|
+
<circle id="Ellipse_44-3" data-name="Ellipse 44" cx="20.355" cy="20.355" r="20.355" transform="translate(57.655 260.097)" fill="#FFC91F"/>
|
36
|
+
<path id="Path_3136-1351" data-name="Path 3136" d="M53.362,43.143H11.518A11.518,11.518,0,0,1,0,31.625V.72A.72.72,0,0,1,1.167.156l52.642,41.7a.72.72,0,0,1-.447,1.284Z" transform="translate(285.137 0.805)" fill="#ccc"/>
|
37
|
+
</g>
|
38
|
+
</g>
|
39
|
+
<path id="Path_3140-1352" data-name="Path 3140" d="M754.518,518.049a9.158,9.158,0,0,1-12.587,3.05L635.078,455.923a9.158,9.158,0,0,1,9.538-15.637l106.852,65.176a9.158,9.158,0,0,1,3.049,12.587Z" transform="translate(123.58 101.359)" fill="#3f3d56"/>
|
40
|
+
<path id="Path_3141-1353" data-name="Path 3141" d="M688.648,486.5a73.265,73.265,0,1,1-24.4-100.7A73.265,73.265,0,0,1,688.648,486.5ZM579.19,419.73a54.949,54.949,0,1,0,75.524-18.3,54.949,54.949,0,0,0-75.524,18.3Z" transform="translate(82.597 67.737)" fill="#3f3d56"/>
|
41
|
+
<circle id="Ellipse_44-4" data-name="Ellipse 44" cx="57.007" cy="57.007" r="57.007" transform="translate(672.542 442.858) rotate(19)" fill="#FFC91F"/>
|
42
|
+
</g>
|
43
|
+
</svg>
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_pulse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rails Pulse
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-09-09 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -145,6 +145,20 @@ dependencies:
|
|
145
145
|
- - "~>"
|
146
146
|
- !ruby/object:Gem::Version
|
147
147
|
version: '6.0'
|
148
|
+
- !ruby/object:Gem::Dependency
|
149
|
+
name: rails-controller-testing
|
150
|
+
requirement: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
type: :development
|
156
|
+
prerelease: false
|
157
|
+
version_requirements: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
148
162
|
description: Ruby on Rails performance monitoring tool that provides insights into
|
149
163
|
your application's performance, helping you identify bottlenecks and optimize your
|
150
164
|
code for better efficiency.
|
@@ -190,7 +204,6 @@ files:
|
|
190
204
|
- app/controllers/concerns/zoom_range_concern.rb
|
191
205
|
- app/controllers/rails_pulse/application_controller.rb
|
192
206
|
- app/controllers/rails_pulse/assets_controller.rb
|
193
|
-
- app/controllers/rails_pulse/caches_controller.rb
|
194
207
|
- app/controllers/rails_pulse/csp_test_controller.rb
|
195
208
|
- app/controllers/rails_pulse/dashboard_controller.rb
|
196
209
|
- app/controllers/rails_pulse/operations_controller.rb
|
@@ -199,7 +212,6 @@ files:
|
|
199
212
|
- app/controllers/rails_pulse/routes_controller.rb
|
200
213
|
- app/helpers/rails_pulse/application_helper.rb
|
201
214
|
- app/helpers/rails_pulse/breadcrumbs_helper.rb
|
202
|
-
- app/helpers/rails_pulse/cached_component_helper.rb
|
203
215
|
- app/helpers/rails_pulse/chart_formatters.rb
|
204
216
|
- app/helpers/rails_pulse/chart_helper.rb
|
205
217
|
- app/helpers/rails_pulse/formatting_helper.rb
|
@@ -219,10 +231,11 @@ files:
|
|
219
231
|
- app/javascript/rails_pulse/controllers/timezone_controller.js
|
220
232
|
- app/javascript/rails_pulse/theme.js
|
221
233
|
- app/jobs/rails_pulse/application_job.rb
|
234
|
+
- app/jobs/rails_pulse/backfill_summaries_job.rb
|
222
235
|
- app/jobs/rails_pulse/cleanup_job.rb
|
236
|
+
- app/jobs/rails_pulse/summary_job.rb
|
223
237
|
- app/mailers/rails_pulse/application_mailer.rb
|
224
238
|
- app/models/rails_pulse/application_record.rb
|
225
|
-
- app/models/rails_pulse/component_cache_key.rb
|
226
239
|
- app/models/rails_pulse/dashboard/charts/average_response_time.rb
|
227
240
|
- app/models/rails_pulse/dashboard/charts/p95_response_time.rb
|
228
241
|
- app/models/rails_pulse/dashboard/tables/slow_queries.rb
|
@@ -232,6 +245,7 @@ files:
|
|
232
245
|
- app/models/rails_pulse/queries/cards/execution_rate.rb
|
233
246
|
- app/models/rails_pulse/queries/cards/percentile_query_times.rb
|
234
247
|
- app/models/rails_pulse/queries/charts/average_query_times.rb
|
248
|
+
- app/models/rails_pulse/queries/tables/index.rb
|
235
249
|
- app/models/rails_pulse/query.rb
|
236
250
|
- app/models/rails_pulse/request.rb
|
237
251
|
- app/models/rails_pulse/requests/charts/average_response_times.rb
|
@@ -243,13 +257,15 @@ files:
|
|
243
257
|
- app/models/rails_pulse/routes/cards/request_count_totals.rb
|
244
258
|
- app/models/rails_pulse/routes/charts/average_response_times.rb
|
245
259
|
- app/models/rails_pulse/routes/tables/index.rb
|
260
|
+
- app/models/rails_pulse/summary.rb
|
246
261
|
- app/services/rails_pulse/sql_query_normalizer.rb
|
262
|
+
- app/services/rails_pulse/summary_service.rb
|
247
263
|
- app/views/layouts/rails_pulse/_menu_items.html.erb
|
248
264
|
- app/views/layouts/rails_pulse/_sidebar_menu.html.erb
|
249
265
|
- app/views/layouts/rails_pulse/application.html.erb
|
250
|
-
- app/views/rails_pulse/caches/show.html.erb
|
251
266
|
- app/views/rails_pulse/components/_breadcrumbs.html.erb
|
252
267
|
- app/views/rails_pulse/components/_code_panel.html.erb
|
268
|
+
- app/views/rails_pulse/components/_empty_state.html.erb
|
253
269
|
- app/views/rails_pulse/components/_metric_card.html.erb
|
254
270
|
- app/views/rails_pulse/components/_metric_row.html.erb
|
255
271
|
- app/views/rails_pulse/components/_operation_details_popover.html.erb
|
@@ -289,11 +305,10 @@ files:
|
|
289
305
|
- config/initializers/rails_charts_csp_patch.rb
|
290
306
|
- config/initializers/rails_pulse.rb
|
291
307
|
- config/routes.rb
|
292
|
-
- db/migrate/
|
293
|
-
- db/
|
294
|
-
- db/migrate/20250228000000_create_queries.rb
|
295
|
-
- db/migrate/20250228000056_create_operations.rb
|
308
|
+
- db/migrate/20241222000001_create_rails_pulse_summaries.rb
|
309
|
+
- db/rails_pulse_schema.rb
|
296
310
|
- lib/generators/rails_pulse/install_generator.rb
|
311
|
+
- lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb
|
297
312
|
- lib/generators/rails_pulse/templates/rails_pulse.rb
|
298
313
|
- lib/rails_pulse.rb
|
299
314
|
- lib/rails_pulse/cleanup_service.rb
|
@@ -301,9 +316,9 @@ files:
|
|
301
316
|
- lib/rails_pulse/engine.rb
|
302
317
|
- lib/rails_pulse/middleware/asset_server.rb
|
303
318
|
- lib/rails_pulse/middleware/request_collector.rb
|
304
|
-
- lib/rails_pulse/migration.rb
|
305
319
|
- lib/rails_pulse/subscribers/operation_subscriber.rb
|
306
320
|
- lib/rails_pulse/version.rb
|
321
|
+
- lib/tasks/rails_pulse.rake
|
307
322
|
- lib/tasks/rails_pulse_tasks.rake
|
308
323
|
- public/rails-pulse-assets/csp-test.js
|
309
324
|
- public/rails-pulse-assets/rails-pulse-icons.js
|
@@ -312,6 +327,7 @@ files:
|
|
312
327
|
- public/rails-pulse-assets/rails-pulse.css.map
|
313
328
|
- public/rails-pulse-assets/rails-pulse.js
|
314
329
|
- public/rails-pulse-assets/rails-pulse.js.map
|
330
|
+
- public/rails-pulse-assets/search.svg
|
315
331
|
homepage: https://www.railspulse.com
|
316
332
|
licenses:
|
317
333
|
- MIT
|
@@ -1,115 +0,0 @@
|
|
1
|
-
module RailsPulse
|
2
|
-
class CachesController < ApplicationController
|
3
|
-
def show
|
4
|
-
@component_id = params[:id]
|
5
|
-
@context = params[:context]
|
6
|
-
@cache_key = ComponentCacheKey.build(@component_id, @context)
|
7
|
-
|
8
|
-
# Preserve component options before refresh
|
9
|
-
existing_options = {}
|
10
|
-
if params[:refresh]
|
11
|
-
existing_cache = Rails.cache.read(@cache_key)
|
12
|
-
existing_options = existing_cache[:component_options] if existing_cache&.dig(:component_options)
|
13
|
-
Rails.cache.delete(@cache_key)
|
14
|
-
end
|
15
|
-
|
16
|
-
# Check if cache exists with just options (from render_skeleton_with_frame)
|
17
|
-
cached_data = Rails.cache.read(@cache_key)
|
18
|
-
if cached_data && !cached_data[:component_data]
|
19
|
-
# Merge options with full data
|
20
|
-
cached_data = {
|
21
|
-
component_data: calculate_component_data,
|
22
|
-
cached_at: Time.current,
|
23
|
-
component_options: cached_data[:component_options] || {}
|
24
|
-
}
|
25
|
-
Rails.cache.write(@cache_key, cached_data, expires_in: ComponentCacheKey.cache_expires_in)
|
26
|
-
elsif !cached_data
|
27
|
-
# No cache exists, create new one (use preserved options if refreshing)
|
28
|
-
cached_data = {
|
29
|
-
component_data: calculate_component_data,
|
30
|
-
cached_at: Time.current,
|
31
|
-
component_options: existing_options
|
32
|
-
}
|
33
|
-
Rails.cache.write(@cache_key, cached_data, expires_in: ComponentCacheKey.cache_expires_in)
|
34
|
-
end
|
35
|
-
|
36
|
-
@component_data = cached_data[:component_data]
|
37
|
-
@cached_at = cached_data[:cached_at]
|
38
|
-
@component_options = cached_data[:component_options] || {}
|
39
|
-
|
40
|
-
# Update cached_at timestamp in component options if refresh action exists
|
41
|
-
if params[:refresh] && @component_options[:actions]
|
42
|
-
update_cached_at_in_actions(@component_options[:actions], @cached_at)
|
43
|
-
# Update the unified cache with new cached_at timestamp
|
44
|
-
cached_data[:component_options] = @component_options
|
45
|
-
Rails.cache.write(@cache_key, cached_data, expires_in: ComponentCacheKey.cache_expires_in)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def calculate_component_data
|
52
|
-
route = extract_route_from_context
|
53
|
-
query = extract_query_from_context
|
54
|
-
|
55
|
-
case @component_id
|
56
|
-
when "average_response_times"
|
57
|
-
Routes::Cards::AverageResponseTimes.new(route: route).to_metric_card
|
58
|
-
when "percentile_response_times"
|
59
|
-
Routes::Cards::PercentileResponseTimes.new(route: route).to_metric_card
|
60
|
-
when "request_count_totals"
|
61
|
-
Routes::Cards::RequestCountTotals.new(route: route).to_metric_card
|
62
|
-
when "error_rate_per_route"
|
63
|
-
Routes::Cards::ErrorRatePerRoute.new(route: route).to_metric_card
|
64
|
-
when "average_query_times"
|
65
|
-
Queries::Cards::AverageQueryTimes.new(query: query).to_metric_card
|
66
|
-
when "percentile_query_times"
|
67
|
-
Queries::Cards::PercentileQueryTimes.new(query: query).to_metric_card
|
68
|
-
when "execution_rate"
|
69
|
-
Queries::Cards::ExecutionRate.new(query: query).to_metric_card
|
70
|
-
when "dashboard_average_response_time"
|
71
|
-
Dashboard::Charts::AverageResponseTime.new.to_chart_data
|
72
|
-
when "dashboard_p95_response_time"
|
73
|
-
Dashboard::Charts::P95ResponseTime.new.to_chart_data
|
74
|
-
when "dashboard_slow_routes"
|
75
|
-
Dashboard::Tables::SlowRoutes.new.to_table_data
|
76
|
-
when "dashboard_slow_queries"
|
77
|
-
Dashboard::Tables::SlowQueries.new.to_table_data
|
78
|
-
else
|
79
|
-
{ title: "Unknown Metric", summary: "N/A" }
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def extract_route_from_context
|
84
|
-
return unless @context
|
85
|
-
|
86
|
-
# Extract route ID from context like "route_123" or return nil for "routes"/"requests"
|
87
|
-
if @context.match(/^route_(\d+)$/)
|
88
|
-
route_id = @context.match(/^route_(\d+)$/)[1]
|
89
|
-
Route.find(route_id)
|
90
|
-
else
|
91
|
-
nil
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def extract_query_from_context
|
96
|
-
return unless @context
|
97
|
-
|
98
|
-
# Extract query ID from context like "query_123" or return nil for other contexts
|
99
|
-
if @context.match(/^query_(\d+)$/)
|
100
|
-
query_id = @context.match(/^query_(\d+)$/)[1]
|
101
|
-
Query.find(query_id)
|
102
|
-
else
|
103
|
-
nil
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def update_cached_at_in_actions(actions, cached_at)
|
108
|
-
actions.each do |action|
|
109
|
-
if action.dig(:data, :rails_pulse__timezone_cached_at_value)
|
110
|
-
action[:data][:rails_pulse__timezone_cached_at_value] = cached_at.iso8601
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
module RailsPulse
|
2
|
-
module CachedComponentHelper
|
3
|
-
def cached_component(options)
|
4
|
-
# cache_key = ComponentCacheKey.build(options[:id], options[:context])
|
5
|
-
|
6
|
-
# Add refresh action for panels if requested
|
7
|
-
if options[:refresh_action] && options[:component] == "panel"
|
8
|
-
options[:actions] ||= []
|
9
|
-
options[:actions] << refresh_action_params(options[:id], options[:context], options[:content_partial])
|
10
|
-
end
|
11
|
-
|
12
|
-
# if Rails.cache.exist?(cache_key)
|
13
|
-
if false
|
14
|
-
render_cached_content(options)
|
15
|
-
else
|
16
|
-
render_skeleton_with_frame(options)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def render_cached_content(options)
|
23
|
-
cache_key = ComponentCacheKey.build(options[:id], options[:context])
|
24
|
-
cached_data = Rails.cache.read(cache_key)
|
25
|
-
@component_data = cached_data[:component_data]
|
26
|
-
@cached_at = cached_data[:cached_at]
|
27
|
-
component_options = cached_data[:component_options] || {}
|
28
|
-
|
29
|
-
# Wrap the cached content in a Turbo Frame so it can be refreshed using a refresh link in the component
|
30
|
-
turbo_frame_tag "#{options[:id]}_#{options[:component]}", class: options[:class] do
|
31
|
-
render "rails_pulse/components/#{options[:component]}", component_options
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def render_skeleton_with_frame(options)
|
36
|
-
# Store component options temporarily so CachesController can access them
|
37
|
-
cache_key = ComponentCacheKey.build(options[:id], options[:context])
|
38
|
-
Rails.cache.write(cache_key, component_options: options, expires_in: 5.minutes)
|
39
|
-
path_options = options.slice :id, :context
|
40
|
-
|
41
|
-
turbo_frame_tag "#{options[:id]}_#{options[:component]}",
|
42
|
-
src: rails_pulse.cache_path(**path_options),
|
43
|
-
loading: "eager",
|
44
|
-
class: options[:class] do
|
45
|
-
render "rails_pulse/skeletons/#{options[:component]}", options
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def refresh_action_params(id, context, content_partial)
|
50
|
-
refresh_params = {
|
51
|
-
id: id,
|
52
|
-
component_type: "panel",
|
53
|
-
refresh: true
|
54
|
-
}
|
55
|
-
|
56
|
-
# Include content_partial in refresh URL if available
|
57
|
-
refresh_params[:content_partial] = content_partial if content_partial
|
58
|
-
|
59
|
-
{
|
60
|
-
url: rails_pulse.cache_path(refresh_params),
|
61
|
-
icon: "refresh-cw",
|
62
|
-
title: "Refresh data",
|
63
|
-
data: {
|
64
|
-
controller: "rails-pulse--timezone",
|
65
|
-
rails_pulse__timezone_target_frame_value: "#{id}_panel",
|
66
|
-
rails_pulse__timezone_cached_at_value: Time.current.iso8601,
|
67
|
-
turbo_frame: "#{id}_panel",
|
68
|
-
turbo_prefetch: "false"
|
69
|
-
}
|
70
|
-
}
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module RailsPulse
|
2
|
-
# Utility class for generating cache keys for cached components
|
3
|
-
class ComponentCacheKey
|
4
|
-
# Generate a cache key for a specific component type and context
|
5
|
-
#
|
6
|
-
# The cache key includes several parts:
|
7
|
-
# - A namespace prefix to avoid collisions with other cached data
|
8
|
-
# - The context (like "routes" or "route_123") to separate different views
|
9
|
-
# - The component type (like "average_response_times") to separate different components
|
10
|
-
# (Note: Time-based cache expiration is now handled via expires_in option)
|
11
|
-
def self.build(id, context = nil)
|
12
|
-
[ "rails_pulse_component", id, context ].compact
|
13
|
-
end
|
14
|
-
|
15
|
-
# Generate a cache expiration duration with jitter
|
16
|
-
#
|
17
|
-
# This returns a duration that can be used with the expires_in option.
|
18
|
-
# We add some randomness (jitter) so all components don't expire at exactly
|
19
|
-
# the same time, which would cause a "thundering herd" problem where all
|
20
|
-
# components recalculate simultaneously and overwhelm the database.
|
21
|
-
def self.cache_expires_in
|
22
|
-
# Get the configured cache duration (e.g., 5 minutes)
|
23
|
-
cache_duration = RailsPulse.configuration.component_cache_duration.to_i
|
24
|
-
|
25
|
-
# Add up to 25% random jitter to spread out cache expirations
|
26
|
-
max_jitter = (cache_duration * 0.25).to_i
|
27
|
-
jitter = rand(max_jitter)
|
28
|
-
|
29
|
-
# Return the base duration plus jitter
|
30
|
-
cache_duration + jitter
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,9 +0,0 @@
|
|
1
|
-
<%
|
2
|
-
id = "#{@component_id}_#{@component_options[:component]}"
|
3
|
-
%>
|
4
|
-
<%= turbo_frame_tag id do %>
|
5
|
-
<%= render partial: "rails_pulse/components/#{@component_options[:component]}", locals: @component_options %>
|
6
|
-
<script nonce="<%= rails_pulse_csp_nonce %>">
|
7
|
-
initializeChartsInContainer('<%= id %>');
|
8
|
-
</script>
|
9
|
-
<% end %>
|
@@ -1,12 +0,0 @@
|
|
1
|
-
class CreateRoutes < RailsPulse::Migration
|
2
|
-
def change
|
3
|
-
create_table :rails_pulse_routes do |t|
|
4
|
-
t.string :method, null: false, comment: "HTTP method (e.g., GET, POST)"
|
5
|
-
t.string :path, null: false, comment: "Request path (e.g., /posts/index)"
|
6
|
-
|
7
|
-
t.timestamps
|
8
|
-
end
|
9
|
-
|
10
|
-
add_index :rails_pulse_routes, [ :method, :path ], unique: true, name: 'index_rails_pulse_routes_on_method_and_path'
|
11
|
-
end
|
12
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
class CreateRequests < RailsPulse::Migration
|
2
|
-
def change
|
3
|
-
create_table :rails_pulse_requests do |t|
|
4
|
-
t.references :route, null: false, foreign_key: { to_table: :rails_pulse_routes }, comment: "Link to the route"
|
5
|
-
t.decimal :duration, precision: 15, scale: 6, null: false, comment: "Total request duration in milliseconds"
|
6
|
-
t.integer :status, null: false, comment: "HTTP status code (e.g., 200, 500)"
|
7
|
-
t.boolean :is_error, null: false, default: false, comment: "True if status >= 500"
|
8
|
-
t.string :request_uuid, null: false, comment: "Unique identifier for the request (e.g., UUID)"
|
9
|
-
t.string :controller_action, comment: "Controller and action handling the request (e.g., PostsController#show)"
|
10
|
-
t.timestamp :occurred_at, null: false, comment: "When the request started"
|
11
|
-
|
12
|
-
t.timestamps
|
13
|
-
end
|
14
|
-
|
15
|
-
add_index :rails_pulse_requests, :occurred_at, name: 'index_rails_pulse_requests_on_occurred_at'
|
16
|
-
add_index :rails_pulse_requests, :request_uuid, unique: true, name: 'index_rails_pulse_requests_on_request_uuid'
|
17
|
-
add_index :rails_pulse_requests, [ :route_id, :occurred_at ], name: 'index_rails_pulse_requests_on_route_id_and_occurred_at'
|
18
|
-
end
|
19
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
class CreateQueries < RailsPulse::Migration
|
2
|
-
def change
|
3
|
-
create_table :rails_pulse_queries do |t|
|
4
|
-
# Use string with reasonable limit instead of text to avoid MySQL index length issues
|
5
|
-
# 1000 characters should be sufficient for most normalized SQL queries
|
6
|
-
t.string :normalized_sql, limit: 1000, null: false, comment: "Normalized SQL query string (e.g., SELECT * FROM users WHERE id = ?)"
|
7
|
-
|
8
|
-
t.timestamps
|
9
|
-
end
|
10
|
-
|
11
|
-
# Now using string instead of text, no need for database-specific logic
|
12
|
-
add_index :rails_pulse_queries, :normalized_sql, unique: true, name: "index_rails_pulse_queries_on_normalized_sql", length: 191
|
13
|
-
end
|
14
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
class CreateOperations < RailsPulse::Migration
|
2
|
-
def change
|
3
|
-
create_table :rails_pulse_operations do |t|
|
4
|
-
t.references :request, null: false, foreign_key: { to_table: :rails_pulse_requests }, comment: "Link to the request"
|
5
|
-
t.references :query, foreign_key: { to_table: :rails_pulse_queries }, index: true, comment: "Link to the normalized SQL query"
|
6
|
-
t.string :operation_type, null: false, comment: "Type of operation (e.g., database, view, gem_call)"
|
7
|
-
t.string :label, null: false, comment: "Descriptive name (e.g., SELECT FROM users WHERE id = 1, render layout)"
|
8
|
-
t.decimal :duration, precision: 15, scale: 6, null: false, comment: "Operation duration in milliseconds"
|
9
|
-
t.string :codebase_location, comment: "File and line number (e.g., app/models/user.rb:25)"
|
10
|
-
t.float :start_time, null: false, default: 0.0, comment: "Operation start time in milliseconds"
|
11
|
-
t.timestamp :occurred_at, null: false, comment: "When the request started"
|
12
|
-
|
13
|
-
t.timestamps
|
14
|
-
end
|
15
|
-
|
16
|
-
add_index :rails_pulse_operations, :operation_type, name: 'index_rails_pulse_operations_on_operation_type'
|
17
|
-
add_index :rails_pulse_operations, :occurred_at, name: 'index_rails_pulse_operations_on_occurred_at'
|
18
|
-
|
19
|
-
# Performance indexes for queries page optimization
|
20
|
-
add_index :rails_pulse_operations, [ :query_id, :occurred_at ], name: 'index_rails_pulse_operations_on_query_and_time'
|
21
|
-
add_index :rails_pulse_operations, [ :query_id, :duration, :occurred_at ], name: 'index_rails_pulse_operations_query_performance'
|
22
|
-
add_index :rails_pulse_operations, [ :occurred_at, :duration, :operation_type ], name: 'index_rails_pulse_operations_on_time_duration_type'
|
23
|
-
end
|
24
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module RailsPulse
|
2
|
-
# Determine the appropriate migration version based on Rails version
|
3
|
-
migration_version = case Rails.version
|
4
|
-
when /^8\./
|
5
|
-
8.0
|
6
|
-
when /^7\.2/
|
7
|
-
7.2
|
8
|
-
when /^7\.1/
|
9
|
-
7.1
|
10
|
-
when /^7\.0/
|
11
|
-
7.0
|
12
|
-
else
|
13
|
-
7.1 # Default fallback
|
14
|
-
end
|
15
|
-
|
16
|
-
class Migration < ActiveRecord::Migration[migration_version]
|
17
|
-
# This base migration class ensures that Rails Pulse migrations
|
18
|
-
# target the correct database when using multiple database configuration.
|
19
|
-
# The connection is determined by the connects_to configuration.
|
20
|
-
|
21
|
-
def connection
|
22
|
-
if RailsPulse.connects_to
|
23
|
-
RailsPulse::ApplicationRecord.connection
|
24
|
-
else
|
25
|
-
super
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|