rails-profiler 0.30.3 → 0.30.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +402 -0
- data/app/controllers/profiler/api/profiles_controller.rb +1 -0
- data/app/controllers/profiler/application_controller.rb +19 -15
- data/app/controllers/profiler/profiles_controller.rb +85 -10
- data/lib/profiler/cluster/slave_proxy.rb +9 -2
- data/lib/profiler/cluster/slave_registry.rb +4 -0
- data/lib/profiler/cluster/token_cache.rb +30 -0
- data/lib/profiler/collectors/ajax_collector.rb +6 -2
- data/lib/profiler/instrumentation/thread_context_propagation.rb +25 -10
- data/lib/profiler/version.rb +1 -1
- data/lib/profiler.rb +7 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 04defc47e71c610492ff23fbce13ebf669f459a1cac74c5933200bdf4d52365c
|
|
4
|
+
data.tar.gz: d99cf7c5531fd369aa8c9367d95b0a5314f71f38de6ed64a7c81a9693f69566a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz: '
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09ba67258286ddc275d2d1cc36234dd8f0e7d24783034dd3959c666b41d96bb334d5c38d720ceca2254d205449094aa57cac9de6afb1842e3437fbd2d238eac3'
|
|
7
|
+
data.tar.gz: '0936d7ca150be6a80809739a2d9b94f77c51c80fad8796bb01d93fb2ca0047a5da3eb4ae832f9567c381bd3dcaacad3000b0273664362570306060c8b3b3eeec'
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
Write your entry under `[Unreleased]`, for a user of the gem. When your branch is
|
|
9
|
+
ready, run `bin/changelog stamp`: it works out the version the merge will publish
|
|
10
|
+
and turns `[Unreleased]` into `[X.Y.Z] - <date>`, reopening an empty one above.
|
|
11
|
+
Run it again after a rebase or a new commit, it corrects the number and folds the
|
|
12
|
+
new entries in. Never edit a section that already carries a tag.
|
|
13
|
+
|
|
14
|
+
A section written by `stamp` carries `<!-- stamped -->`. In the older sections,
|
|
15
|
+
each bullet carries the short sha of the commit it describes, and commits left out
|
|
16
|
+
on purpose are listed in that version's `excluded:` comment with the reason; all of
|
|
17
|
+
it in HTML comments, invisible once rendered. `bin/changelog coverage` checks that
|
|
18
|
+
every commit of every tag interval is accounted for one way or the other.
|
|
19
|
+
|
|
20
|
+
## [Unreleased]
|
|
21
|
+
|
|
22
|
+
## [0.30.5] - 2026-10-03
|
|
23
|
+
|
|
24
|
+
<!-- stamped -->
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Instrumentation:** Pass the arguments of `Thread.new` on to its block, and keep keyword
|
|
29
|
+
arguments as keywords. While a profile was being collected the block got none at all, which on
|
|
30
|
+
Ruby 3.4 broke the Happy Eyeballs hostname resolution of `Socket.tcp`: connections by hostname
|
|
31
|
+
timed out, for example as `Redis::CannotConnectError`. Keyword arguments were flattened at all
|
|
32
|
+
times, on every Ruby. Affects v0.22.1 through v0.30.4.
|
|
33
|
+
- **Instrumentation:** Raise `ThreadError` on a `Thread.new` with no block during a profiled
|
|
34
|
+
request, as Ruby does.
|
|
35
|
+
|
|
36
|
+
## [0.30.4] - 2026-07-01
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **Cluster:** Resolve profiles across slaves from the master `ProfilesController` <!-- 9236c4c -->
|
|
41
|
+
|
|
42
|
+
## [0.30.3] - 2026-06-29
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Wrap the profile dashboards and `TestRunnerPage` with `QueryClientProvider` <!-- 420ae2f -->
|
|
47
|
+
|
|
48
|
+
## [0.30.2] - 2026-06-29
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **Middleware:** Duplicate downstream headers before mutating them <!-- 3efb20c -->
|
|
53
|
+
|
|
54
|
+
## [0.30.1] - 2026-06-29
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **Cluster:** Fix sentinel mismatch, profile list pagination, nil safety in MCP tools and HTTPS slave URLs <!-- 4b5fb1c -->
|
|
59
|
+
|
|
60
|
+
## [0.30.0] - 2026-06-28
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- **Cluster:** Add master/slave profiler clustering <!-- 1435f22 -->
|
|
65
|
+
|
|
66
|
+
## [0.29.0] - 2026-06-28
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **SSE:** Push live profile updates over server-sent events <!-- fa0ae2e -->
|
|
71
|
+
|
|
72
|
+
## [0.28.0] - 2026-06-06
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **HTTP:** Add the `http_backtrace_depth` configuration option <!-- 25b4f99 -->
|
|
77
|
+
|
|
78
|
+
## [0.27.1] - 2026-06-06
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- **API:** Replace manual `fetch()` calls with `orval` generated TanStack Query hooks <!-- e33341e -->
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- **File store:** Prevent `ENOENT` errors on concurrent profile file deletion <!-- 8898f0e -->
|
|
87
|
+
|
|
88
|
+
## [0.27.0] - 2026-06-02
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
- **MCP:** Add console profiling, mailer detail, logs/env/i18n sections and full documentation <!-- ca362a1 -->
|
|
93
|
+
|
|
94
|
+
## [0.26.0] - 2026-06-02
|
|
95
|
+
|
|
96
|
+
### Added
|
|
97
|
+
|
|
98
|
+
- **Test profiler:** Add the `run_tests` MCP tool, the `track_tests` option, specs and docs <!-- 265ce03 -->
|
|
99
|
+
- **Test profiler:** Enrich reporter, add SSE streaming, MCP tools and env safety <!-- a410c4b -->
|
|
100
|
+
- **Test profiler:** Add test profiling, test runner UI and MCP tool <!-- a116fcb -->
|
|
101
|
+
|
|
102
|
+
## [0.25.0] - 2026-06-01
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- **Console:** Add a console profiling tab with IRB instrumentation <!-- f29937a -->
|
|
107
|
+
|
|
108
|
+
### Changed
|
|
109
|
+
|
|
110
|
+
- **Docs:** Document the console profiling tab and the env override behaviour <!-- 031cac6 -->
|
|
111
|
+
|
|
112
|
+
<!-- excluded:
|
|
113
|
+
bd55e9b specs only, no visible change
|
|
114
|
+
-->
|
|
115
|
+
|
|
116
|
+
## [0.24.0] - 2026-06-01
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
|
|
120
|
+
- **MCP:** Add tools to manage `ENV` variables <!-- 9126194 -->
|
|
121
|
+
|
|
122
|
+
## [0.23.0] - 2026-05-27
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
- **Profile list:** Add a global clear-all button and auto-refresh on tab switch <!-- 460a110 -->
|
|
127
|
+
|
|
128
|
+
## [0.22.1] - 2026-05-26
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- **HTTP:** Propagate collector to child threads, add fire-and-forget support, fix deep backtrace capture <!-- 9d0bd61 -->
|
|
133
|
+
|
|
134
|
+
## [0.22.0] - 2026-05-04
|
|
135
|
+
|
|
136
|
+
### Added
|
|
137
|
+
|
|
138
|
+
- **Mailer:** Add a body preview, a variables display, a queued badge and design alignment <!-- b7d608f -->
|
|
139
|
+
- **Collector:** Implement `MailerCollector`, tracking ActionMailer deliveries <!-- e59dd72 -->
|
|
140
|
+
|
|
141
|
+
### Changed
|
|
142
|
+
|
|
143
|
+
- **Toolbar:** Remove the emoji from the mailer toolbar item and the dashboard tab <!-- 55df9bb -->
|
|
144
|
+
|
|
145
|
+
### Fixed
|
|
146
|
+
|
|
147
|
+
- **Mailer:** Add the Mailers tab to `JobProfileDashboard` <!-- 4ce145d -->
|
|
148
|
+
- **Mailer:** Fix thread isolation and `deliver_later` mode detection <!-- 6e76a99 -->
|
|
149
|
+
- **Mailer:** Add `MailerCollector` to the job profiler and align the toolbar item design <!-- 915be28 -->
|
|
150
|
+
- **Mailer:** Handle the Rails 7 encoded mail string in the `deliver.action_mailer` payload <!-- 5479dcf -->
|
|
151
|
+
- **Mailer:** Replace `Thread.current.delete` with a read and nil write for fiber local variables <!-- 8787201 -->
|
|
152
|
+
- **Version:** Remove the pre-release suffix, which broke Bundler resolution <!-- 98caecd -->
|
|
153
|
+
- **Mailer:** Address code review findings from MR !32 <!-- b46d3d2 -->
|
|
154
|
+
|
|
155
|
+
## [0.21.0] - 2026-05-02
|
|
156
|
+
|
|
157
|
+
### Added
|
|
158
|
+
|
|
159
|
+
- **Toolbar:** Add a collapse/expand toggle <!-- 8f0669a -->
|
|
160
|
+
|
|
161
|
+
## [0.20.0] - 2026-05-01
|
|
162
|
+
|
|
163
|
+
### Added
|
|
164
|
+
|
|
165
|
+
- **UI:** Display the gem version and warn on a mismatch <!-- 64ff6eb -->
|
|
166
|
+
|
|
167
|
+
## [0.19.2] - 2026-04-24
|
|
168
|
+
|
|
169
|
+
### Fixed
|
|
170
|
+
|
|
171
|
+
- **HTTP:** Capture `RestClient` `body_stream`, remove body size limits, show bodies in MCP tool results <!-- 89fb0e5 -->
|
|
172
|
+
|
|
173
|
+
<!-- excluded:
|
|
174
|
+
4527e12 release plumbing, version placeholder reset
|
|
175
|
+
-->
|
|
176
|
+
|
|
177
|
+
## [0.19.1] - 2026-04-17
|
|
178
|
+
|
|
179
|
+
### Fixed
|
|
180
|
+
|
|
181
|
+
- **Env vars:** Fix revert restoring wrong value, SQLite busy errors, middleware rescue handling and UI refresh <!-- 55c8661 -->
|
|
182
|
+
|
|
183
|
+
<!-- excluded:
|
|
184
|
+
4637391 pipeline only
|
|
185
|
+
b53b4d7 pipeline only
|
|
186
|
+
7b33bf6 pipeline only
|
|
187
|
+
-->
|
|
188
|
+
|
|
189
|
+
## [0.19.0] - 2026-04-15
|
|
190
|
+
|
|
191
|
+
### Added
|
|
192
|
+
|
|
193
|
+
- **Env:** Propagate env var overrides to Sidekiq workers <!-- e75a883 -->
|
|
194
|
+
|
|
195
|
+
## [0.18.0] - 2026-04-14
|
|
196
|
+
|
|
197
|
+
### Changed
|
|
198
|
+
|
|
199
|
+
- **UI:** Extract shared tab utilities and redesign the Request tab <!-- 7aa8adb -->
|
|
200
|
+
|
|
201
|
+
### Fixed
|
|
202
|
+
|
|
203
|
+
- **HTTP:** Capture outbound request body passed as second argument to `http.request()` <!-- 5a59665 -->
|
|
204
|
+
|
|
205
|
+
## [0.17.0] - 2026-04-13
|
|
206
|
+
|
|
207
|
+
### Added
|
|
208
|
+
|
|
209
|
+
- **Dashboard:** Add env tab on the home page and env/job toolbar items <!-- d0277ab -->
|
|
210
|
+
|
|
211
|
+
## [0.16.0] - 2026-04-13
|
|
212
|
+
|
|
213
|
+
### Added
|
|
214
|
+
|
|
215
|
+
- **Function profiler:** Add `stackprof` sampling mode with multi-clock support and GC visibility <!-- 5f73512 -->
|
|
216
|
+
|
|
217
|
+
### Fixed
|
|
218
|
+
|
|
219
|
+
- **Function profiler:** Fix `stackprof` bugs, remove minimal mode, improve performance <!-- 76c6197 -->
|
|
220
|
+
|
|
221
|
+
## [0.15.0] - 2026-04-11
|
|
222
|
+
|
|
223
|
+
### Added
|
|
224
|
+
|
|
225
|
+
- **Env:** Add environment variables tab <!-- 0e85f18 -->
|
|
226
|
+
|
|
227
|
+
## [0.14.0] - 2026-04-09
|
|
228
|
+
|
|
229
|
+
### Added
|
|
230
|
+
|
|
231
|
+
- **Profiler:** Link jobs to their triggering request or parent job <!-- 74ba5e3 -->
|
|
232
|
+
|
|
233
|
+
## [0.13.0] - 2026-04-09
|
|
234
|
+
|
|
235
|
+
### Added
|
|
236
|
+
|
|
237
|
+
- **UI:** Add copy/download buttons on HTTP bodies, waterfall view, date sort, and other UX improvements <!-- 5afd003 -->
|
|
238
|
+
- **UI:** Always show waterfall above the HTTP request list <!-- 32c6b6d -->
|
|
239
|
+
- **UI:** Use `SmartBodyPreview` in RequestTab for copy/download on request and response bodies <!-- aef4c0c -->
|
|
240
|
+
|
|
241
|
+
### Fixed
|
|
242
|
+
|
|
243
|
+
- **UI:** Derive download file extension from actual MIME type instead of body category <!-- 1dc0bc8 -->
|
|
244
|
+
- **UI:** Move `sortedJobs` after `filteredJobs` to fix TDZ error <!-- d4de7bc -->
|
|
245
|
+
|
|
246
|
+
## [0.12.0] - 2026-04-09
|
|
247
|
+
|
|
248
|
+
### Added
|
|
249
|
+
|
|
250
|
+
- **Profiler:** Add function-level profiling with flamegraph and stats table <!-- fcb04a6 -->
|
|
251
|
+
|
|
252
|
+
## [0.11.1] - 2026-04-09
|
|
253
|
+
|
|
254
|
+
### Added
|
|
255
|
+
|
|
256
|
+
- **Config:** Add `tmp_path` option to centralize the storage directory <!-- 5e77944 -->
|
|
257
|
+
|
|
258
|
+
### Fixed
|
|
259
|
+
|
|
260
|
+
- **MCP:** Decompress gzip+base64 bodies before returning to UI and MCP tool results <!-- 8f5ac47 -->
|
|
261
|
+
|
|
262
|
+
## [0.11.0] - 2026-04-06
|
|
263
|
+
|
|
264
|
+
### Added
|
|
265
|
+
|
|
266
|
+
- **UI:** Add load-more pagination to the profile list <!-- 9f8458b -->
|
|
267
|
+
|
|
268
|
+
## [0.10.1] - 2026-04-06
|
|
269
|
+
|
|
270
|
+
### Changed
|
|
271
|
+
|
|
272
|
+
- **Docs:** Add full user documentation, with a UI guide, an MCP guide and screenshots <!-- 80a9198 -->
|
|
273
|
+
|
|
274
|
+
## [0.10.0] - 2026-04-06
|
|
275
|
+
|
|
276
|
+
### Added
|
|
277
|
+
|
|
278
|
+
- **Middleware:** Add configurable CORS origins, CSP nonce support and body compression <!-- 3a42188 -->
|
|
279
|
+
|
|
280
|
+
## [0.9.1] - 2026-04-06
|
|
281
|
+
|
|
282
|
+
### Removed
|
|
283
|
+
|
|
284
|
+
- **Collectors:** Remove `PerformanceCollector`, replaced by `FlameGraphCollector` <!-- 212e155 -->
|
|
285
|
+
|
|
286
|
+
## [0.9.0] - 2026-04-06
|
|
287
|
+
|
|
288
|
+
### Added
|
|
289
|
+
|
|
290
|
+
- **Profiler:** Add the `Profiler.measure` custom instrumentation API <!-- 0a3044e -->
|
|
291
|
+
|
|
292
|
+
## [0.8.0] - 2026-04-05
|
|
293
|
+
|
|
294
|
+
### Added
|
|
295
|
+
|
|
296
|
+
- **UI:** Add sortable columns, quick filter presets and flamegraph search <!-- ffd2e6f -->
|
|
297
|
+
|
|
298
|
+
## [0.7.0] - 2026-04-05
|
|
299
|
+
|
|
300
|
+
### Added
|
|
301
|
+
|
|
302
|
+
- **Database:** Add N+1 visual detection and `EXPLAIN ANALYZE` integration <!-- a22902d -->
|
|
303
|
+
|
|
304
|
+
## [0.6.0] - 2026-04-05
|
|
305
|
+
|
|
306
|
+
### Added
|
|
307
|
+
|
|
308
|
+
- **MCP:** Reduce token usage with filtering, body file saving and pagination <!-- 834251d -->
|
|
309
|
+
|
|
310
|
+
## [0.5.0] - 2026-04-04
|
|
311
|
+
|
|
312
|
+
### Added
|
|
313
|
+
|
|
314
|
+
- **MCP:** Add outbound IDs, a domain filter, datetimes, a latest token, exceptions and copy helpers <!-- 9330545 -->
|
|
315
|
+
|
|
316
|
+
## [0.4.0] - 2026-04-03
|
|
317
|
+
|
|
318
|
+
### Added
|
|
319
|
+
|
|
320
|
+
- **Collector:** Add an I18n panel tracking translation lookups per request <!-- 3032a15 -->
|
|
321
|
+
|
|
322
|
+
<!-- excluded:
|
|
323
|
+
b50d1eb pipeline only
|
|
324
|
+
-->
|
|
325
|
+
|
|
326
|
+
## [0.3.0] - 2026-04-02
|
|
327
|
+
|
|
328
|
+
### Added
|
|
329
|
+
|
|
330
|
+
- **Collector:** Add a routes panel and enrich the request collector <!-- bdf0e3c -->
|
|
331
|
+
|
|
332
|
+
## [0.2.0] - 2026-03-29
|
|
333
|
+
|
|
334
|
+
### Added
|
|
335
|
+
|
|
336
|
+
- **Collector:** Add a Logs panel, capturing `Rails.logger` messages per request <!-- 0e0e7ce -->
|
|
337
|
+
- Publish the gem to RubyGems.org <!-- f292a21 -->
|
|
338
|
+
|
|
339
|
+
<!-- excluded:
|
|
340
|
+
10c1314 pipeline only, despite the fix: type
|
|
341
|
+
3f97b1d pipeline only
|
|
342
|
+
a8c2044 pipeline only
|
|
343
|
+
27cc550 pipeline only
|
|
344
|
+
-->
|
|
345
|
+
|
|
346
|
+
## [0.1.4] - 2026-03-29
|
|
347
|
+
|
|
348
|
+
### Added
|
|
349
|
+
|
|
350
|
+
- **Profiler:** Capture the full request and response, and add an MCP history clear <!-- 9922c8a -->
|
|
351
|
+
|
|
352
|
+
## [0.1.3] - 2026-03-29
|
|
353
|
+
|
|
354
|
+
### Changed
|
|
355
|
+
|
|
356
|
+
- **Assets:** Migrate Sass `@import` to `@use`, which removes the build deprecation warnings <!-- 1a67e48 -->
|
|
357
|
+
|
|
358
|
+
## [0.1.2] - 2026-03-25
|
|
359
|
+
|
|
360
|
+
This tag holds nothing but the release commit the pipeline wrote. The interval
|
|
361
|
+
carries no change to the gem, so there is nothing to report for this version.
|
|
362
|
+
|
|
363
|
+
## [0.1.1] - 2026-03-25
|
|
364
|
+
|
|
365
|
+
### Added
|
|
366
|
+
|
|
367
|
+
- **HTTP:** Decompress gzip and deflate response bodies before storing them <!-- f71a5c7 -->
|
|
368
|
+
- **Flamegraph:** Add an interactive flame graph tab <!-- 63c4b67 -->
|
|
369
|
+
- **Config:** Add `.well-known` and `favicon.ico` to the default `skip_paths` <!-- 1286447 -->
|
|
370
|
+
- **Storage:** Add an SQLite backend, a blob store and delete/clear APIs <!-- 544015b -->
|
|
371
|
+
- **UI:** Add filters to the profile list action bar and fix the badge colours <!-- 40d4081 -->
|
|
372
|
+
- **HTTP:** Add binary download, inline preview and smart text formatting <!-- b09eab8 -->
|
|
373
|
+
- Rename the HTTP tab, add the outbound HTTP list and extend MCP coverage <!-- 71bc537 -->
|
|
374
|
+
- **Jobs:** Add background job profiling for Sidekiq and ActiveJob <!-- 27b87db -->
|
|
375
|
+
- Add an outbound HTTP request collector <!-- e5f2ac9 -->
|
|
376
|
+
- **MCP:** Migrate to the official Ruby SDK, over HTTP transport <!-- 3fc2d45 -->
|
|
377
|
+
- Implement the dashboard UI, with a tab system and a design system <!-- beb83f7 -->
|
|
378
|
+
- Add the AJAX and dump collectors, and the CORS middleware <!-- 08bad9f -->
|
|
379
|
+
- First implementation of the gem <!-- 360c6fa -->
|
|
380
|
+
|
|
381
|
+
### Changed
|
|
382
|
+
|
|
383
|
+
- **Docs:** Document installation from the GitLab Package Registry <!-- 527c32b -->
|
|
384
|
+
- **Assets:** Serve every gem asset exclusively under `/_profiler/` <!-- e8ae1ae -->
|
|
385
|
+
- **Config:** Add `manifest.json` to the default `skip_paths` <!-- 350f16c -->
|
|
386
|
+
- **Docs:** Correct the installation instructions in the README and remove the dashboard README <!-- e44c8a2 -->
|
|
387
|
+
|
|
388
|
+
### Fixed
|
|
389
|
+
|
|
390
|
+
- **Packaging:** Fix the gemspec metadata and file glob, and exclude source assets <!-- 195b609 -->
|
|
391
|
+
- **Packaging:** Switch to the yarn build scripts and update dependencies <!-- b62f2d8 -->
|
|
392
|
+
- **Packaging:** Fix the `http_collector` require, the collectors option override and asset serving in API only applications <!-- 729f20f -->
|
|
393
|
+
- **Packaging:** Exclude `.gem` build artifacts from the file list <!-- 9b97f60 -->
|
|
394
|
+
- **Profiler:** Improve memory tracking and profile deserialisation <!-- a0af47e -->
|
|
395
|
+
|
|
396
|
+
<!-- excluded:
|
|
397
|
+
e27cdae pipeline only
|
|
398
|
+
02d63c9 pipeline only
|
|
399
|
+
9f598d9 subject is not conventional, tooling only
|
|
400
|
+
7d1d823 tests only
|
|
401
|
+
c0d46e6 licence file, not a change to the gem
|
|
402
|
+
-->
|
|
@@ -12,6 +12,7 @@ module Profiler
|
|
|
12
12
|
# all_types is an opt-in used by the cluster proxy so it can mirror the full
|
|
13
13
|
# storage.list contract; the dashboard relies on the default http-only filter.
|
|
14
14
|
scope = all_types? ? all : all.select { |p| p.profile_type == "http" }
|
|
15
|
+
scope = scope.select { |p| p.parent_token == params[:parent_token] } if params[:parent_token].present?
|
|
15
16
|
page = scope.drop(offset).first(limit + 1)
|
|
16
17
|
render json: {
|
|
17
18
|
profiles: page.first(limit).map(&:to_h),
|
|
@@ -17,26 +17,30 @@ module Profiler
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def build_child_jobs(profile)
|
|
20
|
-
Profiler.storage
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
storage = @resolved_storage || Profiler.storage
|
|
21
|
+
storage.find_by_parent(profile.token)
|
|
22
|
+
.select { |p| p.profile_type == "job" }
|
|
23
|
+
.map do |j|
|
|
24
|
+
job_data = j.collector_data("job") || {}
|
|
25
|
+
{
|
|
26
|
+
token: j.token,
|
|
27
|
+
job_class: j.path,
|
|
28
|
+
job_id: job_data["job_id"],
|
|
29
|
+
queue: job_data["queue"],
|
|
30
|
+
status: job_data["status"],
|
|
31
|
+
duration: j.duration,
|
|
32
|
+
started_at: j.started_at&.iso8601
|
|
33
|
+
}
|
|
34
|
+
end
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def build_parent_summary(profile)
|
|
37
38
|
return nil unless profile.parent_token
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
storage = @resolved_storage || Profiler.storage
|
|
41
|
+
parent = storage.load(profile.parent_token)
|
|
42
|
+
# Cross-node fallback: parent may live on the master when the profile is on a slave
|
|
43
|
+
parent ||= Profiler.storage.load(profile.parent_token) if storage != Profiler.storage
|
|
40
44
|
return nil unless parent
|
|
41
45
|
|
|
42
46
|
if parent.profile_type == "job"
|
|
@@ -13,7 +13,7 @@ module Profiler
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def show
|
|
16
|
-
@profile =
|
|
16
|
+
@profile = resolve_profile(params[:id])
|
|
17
17
|
|
|
18
18
|
unless @profile
|
|
19
19
|
render plain: "Profile not found", status: :not_found
|
|
@@ -34,46 +34,121 @@ module Profiler
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def timeline
|
|
37
|
-
@profile =
|
|
37
|
+
@profile = resolve_profile(params[:id])
|
|
38
|
+
return render plain: "Profile not found", status: :not_found unless @profile
|
|
39
|
+
|
|
38
40
|
render json: @profile.collector_data("performance")
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def database
|
|
42
|
-
@profile =
|
|
44
|
+
@profile = resolve_profile(params[:id])
|
|
45
|
+
return render plain: "Profile not found", status: :not_found unless @profile
|
|
46
|
+
|
|
43
47
|
render json: @profile.collector_data("database")
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
def views
|
|
47
|
-
@profile =
|
|
51
|
+
@profile = resolve_profile(params[:id])
|
|
52
|
+
return render plain: "Profile not found", status: :not_found unless @profile
|
|
53
|
+
|
|
48
54
|
render json: @profile.collector_data("view")
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
def cache
|
|
52
|
-
@profile =
|
|
58
|
+
@profile = resolve_profile(params[:id])
|
|
59
|
+
return render plain: "Profile not found", status: :not_found unless @profile
|
|
60
|
+
|
|
53
61
|
render json: @profile.collector_data("cache")
|
|
54
62
|
end
|
|
55
63
|
|
|
56
64
|
def performance
|
|
57
|
-
@profile =
|
|
65
|
+
@profile = resolve_profile(params[:id])
|
|
66
|
+
return render plain: "Profile not found", status: :not_found unless @profile
|
|
67
|
+
|
|
58
68
|
render json: @profile.collector_data("performance")
|
|
59
69
|
end
|
|
60
70
|
|
|
61
71
|
def flamegraph
|
|
62
|
-
@profile =
|
|
72
|
+
@profile = resolve_profile(params[:id])
|
|
73
|
+
return render plain: "Profile not found", status: :not_found unless @profile
|
|
74
|
+
|
|
63
75
|
render json: @profile.collector_data("flamegraph")
|
|
64
76
|
end
|
|
65
77
|
|
|
66
78
|
private
|
|
67
79
|
|
|
80
|
+
# Resolves a profile token against local storage first, then online slaves.
|
|
81
|
+
# Sets @resolved_storage to the storage that owns the profile so that
|
|
82
|
+
# secondary lookups (child jobs, parent, AJAX) go to the same node.
|
|
83
|
+
def resolve_profile(token)
|
|
84
|
+
# Local storage — fast path, covers master-owned profiles
|
|
85
|
+
if (profile = Profiler.storage.load(token))
|
|
86
|
+
@resolved_storage = Profiler.storage
|
|
87
|
+
return profile
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Cache hit — skip fan-out if we already know which slave has this token
|
|
91
|
+
if (cached_slave = Profiler.token_cache.fetch(token))
|
|
92
|
+
entry = Profiler.slave_registry.all.find { |e| e[:name] == cached_slave }
|
|
93
|
+
if entry && entry[:status] == "online"
|
|
94
|
+
begin
|
|
95
|
+
proxy = Cluster::SlaveProxy.new(cached_slave, open_timeout: 2, read_timeout: 3)
|
|
96
|
+
if (profile = proxy.load(token))
|
|
97
|
+
@resolved_storage = proxy
|
|
98
|
+
return profile
|
|
99
|
+
end
|
|
100
|
+
rescue => e
|
|
101
|
+
Rails.logger.warn("[Profiler] Cached slave #{cached_slave} failed for #{token}: #{e.message}")
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
Profiler.token_cache.invalidate(token)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Parallel fan-out across all online slaves
|
|
108
|
+
online_slaves = Profiler.slave_registry.online_names
|
|
109
|
+
return nil if online_slaves.empty?
|
|
110
|
+
|
|
111
|
+
found_profile = nil
|
|
112
|
+
found_proxy = nil
|
|
113
|
+
found_slave = nil
|
|
114
|
+
result_mutex = Mutex.new
|
|
115
|
+
|
|
116
|
+
threads = online_slaves.map do |slave_name|
|
|
117
|
+
Thread.new do
|
|
118
|
+
begin
|
|
119
|
+
proxy = Cluster::SlaveProxy.new(slave_name, open_timeout: 2, read_timeout: 3)
|
|
120
|
+
profile = proxy.load(token)
|
|
121
|
+
if profile
|
|
122
|
+
result_mutex.synchronize do
|
|
123
|
+
unless found_profile
|
|
124
|
+
found_profile = profile
|
|
125
|
+
found_proxy = proxy
|
|
126
|
+
found_slave = slave_name
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
rescue => e
|
|
131
|
+
Rails.logger.warn("[Profiler] Fan-out error for slave #{slave_name} (#{token}): #{e.message}")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
threads.each(&:join)
|
|
137
|
+
|
|
138
|
+
if found_profile
|
|
139
|
+
Profiler.token_cache.store(token, found_slave)
|
|
140
|
+
@resolved_storage = found_proxy
|
|
141
|
+
found_profile
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
68
145
|
def recalculate_ajax_data(profile)
|
|
69
|
-
# Find AJAX collector in the configured collectors
|
|
70
146
|
ajax_collector_class = Profiler::Collectors::AjaxCollector
|
|
71
147
|
|
|
72
148
|
if Profiler.configuration.collectors.include?(ajax_collector_class)
|
|
73
|
-
collector = ajax_collector_class.new(profile)
|
|
149
|
+
collector = ajax_collector_class.new(profile, storage: @resolved_storage || Profiler.storage)
|
|
74
150
|
collector.collect
|
|
75
151
|
|
|
76
|
-
# Update tab metadata to reflect has_data status
|
|
77
152
|
if profile.instance_variable_get(:@collectors_metadata)
|
|
78
153
|
ajax_tab = profile.instance_variable_get(:@collectors_metadata).find { |tab| tab[:key] == 'ajax' }
|
|
79
154
|
if ajax_tab
|
|
@@ -11,11 +11,13 @@ module Profiler
|
|
|
11
11
|
OPEN_TIMEOUT = 5 # seconds
|
|
12
12
|
READ_TIMEOUT = 10 # seconds
|
|
13
13
|
|
|
14
|
-
def initialize(slave_name)
|
|
14
|
+
def initialize(slave_name, open_timeout: nil, read_timeout: nil)
|
|
15
15
|
entry = Profiler.slave_registry.find!(slave_name)
|
|
16
16
|
raise Profiler::Error, "Slave profiler '#{slave_name}' is offline" if entry.status == "offline"
|
|
17
17
|
|
|
18
18
|
@base_url = entry.url.to_s.chomp("/")
|
|
19
|
+
@open_timeout = open_timeout || OPEN_TIMEOUT
|
|
20
|
+
@read_timeout = read_timeout || READ_TIMEOUT
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
# Storage-compatible interface for MCP query tools
|
|
@@ -35,6 +37,11 @@ module Profiler
|
|
|
35
37
|
profile_from_api(raw)
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
def find_by_parent(parent_token)
|
|
41
|
+
data = get_json("/_profiler/api/profiles", parent_token: parent_token, all_types: true)
|
|
42
|
+
Array(data["profiles"]).map { |h| profile_from_api(h) }
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
def clear(type: nil)
|
|
39
46
|
params = type ? "?type=#{URI.encode_www_form_component(type)}" : ""
|
|
40
47
|
delete_json("/_profiler/api/profiles/clear#{params}")
|
|
@@ -80,7 +87,7 @@ module Profiler
|
|
|
80
87
|
|
|
81
88
|
def request(uri, req)
|
|
82
89
|
resp = Net::HTTP.start(uri.hostname, uri.port,
|
|
83
|
-
open_timeout:
|
|
90
|
+
open_timeout: @open_timeout, read_timeout: @read_timeout,
|
|
84
91
|
use_ssl: uri.scheme == "https") do |http|
|
|
85
92
|
http.request(req)
|
|
86
93
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent-ruby"
|
|
4
|
+
|
|
5
|
+
module Profiler
|
|
6
|
+
module Cluster
|
|
7
|
+
class TokenCache
|
|
8
|
+
TTL = 600 # 10 minutes
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@cache = Concurrent::Map.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def fetch(token)
|
|
15
|
+
entry = @cache[token]
|
|
16
|
+
return nil if entry.nil? || Time.now > entry[:expires_at]
|
|
17
|
+
|
|
18
|
+
entry[:slave_name]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def store(token, slave_name)
|
|
22
|
+
@cache[token] = { slave_name: slave_name, expires_at: Time.now + TTL }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def invalidate(token)
|
|
26
|
+
@cache.delete(token)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -5,6 +5,11 @@ require_relative "base_collector"
|
|
|
5
5
|
module Profiler
|
|
6
6
|
module Collectors
|
|
7
7
|
class AjaxCollector < BaseCollector
|
|
8
|
+
def initialize(profile, storage: Profiler.storage)
|
|
9
|
+
super(profile)
|
|
10
|
+
@storage = storage
|
|
11
|
+
end
|
|
12
|
+
|
|
8
13
|
def icon
|
|
9
14
|
"🌐"
|
|
10
15
|
end
|
|
@@ -29,8 +34,7 @@ module Profiler
|
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def collect
|
|
32
|
-
|
|
33
|
-
ajax_profiles = Profiler.storage.find_by_parent(@profile.token)
|
|
37
|
+
ajax_profiles = @storage.find_by_parent(@profile.token)
|
|
34
38
|
|
|
35
39
|
return store_data({}) if ajax_profiles.empty?
|
|
36
40
|
|
|
@@ -9,24 +9,39 @@ module Profiler
|
|
|
9
9
|
].freeze
|
|
10
10
|
|
|
11
11
|
def initialize(*args, &block)
|
|
12
|
+
# Ruby refuses a Thread with no block, and the wrapper below is a block, so
|
|
13
|
+
# without this the error would never come while a profile is being collected.
|
|
14
|
+
return super if block.nil?
|
|
15
|
+
|
|
12
16
|
parent_context = PROPAGATED_KEYS.filter_map do |key|
|
|
13
17
|
val = Thread.current[key]
|
|
14
18
|
[key, val] unless val.nil?
|
|
15
19
|
end.to_h
|
|
16
20
|
|
|
17
|
-
if parent_context.empty?
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
return super if parent_context.empty?
|
|
22
|
+
|
|
23
|
+
# Thread.new hands its arguments to the block, so the wrapper has to take
|
|
24
|
+
# them and pass them on. Ruby 3.4 depends on it in the stdlib: the Happy
|
|
25
|
+
# Eyeballs hostname resolution of Socket.tcp runs
|
|
26
|
+
# Thread.new(*thread_args) { |*thread_args| resolve_hostname(*thread_args) }
|
|
27
|
+
# and a wrapper that swallows the arguments leaves the name unresolved.
|
|
28
|
+
# The wrapper is a named proc rather than a literal block because only a
|
|
29
|
+
# proc can be given the ruby2_keywords flag, which, together with the one
|
|
30
|
+
# on this method, keeps a trailing keyword hash a keyword hash rather than
|
|
31
|
+
# flattening it into a positional argument.
|
|
32
|
+
wrapper = proc do |*block_args|
|
|
33
|
+
parent_context.each { |k, v| Thread.current[k] = v }
|
|
34
|
+
begin
|
|
35
|
+
block&.call(*block_args)
|
|
36
|
+
ensure
|
|
37
|
+
PROPAGATED_KEYS.each { |k| Thread.current[k] = nil }
|
|
27
38
|
end
|
|
28
39
|
end
|
|
40
|
+
wrapper.ruby2_keywords
|
|
41
|
+
|
|
42
|
+
super(*args, &wrapper)
|
|
29
43
|
end
|
|
44
|
+
ruby2_keywords :initialize
|
|
30
45
|
end
|
|
31
46
|
end
|
|
32
47
|
end
|
data/lib/profiler/version.rb
CHANGED
data/lib/profiler.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.30.
|
|
4
|
+
version: 0.30.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sébastien Duplessy
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -103,6 +103,7 @@ executables:
|
|
|
103
103
|
extensions: []
|
|
104
104
|
extra_rdoc_files: []
|
|
105
105
|
files:
|
|
106
|
+
- CHANGELOG.md
|
|
106
107
|
- app/assets/builds/profiler-toolbar.js
|
|
107
108
|
- app/assets/builds/profiler.css
|
|
108
109
|
- app/assets/builds/profiler.js
|
|
@@ -135,6 +136,7 @@ files:
|
|
|
135
136
|
- lib/profiler/cluster/master_client.rb
|
|
136
137
|
- lib/profiler/cluster/slave_proxy.rb
|
|
137
138
|
- lib/profiler/cluster/slave_registry.rb
|
|
139
|
+
- lib/profiler/cluster/token_cache.rb
|
|
138
140
|
- lib/profiler/collectors/ajax_collector.rb
|
|
139
141
|
- lib/profiler/collectors/base_collector.rb
|
|
140
142
|
- lib/profiler/collectors/cache_collector.rb
|
|
@@ -230,6 +232,7 @@ licenses:
|
|
|
230
232
|
metadata:
|
|
231
233
|
homepage_uri: https://git.duplessy.eu/sebastien/rails-profiler-gem
|
|
232
234
|
source_code_uri: https://git.duplessy.eu/sebastien/rails-profiler-gem
|
|
235
|
+
changelog_uri: https://git.duplessy.eu/sebastien/rails-profiler-gem/-/blob/master/CHANGELOG.md
|
|
233
236
|
post_install_message:
|
|
234
237
|
rdoc_options: []
|
|
235
238
|
require_paths:
|