rails-profiler 0.32.1 → 0.32.3
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 +117 -74
- data/app/assets/builds/profiler-toolbar.js +25 -9
- data/app/assets/builds/profiler.css +13 -0
- data/app/assets/builds/profiler.js +18 -12
- data/lib/profiler/job_profiler.rb +11 -0
- data/lib/profiler/mcp/server.rb +1 -1
- data/lib/profiler/middleware/toolbar_injector.rb +4 -0
- data/lib/profiler/storage/base_store.rb +8 -1
- data/lib/profiler/test_helpers/minitest_support.rb +21 -3
- data/lib/profiler/test_helpers/rspec_support.rb +54 -3
- data/lib/profiler/test_profiler.rb +4 -13
- data/lib/profiler/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97fa343641a35a6bb20283e405ed8955b7039d4da924a7ce4195feeaeffacec6
|
|
4
|
+
data.tar.gz: d7f9d92015e47e0f20c2b344e2d297b169fcf5d9b184aad17f8d3c68f109f5f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd484fea1576a382bc4a6ecef0a39bad2d25b0197d672c62b41e83fed0ef39dfbaf96e8d6100d9d34ed8fca48d765dacbf97099b8963f505503cfc56058be646
|
|
7
|
+
data.tar.gz: de36e884fb0fd828d6d498c93858c73998762ec5823c7be1806897bd66ab36a97e61c2a1aa9d5a854694a6d184495855c812afcc0b6646cfe5318aac0f0dd037
|
data/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,49 @@ every commit of every tag interval is accounted for one way or the other.
|
|
|
19
19
|
|
|
20
20
|
## [Unreleased]
|
|
21
21
|
|
|
22
|
+
## [0.32.3] - 2026-10-14
|
|
23
|
+
|
|
24
|
+
<!-- stamped -->
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Jobs:** the toolbar of a request now sees the jobs it enqueued start and end after its
|
|
29
|
+
response, without a page reload. A job enqueued by a request is saved as `running` as soon as
|
|
30
|
+
it starts, so it appears in the `child_jobs` of that request while it runs, and every save of
|
|
31
|
+
the job tells the request's toolbar that its profile changed, so it reads the job again when it
|
|
32
|
+
ends, `completed` or `failed`. Without Redis storage, this reaches the toolbar only when the job
|
|
33
|
+
runs in a process serving the profiler's pages (the `async` or `inline` adapters); a job run by
|
|
34
|
+
another process is still listed on the next reload.
|
|
35
|
+
With several Puma workers and no Redis, the toolbar sees a job start and end only when its
|
|
36
|
+
polling happens to reach the worker that ran the job.
|
|
37
|
+
- **Jobs:** a job that is still running is shown as running, in the JOB panel of the toolbar and
|
|
38
|
+
in the Background Jobs list of `/_profiler`: a turning ◐ with `running` and no duration, a blue
|
|
39
|
+
**Running** badge in the list, instead of a green check mark and a duration of 0 ms that read as
|
|
40
|
+
an instant success. The page of the job itself and the Jobs tab of a profile do the same: a blue
|
|
41
|
+
**Running** badge and no duration. The JOB item of the toolbar is blue while one of its jobs runs, the list has
|
|
42
|
+
a **Running** status filter, and its **Completed** filter no longer includes running jobs. The
|
|
43
|
+
`status` filter of the MCP tool `query_jobs` accepts `running`. A job killed while it performs
|
|
44
|
+
(`SIGKILL`, out of memory, container stopped) never records its end, so it stays `running` in the
|
|
45
|
+
toolbar and the list for good.
|
|
46
|
+
|
|
47
|
+
## [0.32.2] - 2026-10-13
|
|
48
|
+
|
|
49
|
+
<!-- stamped -->
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **Test profiling:** `Profiler::TestHelpers::RSpecSupport.install` no longer makes every RSpec
|
|
54
|
+
example fail with `NoMethodError: undefined method 'line_number'`. The workaround documented
|
|
55
|
+
for 0.32.1, a hand-written `around` hook, can be replaced by `install` again.
|
|
56
|
+
- **Test profiling:** Failing and pending RSpec examples are recorded with their real status,
|
|
57
|
+
`failed` or `pending`, instead of `passed`, in the test profiles and in the report printed
|
|
58
|
+
after the suite. A failure keeps its message, and an example marked `pending` or `skip` in its
|
|
59
|
+
body keeps its reason. This holds for an example tagged `:aggregate_failures` and for a failure
|
|
60
|
+
raised by an `around` hook of the suite, which the profiler now wraps.
|
|
61
|
+
- **Test profiling:** With Minitest, an error raised by the profiler no longer runs the test a
|
|
62
|
+
second time, unprofiled, while hiding the error. The test fails with that error, once, and the
|
|
63
|
+
run goes on with the next tests.
|
|
64
|
+
|
|
22
65
|
## [0.32.1] - 2026-10-13
|
|
23
66
|
|
|
24
67
|
<!-- stamped -->
|
|
@@ -847,323 +890,323 @@ every commit of every tag interval is accounted for one way or the other.
|
|
|
847
890
|
|
|
848
891
|
### Fixed
|
|
849
892
|
|
|
850
|
-
- **Cluster:** Resolve profiles across slaves from the master `ProfilesController` <!--
|
|
893
|
+
- **Cluster:** Resolve profiles across slaves from the master `ProfilesController` <!-- 57d379f -->
|
|
851
894
|
|
|
852
895
|
## [0.30.3] - 2026-06-29
|
|
853
896
|
|
|
854
897
|
### Fixed
|
|
855
898
|
|
|
856
|
-
- Wrap the profile dashboards and `TestRunnerPage` with `QueryClientProvider` <!--
|
|
899
|
+
- Wrap the profile dashboards and `TestRunnerPage` with `QueryClientProvider` <!-- e5ad047 -->
|
|
857
900
|
|
|
858
901
|
## [0.30.2] - 2026-06-29
|
|
859
902
|
|
|
860
903
|
### Fixed
|
|
861
904
|
|
|
862
|
-
- **Middleware:** Duplicate downstream headers before mutating them <!--
|
|
905
|
+
- **Middleware:** Duplicate downstream headers before mutating them <!-- 01cea9c -->
|
|
863
906
|
|
|
864
907
|
## [0.30.1] - 2026-06-29
|
|
865
908
|
|
|
866
909
|
### Fixed
|
|
867
910
|
|
|
868
|
-
- **Cluster:** Fix sentinel mismatch, profile list pagination, nil safety in MCP tools and HTTPS slave URLs <!--
|
|
911
|
+
- **Cluster:** Fix sentinel mismatch, profile list pagination, nil safety in MCP tools and HTTPS slave URLs <!-- a8a508b -->
|
|
869
912
|
|
|
870
913
|
## [0.30.0] - 2026-06-28
|
|
871
914
|
|
|
872
915
|
### Added
|
|
873
916
|
|
|
874
|
-
- **Cluster:** Add master/slave profiler clustering <!--
|
|
917
|
+
- **Cluster:** Add master/slave profiler clustering <!-- 8109047 -->
|
|
875
918
|
|
|
876
919
|
## [0.29.0] - 2026-06-28
|
|
877
920
|
|
|
878
921
|
### Added
|
|
879
922
|
|
|
880
|
-
- **SSE:** Push live profile updates over server-sent events <!--
|
|
923
|
+
- **SSE:** Push live profile updates over server-sent events <!-- ff431f6 -->
|
|
881
924
|
|
|
882
925
|
## [0.28.0] - 2026-06-06
|
|
883
926
|
|
|
884
927
|
### Added
|
|
885
928
|
|
|
886
|
-
- **HTTP:** Add the `http_backtrace_depth` configuration option <!--
|
|
929
|
+
- **HTTP:** Add the `http_backtrace_depth` configuration option <!-- e15864c -->
|
|
887
930
|
|
|
888
931
|
## [0.27.1] - 2026-06-06
|
|
889
932
|
|
|
890
933
|
### Changed
|
|
891
934
|
|
|
892
|
-
- **API:** Replace manual `fetch()` calls with `orval` generated TanStack Query hooks <!--
|
|
935
|
+
- **API:** Replace manual `fetch()` calls with `orval` generated TanStack Query hooks <!-- cc3e2ca -->
|
|
893
936
|
|
|
894
937
|
### Fixed
|
|
895
938
|
|
|
896
|
-
- **File store:** Prevent `ENOENT` errors on concurrent profile file deletion <!--
|
|
939
|
+
- **File store:** Prevent `ENOENT` errors on concurrent profile file deletion <!-- d1dd476 -->
|
|
897
940
|
|
|
898
941
|
## [0.27.0] - 2026-06-02
|
|
899
942
|
|
|
900
943
|
### Added
|
|
901
944
|
|
|
902
|
-
- **MCP:** Add console profiling, mailer detail, logs/env/i18n sections and full documentation <!--
|
|
945
|
+
- **MCP:** Add console profiling, mailer detail, logs/env/i18n sections and full documentation <!-- 9f97a3f -->
|
|
903
946
|
|
|
904
947
|
## [0.26.0] - 2026-06-02
|
|
905
948
|
|
|
906
949
|
### Added
|
|
907
950
|
|
|
908
|
-
- **Test profiler:** Add the `run_tests` MCP tool, the `track_tests` option, specs and docs <!--
|
|
909
|
-
- **Test profiler:** Enrich reporter, add SSE streaming, MCP tools and env safety <!--
|
|
910
|
-
- **Test profiler:** Add test profiling, test runner UI and MCP tool <!--
|
|
951
|
+
- **Test profiler:** Add the `run_tests` MCP tool, the `track_tests` option, specs and docs <!-- 5c63fc0 -->
|
|
952
|
+
- **Test profiler:** Enrich reporter, add SSE streaming, MCP tools and env safety <!-- 58b2d8b -->
|
|
953
|
+
- **Test profiler:** Add test profiling, test runner UI and MCP tool <!-- 4c50149 -->
|
|
911
954
|
|
|
912
955
|
## [0.25.0] - 2026-06-01
|
|
913
956
|
|
|
914
957
|
### Added
|
|
915
958
|
|
|
916
|
-
- **Console:** Add a console profiling tab with IRB instrumentation <!--
|
|
959
|
+
- **Console:** Add a console profiling tab with IRB instrumentation <!-- c6ce1b7 -->
|
|
917
960
|
|
|
918
961
|
### Changed
|
|
919
962
|
|
|
920
|
-
- **Docs:** Document the console profiling tab and the env override behaviour <!--
|
|
963
|
+
- **Docs:** Document the console profiling tab and the env override behaviour <!-- e4f6fe9 -->
|
|
921
964
|
|
|
922
965
|
<!-- excluded:
|
|
923
|
-
|
|
966
|
+
e8941a4 specs only, no visible change
|
|
924
967
|
-->
|
|
925
968
|
|
|
926
969
|
## [0.24.0] - 2026-06-01
|
|
927
970
|
|
|
928
971
|
### Added
|
|
929
972
|
|
|
930
|
-
- **MCP:** Add tools to manage `ENV` variables <!--
|
|
973
|
+
- **MCP:** Add tools to manage `ENV` variables <!-- 52f43ce -->
|
|
931
974
|
|
|
932
975
|
## [0.23.0] - 2026-05-27
|
|
933
976
|
|
|
934
977
|
### Added
|
|
935
978
|
|
|
936
|
-
- **Profile list:** Add a global clear-all button and auto-refresh on tab switch <!--
|
|
979
|
+
- **Profile list:** Add a global clear-all button and auto-refresh on tab switch <!-- b85fc97 -->
|
|
937
980
|
|
|
938
981
|
## [0.22.1] - 2026-05-26
|
|
939
982
|
|
|
940
983
|
### Fixed
|
|
941
984
|
|
|
942
|
-
- **HTTP:** Propagate collector to child threads, add fire-and-forget support, fix deep backtrace capture <!--
|
|
985
|
+
- **HTTP:** Propagate collector to child threads, add fire-and-forget support, fix deep backtrace capture <!-- 8fd3f63 -->
|
|
943
986
|
|
|
944
987
|
## [0.22.0] - 2026-05-04
|
|
945
988
|
|
|
946
989
|
### Added
|
|
947
990
|
|
|
948
|
-
- **Mailer:** Add a body preview, a variables display, a queued badge and design alignment <!--
|
|
949
|
-
- **Collector:** Implement `MailerCollector`, tracking ActionMailer deliveries <!--
|
|
991
|
+
- **Mailer:** Add a body preview, a variables display, a queued badge and design alignment <!-- d016698 -->
|
|
992
|
+
- **Collector:** Implement `MailerCollector`, tracking ActionMailer deliveries <!-- b1705c9 -->
|
|
950
993
|
|
|
951
994
|
### Changed
|
|
952
995
|
|
|
953
|
-
- **Toolbar:** Remove the emoji from the mailer toolbar item and the dashboard tab <!--
|
|
996
|
+
- **Toolbar:** Remove the emoji from the mailer toolbar item and the dashboard tab <!-- f18e615 -->
|
|
954
997
|
|
|
955
998
|
### Fixed
|
|
956
999
|
|
|
957
|
-
- **Mailer:** Add the Mailers tab to `JobProfileDashboard` <!--
|
|
958
|
-
- **Mailer:** Fix thread isolation and `deliver_later` mode detection <!--
|
|
959
|
-
- **Mailer:** Add `MailerCollector` to the job profiler and align the toolbar item design <!--
|
|
960
|
-
- **Mailer:** Handle the Rails 7 encoded mail string in the `deliver.action_mailer` payload <!--
|
|
961
|
-
- **Mailer:** Replace `Thread.current.delete` with a read and nil write for fiber local variables <!--
|
|
962
|
-
- **Version:** Remove the pre-release suffix, which broke Bundler resolution <!--
|
|
963
|
-
- **Mailer:** Address code review findings from MR !32 <!--
|
|
1000
|
+
- **Mailer:** Add the Mailers tab to `JobProfileDashboard` <!-- 05f0549 -->
|
|
1001
|
+
- **Mailer:** Fix thread isolation and `deliver_later` mode detection <!-- 35c9de3 -->
|
|
1002
|
+
- **Mailer:** Add `MailerCollector` to the job profiler and align the toolbar item design <!-- e566a77 -->
|
|
1003
|
+
- **Mailer:** Handle the Rails 7 encoded mail string in the `deliver.action_mailer` payload <!-- c5519ce -->
|
|
1004
|
+
- **Mailer:** Replace `Thread.current.delete` with a read and nil write for fiber local variables <!-- 2828c44 -->
|
|
1005
|
+
- **Version:** Remove the pre-release suffix, which broke Bundler resolution <!-- eba7e9f -->
|
|
1006
|
+
- **Mailer:** Address code review findings from MR !32 <!-- 655d090 -->
|
|
964
1007
|
|
|
965
1008
|
## [0.21.0] - 2026-05-02
|
|
966
1009
|
|
|
967
1010
|
### Added
|
|
968
1011
|
|
|
969
|
-
- **Toolbar:** Add a collapse/expand toggle <!--
|
|
1012
|
+
- **Toolbar:** Add a collapse/expand toggle <!-- 7bc637a -->
|
|
970
1013
|
|
|
971
1014
|
## [0.20.0] - 2026-05-01
|
|
972
1015
|
|
|
973
1016
|
### Added
|
|
974
1017
|
|
|
975
|
-
- **UI:** Display the gem version and warn on a mismatch <!--
|
|
1018
|
+
- **UI:** Display the gem version and warn on a mismatch <!-- a2509c2 -->
|
|
976
1019
|
|
|
977
1020
|
## [0.19.2] - 2026-04-24
|
|
978
1021
|
|
|
979
1022
|
### Fixed
|
|
980
1023
|
|
|
981
|
-
- **HTTP:** Capture `RestClient` `body_stream`, remove body size limits, show bodies in MCP tool results <!--
|
|
1024
|
+
- **HTTP:** Capture `RestClient` `body_stream`, remove body size limits, show bodies in MCP tool results <!-- 20654f5 -->
|
|
982
1025
|
|
|
983
1026
|
<!-- excluded:
|
|
984
|
-
|
|
1027
|
+
a9f2cd3 release plumbing, version placeholder reset
|
|
985
1028
|
-->
|
|
986
1029
|
|
|
987
1030
|
## [0.19.1] - 2026-04-17
|
|
988
1031
|
|
|
989
1032
|
### Fixed
|
|
990
1033
|
|
|
991
|
-
- **Env vars:** Fix revert restoring wrong value, SQLite busy errors, middleware rescue handling and UI refresh <!--
|
|
1034
|
+
- **Env vars:** Fix revert restoring wrong value, SQLite busy errors, middleware rescue handling and UI refresh <!-- 33bd079 -->
|
|
992
1035
|
|
|
993
1036
|
<!-- excluded:
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1037
|
+
9da240b pipeline only
|
|
1038
|
+
09748d1 pipeline only
|
|
1039
|
+
4e8500b pipeline only
|
|
997
1040
|
-->
|
|
998
1041
|
|
|
999
1042
|
## [0.19.0] - 2026-04-15
|
|
1000
1043
|
|
|
1001
1044
|
### Added
|
|
1002
1045
|
|
|
1003
|
-
- **Env:** Propagate env var overrides to Sidekiq workers <!--
|
|
1046
|
+
- **Env:** Propagate env var overrides to Sidekiq workers <!-- c404799 -->
|
|
1004
1047
|
|
|
1005
1048
|
## [0.18.0] - 2026-04-14
|
|
1006
1049
|
|
|
1007
1050
|
### Changed
|
|
1008
1051
|
|
|
1009
|
-
- **UI:** Extract shared tab utilities and redesign the Request tab <!--
|
|
1052
|
+
- **UI:** Extract shared tab utilities and redesign the Request tab <!-- 47e6269 -->
|
|
1010
1053
|
|
|
1011
1054
|
### Fixed
|
|
1012
1055
|
|
|
1013
|
-
- **HTTP:** Capture outbound request body passed as second argument to `http.request()` <!--
|
|
1056
|
+
- **HTTP:** Capture outbound request body passed as second argument to `http.request()` <!-- 2dda00f -->
|
|
1014
1057
|
|
|
1015
1058
|
## [0.17.0] - 2026-04-13
|
|
1016
1059
|
|
|
1017
1060
|
### Added
|
|
1018
1061
|
|
|
1019
|
-
- **Dashboard:** Add env tab on the home page and env/job toolbar items <!--
|
|
1062
|
+
- **Dashboard:** Add env tab on the home page and env/job toolbar items <!-- c623c40 -->
|
|
1020
1063
|
|
|
1021
1064
|
## [0.16.0] - 2026-04-13
|
|
1022
1065
|
|
|
1023
1066
|
### Added
|
|
1024
1067
|
|
|
1025
|
-
- **Function profiler:** Add `stackprof` sampling mode with multi-clock support and GC visibility <!--
|
|
1068
|
+
- **Function profiler:** Add `stackprof` sampling mode with multi-clock support and GC visibility <!-- 5f461d9 -->
|
|
1026
1069
|
|
|
1027
1070
|
### Fixed
|
|
1028
1071
|
|
|
1029
|
-
- **Function profiler:** Fix `stackprof` bugs, remove minimal mode, improve performance <!--
|
|
1072
|
+
- **Function profiler:** Fix `stackprof` bugs, remove minimal mode, improve performance <!-- 8adc3ca -->
|
|
1030
1073
|
|
|
1031
1074
|
## [0.15.0] - 2026-04-11
|
|
1032
1075
|
|
|
1033
1076
|
### Added
|
|
1034
1077
|
|
|
1035
|
-
- **Env:** Add environment variables tab <!--
|
|
1078
|
+
- **Env:** Add environment variables tab <!-- fa3bcec -->
|
|
1036
1079
|
|
|
1037
1080
|
## [0.14.0] - 2026-04-09
|
|
1038
1081
|
|
|
1039
1082
|
### Added
|
|
1040
1083
|
|
|
1041
|
-
- **Profiler:** Link jobs to their triggering request or parent job <!--
|
|
1084
|
+
- **Profiler:** Link jobs to their triggering request or parent job <!-- 5d598f6 -->
|
|
1042
1085
|
|
|
1043
1086
|
## [0.13.0] - 2026-04-09
|
|
1044
1087
|
|
|
1045
1088
|
### Added
|
|
1046
1089
|
|
|
1047
|
-
- **UI:** Add copy/download buttons on HTTP bodies, waterfall view, date sort, and other UX improvements <!--
|
|
1048
|
-
- **UI:** Always show waterfall above the HTTP request list <!--
|
|
1049
|
-
- **UI:** Use `SmartBodyPreview` in RequestTab for copy/download on request and response bodies <!--
|
|
1090
|
+
- **UI:** Add copy/download buttons on HTTP bodies, waterfall view, date sort, and other UX improvements <!-- e7b430b -->
|
|
1091
|
+
- **UI:** Always show waterfall above the HTTP request list <!-- bec6cb0 -->
|
|
1092
|
+
- **UI:** Use `SmartBodyPreview` in RequestTab for copy/download on request and response bodies <!-- 439a9ac -->
|
|
1050
1093
|
|
|
1051
1094
|
### Fixed
|
|
1052
1095
|
|
|
1053
|
-
- **UI:** Derive download file extension from actual MIME type instead of body category <!--
|
|
1054
|
-
- **UI:** Move `sortedJobs` after `filteredJobs` to fix TDZ error <!--
|
|
1096
|
+
- **UI:** Derive download file extension from actual MIME type instead of body category <!-- 987a65e -->
|
|
1097
|
+
- **UI:** Move `sortedJobs` after `filteredJobs` to fix TDZ error <!-- 235d492 -->
|
|
1055
1098
|
|
|
1056
1099
|
## [0.12.0] - 2026-04-09
|
|
1057
1100
|
|
|
1058
1101
|
### Added
|
|
1059
1102
|
|
|
1060
|
-
- **Profiler:** Add function-level profiling with flamegraph and stats table <!--
|
|
1103
|
+
- **Profiler:** Add function-level profiling with flamegraph and stats table <!-- 2781cff -->
|
|
1061
1104
|
|
|
1062
1105
|
## [0.11.1] - 2026-04-09
|
|
1063
1106
|
|
|
1064
1107
|
### Added
|
|
1065
1108
|
|
|
1066
|
-
- **Config:** Add `tmp_path` option to centralize the storage directory <!--
|
|
1109
|
+
- **Config:** Add `tmp_path` option to centralize the storage directory <!-- d68a4c7 -->
|
|
1067
1110
|
|
|
1068
1111
|
### Fixed
|
|
1069
1112
|
|
|
1070
|
-
- **MCP:** Decompress gzip+base64 bodies before returning to UI and MCP tool results <!--
|
|
1113
|
+
- **MCP:** Decompress gzip+base64 bodies before returning to UI and MCP tool results <!-- c551773 -->
|
|
1071
1114
|
|
|
1072
1115
|
## [0.11.0] - 2026-04-06
|
|
1073
1116
|
|
|
1074
1117
|
### Added
|
|
1075
1118
|
|
|
1076
|
-
- **UI:** Add load-more pagination to the profile list <!--
|
|
1119
|
+
- **UI:** Add load-more pagination to the profile list <!-- ef49a2a -->
|
|
1077
1120
|
|
|
1078
1121
|
## [0.10.1] - 2026-04-06
|
|
1079
1122
|
|
|
1080
1123
|
### Changed
|
|
1081
1124
|
|
|
1082
|
-
- **Docs:** Add full user documentation, with a UI guide, an MCP guide and screenshots <!--
|
|
1125
|
+
- **Docs:** Add full user documentation, with a UI guide, an MCP guide and screenshots <!-- 2e34dc1 -->
|
|
1083
1126
|
|
|
1084
1127
|
## [0.10.0] - 2026-04-06
|
|
1085
1128
|
|
|
1086
1129
|
### Added
|
|
1087
1130
|
|
|
1088
|
-
- **Middleware:** Add configurable CORS origins, CSP nonce support and body compression <!--
|
|
1131
|
+
- **Middleware:** Add configurable CORS origins, CSP nonce support and body compression <!-- bb13049 -->
|
|
1089
1132
|
|
|
1090
1133
|
## [0.9.1] - 2026-04-06
|
|
1091
1134
|
|
|
1092
1135
|
### Removed
|
|
1093
1136
|
|
|
1094
|
-
- **Collectors:** Remove `PerformanceCollector`, replaced by `FlameGraphCollector` <!--
|
|
1137
|
+
- **Collectors:** Remove `PerformanceCollector`, replaced by `FlameGraphCollector` <!-- fd03ac6 -->
|
|
1095
1138
|
|
|
1096
1139
|
## [0.9.0] - 2026-04-06
|
|
1097
1140
|
|
|
1098
1141
|
### Added
|
|
1099
1142
|
|
|
1100
|
-
- **Profiler:** Add the `Profiler.measure` custom instrumentation API <!--
|
|
1143
|
+
- **Profiler:** Add the `Profiler.measure` custom instrumentation API <!-- a3ab553 -->
|
|
1101
1144
|
|
|
1102
1145
|
## [0.8.0] - 2026-04-05
|
|
1103
1146
|
|
|
1104
1147
|
### Added
|
|
1105
1148
|
|
|
1106
|
-
- **UI:** Add sortable columns, quick filter presets and flamegraph search <!--
|
|
1149
|
+
- **UI:** Add sortable columns, quick filter presets and flamegraph search <!-- 5bfb511 -->
|
|
1107
1150
|
|
|
1108
1151
|
## [0.7.0] - 2026-04-05
|
|
1109
1152
|
|
|
1110
1153
|
### Added
|
|
1111
1154
|
|
|
1112
|
-
- **Database:** Add N+1 visual detection and `EXPLAIN ANALYZE` integration <!--
|
|
1155
|
+
- **Database:** Add N+1 visual detection and `EXPLAIN ANALYZE` integration <!-- af266fc -->
|
|
1113
1156
|
|
|
1114
1157
|
## [0.6.0] - 2026-04-05
|
|
1115
1158
|
|
|
1116
1159
|
### Added
|
|
1117
1160
|
|
|
1118
|
-
- **MCP:** Reduce token usage with filtering, body file saving and pagination <!--
|
|
1161
|
+
- **MCP:** Reduce token usage with filtering, body file saving and pagination <!-- fa67dc8 -->
|
|
1119
1162
|
|
|
1120
1163
|
## [0.5.0] - 2026-04-04
|
|
1121
1164
|
|
|
1122
1165
|
### Added
|
|
1123
1166
|
|
|
1124
|
-
- **MCP:** Add outbound IDs, a domain filter, datetimes, a latest token, exceptions and copy helpers <!--
|
|
1167
|
+
- **MCP:** Add outbound IDs, a domain filter, datetimes, a latest token, exceptions and copy helpers <!-- b36bc24 -->
|
|
1125
1168
|
|
|
1126
1169
|
## [0.4.0] - 2026-04-03
|
|
1127
1170
|
|
|
1128
1171
|
### Added
|
|
1129
1172
|
|
|
1130
|
-
- **Collector:** Add an I18n panel tracking translation lookups per request <!--
|
|
1173
|
+
- **Collector:** Add an I18n panel tracking translation lookups per request <!-- ef4a672 -->
|
|
1131
1174
|
|
|
1132
1175
|
<!-- excluded:
|
|
1133
|
-
|
|
1176
|
+
ad4fea1 pipeline only
|
|
1134
1177
|
-->
|
|
1135
1178
|
|
|
1136
1179
|
## [0.3.0] - 2026-04-02
|
|
1137
1180
|
|
|
1138
1181
|
### Added
|
|
1139
1182
|
|
|
1140
|
-
- **Collector:** Add a routes panel and enrich the request collector <!--
|
|
1183
|
+
- **Collector:** Add a routes panel and enrich the request collector <!-- febed69 -->
|
|
1141
1184
|
|
|
1142
1185
|
## [0.2.0] - 2026-03-29
|
|
1143
1186
|
|
|
1144
1187
|
### Added
|
|
1145
1188
|
|
|
1146
|
-
- **Collector:** Add a Logs panel, capturing `Rails.logger` messages per request <!--
|
|
1147
|
-
- Publish the gem to RubyGems.org <!--
|
|
1189
|
+
- **Collector:** Add a Logs panel, capturing `Rails.logger` messages per request <!-- 3f1d906 -->
|
|
1190
|
+
- Publish the gem to RubyGems.org <!-- a63e6b4 -->
|
|
1148
1191
|
|
|
1149
1192
|
<!-- excluded:
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1193
|
+
17feea9 pipeline only, despite the fix: type
|
|
1194
|
+
86fcb3f pipeline only
|
|
1195
|
+
5891bfa pipeline only
|
|
1196
|
+
81150a3 pipeline only
|
|
1154
1197
|
-->
|
|
1155
1198
|
|
|
1156
1199
|
## [0.1.4] - 2026-03-29
|
|
1157
1200
|
|
|
1158
1201
|
### Added
|
|
1159
1202
|
|
|
1160
|
-
- **Profiler:** Capture the full request and response, and add an MCP history clear <!--
|
|
1203
|
+
- **Profiler:** Capture the full request and response, and add an MCP history clear <!-- 7d54262 -->
|
|
1161
1204
|
|
|
1162
1205
|
## [0.1.3] - 2026-03-29
|
|
1163
1206
|
|
|
1164
1207
|
### Changed
|
|
1165
1208
|
|
|
1166
|
-
- **Assets:** Migrate Sass `@import` to `@use`, which removes the build deprecation warnings <!--
|
|
1209
|
+
- **Assets:** Migrate Sass `@import` to `@use`, which removes the build deprecation warnings <!-- a0ac9b5 -->
|
|
1167
1210
|
|
|
1168
1211
|
## [0.1.2] - 2026-03-25
|
|
1169
1212
|
|
|
@@ -1204,7 +1247,7 @@ carries no change to the gem, so there is nothing to report for this version.
|
|
|
1204
1247
|
- **Profiler:** Improve memory tracking and profile deserialisation <!-- a0af47e -->
|
|
1205
1248
|
|
|
1206
1249
|
<!-- excluded:
|
|
1207
|
-
|
|
1250
|
+
d797be5 pipeline only
|
|
1208
1251
|
02d63c9 pipeline only
|
|
1209
1252
|
9f598d9 subject is not conventional, tooling only
|
|
1210
1253
|
7d1d823 tests only
|
|
@@ -3723,7 +3723,23 @@
|
|
|
3723
3723
|
}
|
|
3724
3724
|
|
|
3725
3725
|
// app/assets/typescript/profiler/components/toolbar/panels/JobsPanel.tsx
|
|
3726
|
+
function JobStatus({ job }) {
|
|
3727
|
+
if (job.status === "running") {
|
|
3728
|
+
return /* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono profiler-text--info", children: [
|
|
3729
|
+
/* @__PURE__ */ u3("span", { class: "profiler-spinner", "aria-hidden": "true", children: "\u25D0" }),
|
|
3730
|
+
" running"
|
|
3731
|
+
] });
|
|
3732
|
+
}
|
|
3733
|
+
const failed = job.status === "failed";
|
|
3734
|
+
return /* @__PURE__ */ u3("strong", { class: `profiler-text--xs profiler-text--mono ${failed ? "profiler-text--error" : "profiler-text--success"}`, children: [
|
|
3735
|
+
failed ? "\u2717" : "\u2713",
|
|
3736
|
+
" ",
|
|
3737
|
+
job.duration?.toFixed(0),
|
|
3738
|
+
"ms"
|
|
3739
|
+
] });
|
|
3740
|
+
}
|
|
3726
3741
|
function JobsPanel({ jobs }) {
|
|
3742
|
+
const running = jobs.filter((j3) => j3.status === "running").length;
|
|
3727
3743
|
const completed = jobs.filter((j3) => j3.status === "completed").length;
|
|
3728
3744
|
const failed = jobs.filter((j3) => j3.status === "failed").length;
|
|
3729
3745
|
const preview = jobs.slice(0, 5);
|
|
@@ -3739,9 +3755,13 @@
|
|
|
3739
3755
|
] }),
|
|
3740
3756
|
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-content", children: [
|
|
3741
3757
|
/* @__PURE__ */ u3("div", { class: "profiler-section__header", children: "Summary" }),
|
|
3758
|
+
running > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
3759
|
+
/* @__PURE__ */ u3("span", { children: "Running" }),
|
|
3760
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--info", children: running })
|
|
3761
|
+
] }),
|
|
3742
3762
|
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
3743
3763
|
/* @__PURE__ */ u3("span", { children: "Completed" }),
|
|
3744
|
-
/* @__PURE__ */ u3("strong", { class: "profiler-text--success", children: completed })
|
|
3764
|
+
/* @__PURE__ */ u3("strong", { class: completed > 0 ? "profiler-text--success" : "profiler-text--muted", children: completed })
|
|
3745
3765
|
] }),
|
|
3746
3766
|
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
3747
3767
|
/* @__PURE__ */ u3("span", { children: "Failed" }),
|
|
@@ -3750,12 +3770,7 @@
|
|
|
3750
3770
|
/* @__PURE__ */ u3("div", { class: "profiler-section__header profiler-mt-3", children: "Last jobs" }),
|
|
3751
3771
|
preview.map((job, i3) => /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
3752
3772
|
/* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: job.job_class }),
|
|
3753
|
-
/* @__PURE__ */ u3(
|
|
3754
|
-
job.status === "failed" ? "\u2717" : "\u2713",
|
|
3755
|
-
" ",
|
|
3756
|
-
job.duration?.toFixed(0),
|
|
3757
|
-
"ms"
|
|
3758
|
-
] })
|
|
3773
|
+
/* @__PURE__ */ u3(JobStatus, { job })
|
|
3759
3774
|
] }, i3)),
|
|
3760
3775
|
remaining > 0 && /* @__PURE__ */ u3("div", { class: "profiler-more", children: [
|
|
3761
3776
|
"+ ",
|
|
@@ -4102,13 +4117,14 @@
|
|
|
4102
4117
|
ToolbarItem,
|
|
4103
4118
|
{
|
|
4104
4119
|
href: `/_profiler/profiles/${token}?tab=jobs`,
|
|
4105
|
-
className: childJobs.some((j3) => j3.status === "failed") ? "profiler-text--error" : "profiler-text--success",
|
|
4120
|
+
className: childJobs.some((j3) => j3.status === "failed") ? "profiler-text--error" : childJobs.some((j3) => j3.status === "running") ? "profiler-text--info" : "profiler-text--success",
|
|
4106
4121
|
panelLarge: true,
|
|
4107
4122
|
panel: /* @__PURE__ */ u3(JobsPanel, { jobs: childJobs }),
|
|
4108
4123
|
children: [
|
|
4109
4124
|
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "JOB" }),
|
|
4110
4125
|
/* @__PURE__ */ u3("span", { children: childJobs.length }),
|
|
4111
|
-
childJobs.some((j3) => j3.status === "failed") && /* @__PURE__ */ u3("span", { class: "profiler-text--error profiler-text--xs", children: "\u2717" })
|
|
4126
|
+
childJobs.some((j3) => j3.status === "failed") && /* @__PURE__ */ u3("span", { class: "profiler-text--error profiler-text--xs", children: "\u2717" }),
|
|
4127
|
+
childJobs.some((j3) => j3.status === "running") && /* @__PURE__ */ u3("span", { class: "profiler-spinner profiler-text--info profiler-text--xs", "aria-hidden": "true", children: "\u25D0" })
|
|
4112
4128
|
]
|
|
4113
4129
|
}
|
|
4114
4130
|
),
|
|
@@ -606,6 +606,11 @@ tr:hover .btn-row-delete {
|
|
|
606
606
|
transform: rotate(360deg);
|
|
607
607
|
}
|
|
608
608
|
}
|
|
609
|
+
.profiler-spinner {
|
|
610
|
+
display: inline-block;
|
|
611
|
+
animation: spin 1s linear infinite;
|
|
612
|
+
}
|
|
613
|
+
|
|
609
614
|
@keyframes pulse {
|
|
610
615
|
0%, 100% {
|
|
611
616
|
opacity: 1;
|
|
@@ -1558,6 +1563,10 @@ a.profiler-toolbar-item {
|
|
|
1558
1563
|
color: var(--profiler-error) !important;
|
|
1559
1564
|
}
|
|
1560
1565
|
|
|
1566
|
+
.profiler-text--info {
|
|
1567
|
+
color: var(--profiler-info) !important;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1561
1570
|
.profiler-text--muted {
|
|
1562
1571
|
color: var(--profiler-text-muted) !important;
|
|
1563
1572
|
}
|
|
@@ -1578,6 +1587,10 @@ a.profiler-toolbar-item {
|
|
|
1578
1587
|
background: var(--profiler-success);
|
|
1579
1588
|
}
|
|
1580
1589
|
|
|
1590
|
+
.profiler-toolbar-item.profiler-text--info::after {
|
|
1591
|
+
background: var(--profiler-info);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1581
1594
|
.profiler-toolbar-hoverable {
|
|
1582
1595
|
position: relative;
|
|
1583
1596
|
}
|
|
@@ -5569,6 +5569,11 @@
|
|
|
5569
5569
|
if (duration >= 100) return "badge-warning";
|
|
5570
5570
|
return "badge-success";
|
|
5571
5571
|
}
|
|
5572
|
+
function jobStatusOf(p3) {
|
|
5573
|
+
const status = p3.collectors_data?.job?.status;
|
|
5574
|
+
if (status) return status;
|
|
5575
|
+
return p3.status === 500 ? "failed" : "completed";
|
|
5576
|
+
}
|
|
5572
5577
|
function formatTime(iso) {
|
|
5573
5578
|
return new Date(iso).toLocaleTimeString("en", { hour12: false });
|
|
5574
5579
|
}
|
|
@@ -5827,8 +5832,7 @@
|
|
|
5827
5832
|
}) : filteredProfiles;
|
|
5828
5833
|
const filteredJobs = jobs.filter((p3) => {
|
|
5829
5834
|
if (jobSearch && !p3.path.toLowerCase().includes(jobSearch.toLowerCase())) return false;
|
|
5830
|
-
if (jobStatus
|
|
5831
|
-
if (jobStatus === "completed" && p3.status === 500) return false;
|
|
5835
|
+
if (jobStatus && jobStatusOf(p3) !== jobStatus) return false;
|
|
5832
5836
|
if (jobDuration) {
|
|
5833
5837
|
if (jobDuration === "lt100" ? p3.duration >= 100 : p3.duration < parseInt(jobDuration)) return false;
|
|
5834
5838
|
}
|
|
@@ -6104,6 +6108,7 @@
|
|
|
6104
6108
|
),
|
|
6105
6109
|
/* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: jobStatus, onChange: (e3) => setJobStatus(e3.target.value), children: [
|
|
6106
6110
|
/* @__PURE__ */ u3("option", { value: "", children: "All Statuses" }),
|
|
6111
|
+
/* @__PURE__ */ u3("option", { value: "running", children: "Running" }),
|
|
6107
6112
|
/* @__PURE__ */ u3("option", { value: "completed", children: "Completed" }),
|
|
6108
6113
|
/* @__PURE__ */ u3("option", { value: "failed", children: "Failed" })
|
|
6109
6114
|
] }),
|
|
@@ -6147,7 +6152,7 @@
|
|
|
6147
6152
|
] }) }),
|
|
6148
6153
|
/* @__PURE__ */ u3("tbody", { children: sortedJobs.map((p3) => {
|
|
6149
6154
|
const jobData = p3.collectors_data?.job;
|
|
6150
|
-
const
|
|
6155
|
+
const status = jobStatusOf(p3);
|
|
6151
6156
|
return /* @__PURE__ */ u3("tr", { children: [
|
|
6152
6157
|
/* @__PURE__ */ u3("td", { children: formatTime(p3.started_at) }),
|
|
6153
6158
|
/* @__PURE__ */ u3("td", { children: [
|
|
@@ -6155,11 +6160,14 @@
|
|
|
6155
6160
|
p3.gem_version && p3.gem_version !== currentVersion && /* @__PURE__ */ u3("span", { class: "profiler-version-warn", title: `Captur\xE9 avec v${p3.gem_version} (actuel : v${currentVersion})`, children: "\u26A0\uFE0F" })
|
|
6156
6161
|
] }),
|
|
6157
6162
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: jobData?.queue || "-" }) }),
|
|
6158
|
-
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: durationClass(p3.duration), children: [
|
|
6163
|
+
/* @__PURE__ */ u3("td", { children: status === "running" ? /* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: "-" }) : /* @__PURE__ */ u3("span", { class: durationClass(p3.duration), children: [
|
|
6159
6164
|
p3.duration.toFixed(2),
|
|
6160
6165
|
" ms"
|
|
6161
6166
|
] }) }),
|
|
6162
|
-
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class:
|
|
6167
|
+
/* @__PURE__ */ u3("td", { children: status === "running" ? /* @__PURE__ */ u3("span", { class: "badge-info", children: [
|
|
6168
|
+
/* @__PURE__ */ u3("span", { class: "profiler-spinner", "aria-hidden": "true", children: "\u25D0" }),
|
|
6169
|
+
" Running"
|
|
6170
|
+
] }) : /* @__PURE__ */ u3("span", { class: status === "failed" ? "badge-error" : "badge-success", children: status === "failed" ? "\u2717 Failed" : "\u2713 Completed" }) }),
|
|
6163
6171
|
/* @__PURE__ */ u3("td", { children: jobData?.executions ?? "-" }),
|
|
6164
6172
|
/* @__PURE__ */ u3("td", { class: "profiler-text--xs profiler-text--mono profiler-text--muted", children: /* @__PURE__ */ u3("button", { class: "token-copy", onClick: () => copyToken(p3.token), title: "Copy full token", children: copiedToken === p3.token ? "\u2713" : p3.token.substring(0, 8) + "\u2026" }) }),
|
|
6165
6173
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("button", { class: "btn-row-delete", onClick: () => deleteJob2(p3.token), title: "Delete", children: "\xD7" }) })
|
|
@@ -8631,10 +8639,10 @@
|
|
|
8631
8639
|
jobs.map((job, index) => /* @__PURE__ */ u3("div", { class: `profiler-ajax-card profiler-ajax-card--${job.status === "completed" ? "success" : job.status === "failed" ? "error" : "default"}`, children: [
|
|
8632
8640
|
/* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
|
|
8633
8641
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-3", children: [
|
|
8634
|
-
/* @__PURE__ */ u3("span", { class: `badge-${job.status === "completed" ? "success" : job.status === "failed" ? "error" : "warning"}`, children: job.status ?? "unknown" }),
|
|
8642
|
+
/* @__PURE__ */ u3("span", { class: `badge-${job.status === "completed" ? "success" : job.status === "failed" ? "error" : job.status === "running" ? "info" : "warning"}`, children: job.status ?? "unknown" }),
|
|
8635
8643
|
/* @__PURE__ */ u3("strong", { class: "profiler-ajax-card__path", children: job.job_class })
|
|
8636
8644
|
] }),
|
|
8637
|
-
/* @__PURE__ */ u3("span", { class: job.duration >= 1e3 ? "badge-error" : job.duration >= 200 ? "badge-warning" : "badge-success", children: [
|
|
8645
|
+
job.status === "running" ? /* @__PURE__ */ u3("span", { class: "badge-info", children: "running" }) : /* @__PURE__ */ u3("span", { class: job.duration >= 1e3 ? "badge-error" : job.duration >= 200 ? "badge-warning" : "badge-success", children: [
|
|
8638
8646
|
job.duration?.toFixed(2),
|
|
8639
8647
|
" ms"
|
|
8640
8648
|
] })
|
|
@@ -9063,6 +9071,7 @@
|
|
|
9063
9071
|
const [activeTab, setActiveTab] = d2(hasException ? "exception" : defaultTab);
|
|
9064
9072
|
const jobData = cd["job"];
|
|
9065
9073
|
const isFailed = jobData?.status === "failed";
|
|
9074
|
+
const isRunning = jobData?.status === "running";
|
|
9066
9075
|
const parent = profile.parent_profile;
|
|
9067
9076
|
const handleTabClick = (tab) => (e3) => {
|
|
9068
9077
|
e3.preventDefault();
|
|
@@ -9082,14 +9091,11 @@
|
|
|
9082
9091
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mt-2", children: [
|
|
9083
9092
|
/* @__PURE__ */ u3("span", { children: [
|
|
9084
9093
|
"Duration: ",
|
|
9085
|
-
/* @__PURE__ */ u3("strong", { children:
|
|
9086
|
-
profile.duration.toFixed(2),
|
|
9087
|
-
" ms"
|
|
9088
|
-
] })
|
|
9094
|
+
/* @__PURE__ */ u3("strong", { children: isRunning ? "running" : `${profile.duration.toFixed(2)} ms` })
|
|
9089
9095
|
] }),
|
|
9090
9096
|
/* @__PURE__ */ u3("span", { children: [
|
|
9091
9097
|
"Status: ",
|
|
9092
|
-
/* @__PURE__ */ u3("strong", { children: /* @__PURE__ */ u3("span", { class: `badge-${isFailed ? "error" : "success"}`, children: isFailed ? "Failed" : "Completed" }) })
|
|
9098
|
+
/* @__PURE__ */ u3("strong", { children: /* @__PURE__ */ u3("span", { class: `badge-${isFailed ? "error" : isRunning ? "info" : "success"}`, children: isFailed ? "Failed" : isRunning ? "Running" : "Completed" }) })
|
|
9093
9099
|
] }),
|
|
9094
9100
|
allocatedObjects(profile) != null && /* @__PURE__ */ u3("span", { title: ALLOCATIONS_HINT, children: [
|
|
9095
9101
|
"Allocations: ",
|
|
@@ -72,6 +72,7 @@ module Profiler
|
|
|
72
72
|
return block.call unless Collectors::Lifecycle.subscribe_all(collectors, "JobProfiler")
|
|
73
73
|
|
|
74
74
|
exception_collector = collectors.find { |c| c.is_a?(Collectors::ExceptionCollector) }
|
|
75
|
+
save_running(profile, job_collector) if @parent_token
|
|
75
76
|
|
|
76
77
|
allocations_before = AllocationCounter.current if Profiler.configuration.track_memory
|
|
77
78
|
|
|
@@ -118,6 +119,16 @@ module Profiler
|
|
|
118
119
|
|
|
119
120
|
private
|
|
120
121
|
|
|
122
|
+
# A job enqueued by a request runs after that request's response: saved now, running, the
|
|
123
|
+
# toolbar of the request lists it before it ends. The save at the end replaces it.
|
|
124
|
+
def save_running(profile, job_collector)
|
|
125
|
+
job_collector.collect
|
|
126
|
+
profile.duration = 0.0
|
|
127
|
+
Profiler.save_profile(profile, from: "JobProfiler")
|
|
128
|
+
rescue StandardError => e
|
|
129
|
+
Profiler.log_error("JobProfiler: could not record job #{@job_class} as running", e)
|
|
130
|
+
end
|
|
131
|
+
|
|
121
132
|
def sanitize_arguments(args)
|
|
122
133
|
return [] unless args
|
|
123
134
|
|
data/lib/profiler/mcp/server.rb
CHANGED
|
@@ -230,7 +230,7 @@ module Profiler
|
|
|
230
230
|
input_schema: {
|
|
231
231
|
properties: {
|
|
232
232
|
queue: { type: "string", description: "Filter by queue name" },
|
|
233
|
-
status: { type: "string", description: "Filter by status (completed, failed)" },
|
|
233
|
+
status: { type: "string", description: "Filter by status (running, completed, failed). A job enqueued by a request is listed as running while it performs." },
|
|
234
234
|
limit: { type: "number", description: "Maximum number of results (default 20)" },
|
|
235
235
|
fields: { type: "array", items: { type: "string" }, description: "Columns to include. Valid values: time, job_class, queue, status, duration, token. Omit for all." },
|
|
236
236
|
cursor: { type: "string", description: "Pagination cursor: ISO8601 timestamp of the last item seen. Returns jobs older than this." },
|
|
@@ -233,6 +233,8 @@ module Profiler
|
|
|
233
233
|
from { transform: translateY(100%); opacity: 0; }
|
|
234
234
|
to { transform: translateY(0); opacity: 1; }
|
|
235
235
|
}
|
|
236
|
+
@keyframes pfSpin { to { transform: rotate(360deg); } }
|
|
237
|
+
#profiler-toolbar .profiler-spinner { display: inline-block; animation: pfSpin 1s linear infinite; }
|
|
236
238
|
@keyframes pfPop {
|
|
237
239
|
0% { transform: scaleX(1); }
|
|
238
240
|
15% { transform: scaleX(1.18); }
|
|
@@ -303,11 +305,13 @@ module Profiler
|
|
|
303
305
|
#profiler-toolbar .profiler-text--error::after { background: var(--pf-error); }
|
|
304
306
|
#profiler-toolbar .profiler-text--warning::after { background: var(--pf-warning); }
|
|
305
307
|
#profiler-toolbar .profiler-text--success::after { background: var(--pf-success); }
|
|
308
|
+
#profiler-toolbar .profiler-text--info::after { background: var(--pf-info); }
|
|
306
309
|
|
|
307
310
|
/* Semantic colors */
|
|
308
311
|
#profiler-toolbar .profiler-text--success { color: var(--pf-success) !important; }
|
|
309
312
|
#profiler-toolbar .profiler-text--warning { color: var(--pf-warning) !important; }
|
|
310
313
|
#profiler-toolbar .profiler-text--error { color: var(--pf-error) !important; }
|
|
314
|
+
#profiler-toolbar .profiler-text--info { color: var(--pf-info) !important; }
|
|
311
315
|
#profiler-toolbar .profiler-text--accent { color: var(--pf-amber) !important; }
|
|
312
316
|
#profiler-toolbar .profiler-text--muted { color: var(--pf-muted) !important; }
|
|
313
317
|
|
|
@@ -6,13 +6,15 @@ module Profiler
|
|
|
6
6
|
module Storage
|
|
7
7
|
class BaseStore
|
|
8
8
|
# Public interface: persists the profile then records the save, for the pages showing it
|
|
9
|
-
# to see it changed (Profiler::SSE).
|
|
9
|
+
# to see it changed (Profiler::SSE). A job's save is also recorded for the profile that
|
|
10
|
+
# enqueued it: the toolbar of that request lists its jobs, and has to see them start and end.
|
|
10
11
|
# Subclasses implement #do_save, not #save. The token must be one the gem issued
|
|
11
12
|
# (Storage::Token); every other public method answers "not found" for any other token.
|
|
12
13
|
def save(token, profile)
|
|
13
14
|
Token.validate!(token)
|
|
14
15
|
result = do_save(token, profile)
|
|
15
16
|
broadcast_event(token)
|
|
17
|
+
broadcast_event(profile.parent_token) if job_with_parent?(profile)
|
|
16
18
|
result
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -53,6 +55,11 @@ module Profiler
|
|
|
53
55
|
|
|
54
56
|
private
|
|
55
57
|
|
|
58
|
+
# Not for an AJAX request linked to its page: the toolbar reads those on its own links.
|
|
59
|
+
def job_with_parent?(profile)
|
|
60
|
+
profile.profile_type == "job" && Token.valid?(profile.parent_token)
|
|
61
|
+
end
|
|
62
|
+
|
|
56
63
|
def broadcast_event(token)
|
|
57
64
|
Profiler::SSE.current.broadcast(token)
|
|
58
65
|
rescue StandardError => e
|
|
@@ -24,14 +24,32 @@ module Profiler
|
|
|
24
24
|
|
|
25
25
|
module RunWrapper
|
|
26
26
|
def run
|
|
27
|
+
file, line = profiler_test_location
|
|
27
28
|
Profiler::TestProfiler.profile(
|
|
28
29
|
test_name: "#{self.class.name}##{name}",
|
|
29
|
-
test_file:
|
|
30
|
-
test_line:
|
|
30
|
+
test_file: file,
|
|
31
|
+
test_line: line,
|
|
31
32
|
framework: :minitest
|
|
32
33
|
) { super }
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
# Test#run captures what the test raises, so this comes from the
|
|
36
|
+
# profiler. Escaping, it would stop the whole run: it fails this
|
|
37
|
+
# test instead, as it does an RSpec example, without running it again.
|
|
38
|
+
self.time ||= 0
|
|
39
|
+
capture_exceptions { raise e }
|
|
40
|
+
Minitest::Result.from(self)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Only the lookup is guarded: rescuing around super would run the test
|
|
46
|
+
# again whenever anything raised a NameError after it, NoMethodError
|
|
47
|
+
# included.
|
|
48
|
+
def profiler_test_location
|
|
49
|
+
location = self.class.instance_method(name).source_location
|
|
50
|
+
[location&.first || "", location&.last || 0]
|
|
33
51
|
rescue NameError
|
|
34
|
-
|
|
52
|
+
["", 0]
|
|
35
53
|
end
|
|
36
54
|
end
|
|
37
55
|
end
|
|
@@ -15,19 +15,70 @@ module Profiler
|
|
|
15
15
|
# end
|
|
16
16
|
module RSpecSupport
|
|
17
17
|
def self.install(config)
|
|
18
|
-
|
|
18
|
+
hook = proc do |example|
|
|
19
19
|
Profiler::TestProfiler.profile(
|
|
20
20
|
test_name: example.full_description,
|
|
21
21
|
test_file: example.file_path,
|
|
22
|
-
test_line: example.line_number,
|
|
22
|
+
test_line: example.metadata[:line_number],
|
|
23
23
|
framework: :rspec
|
|
24
|
-
)
|
|
24
|
+
) do
|
|
25
|
+
example.run
|
|
26
|
+
Outcome.new(example)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# config.around would nest this hook inside the config-level around
|
|
31
|
+
# hooks registered before it, among them the one RSpec registers for
|
|
32
|
+
# :aggregate_failures when the configuration is created. An aggregated
|
|
33
|
+
# failure, like one raised by such a hook after the example, is only
|
|
34
|
+
# set on the example once that hook has returned: the example would be
|
|
35
|
+
# recorded as passed. Appended, the hook wraps them all. Groups already
|
|
36
|
+
# defined get it the way config.around gives it to them. config.hooks
|
|
37
|
+
# is not public API, hence the fallback.
|
|
38
|
+
if config.respond_to?(:hooks) && config.hooks.respond_to?(:register)
|
|
39
|
+
config.hooks.register(:append, :around, :example, &hook)
|
|
40
|
+
RSpec.world.example_groups.each do |group|
|
|
41
|
+
group.hooks.register(:append, :around, :example, &hook)
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
config.around(:example, &hook)
|
|
25
45
|
end
|
|
26
46
|
|
|
27
47
|
config.after(:suite) do
|
|
28
48
|
Profiler::TestHelpers::Reporter.print
|
|
29
49
|
end
|
|
30
50
|
end
|
|
51
|
+
|
|
52
|
+
# What the profiled block hands back to TestProfiler: the same interface as
|
|
53
|
+
# Minitest::Result (passed?, skipped?, failure).
|
|
54
|
+
#
|
|
55
|
+
# Procsy#run returns a boolean, and execution_result.status is still nil at
|
|
56
|
+
# this point: Example#finish only writes it once every around hook has
|
|
57
|
+
# returned. The outcome is read from the state #finish relies on, in the
|
|
58
|
+
# same order: an exception means failed, then a pending message (from
|
|
59
|
+
# `pending` or `skip` in the example) means pending.
|
|
60
|
+
class Outcome
|
|
61
|
+
Reason = Struct.new(:message)
|
|
62
|
+
|
|
63
|
+
def initialize(example)
|
|
64
|
+
@exception = example.exception
|
|
65
|
+
@pending_message = example.execution_result.pending_message
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def passed?
|
|
69
|
+
@exception.nil? && @pending_message.nil?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def skipped?
|
|
73
|
+
@exception.nil? && !@pending_message.nil?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def failure
|
|
77
|
+
return @exception if @exception
|
|
78
|
+
|
|
79
|
+
Reason.new(@pending_message) if @pending_message
|
|
80
|
+
end
|
|
81
|
+
end
|
|
31
82
|
end
|
|
32
83
|
end
|
|
33
84
|
end
|
|
@@ -67,8 +67,10 @@ module Profiler
|
|
|
67
67
|
begin
|
|
68
68
|
result = block.call
|
|
69
69
|
|
|
70
|
-
#
|
|
71
|
-
#
|
|
70
|
+
# Test frameworks catch failures themselves, so they never reach the
|
|
71
|
+
# rescue below. The block reports them by returning an object that
|
|
72
|
+
# responds to passed?, skipped? and failure: Minitest::Test#run returns
|
|
73
|
+
# a Minitest::Result, and RSpecSupport builds an equivalent outcome.
|
|
72
74
|
if result.respond_to?(:passed?)
|
|
73
75
|
unless result.passed?
|
|
74
76
|
test_status = result.skipped? ? "pending" : "failed"
|
|
@@ -81,17 +83,6 @@ module Profiler
|
|
|
81
83
|
)
|
|
82
84
|
end
|
|
83
85
|
|
|
84
|
-
# RSpec: example.run catches errors internally and stores them in execution_result.
|
|
85
|
-
if result.respond_to?(:execution_result)
|
|
86
|
-
er = result.execution_result
|
|
87
|
-
rspec_status = er.status&.to_s
|
|
88
|
-
if rspec_status && rspec_status != "passed" && test_status == "passed"
|
|
89
|
-
test_status = rspec_status
|
|
90
|
-
msg = er.exception&.message.to_s
|
|
91
|
-
error_message = msg.empty? ? nil : msg
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
86
|
result
|
|
96
87
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
97
88
|
# Capture all exceptions (including test failures which may subclass Exception)
|
data/lib/profiler/version.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.32.
|
|
4
|
+
version: 0.32.3
|
|
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-10-
|
|
11
|
+
date: 2026-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|