rails-profiler 0.32.2 → 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 +25 -0
- 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/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,31 @@ 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
|
+
|
|
22
47
|
## [0.32.2] - 2026-10-13
|
|
23
48
|
|
|
24
49
|
<!-- stamped -->
|
|
@@ -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
|
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
|