rails-profiler 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/profiler-toolbar.js +18 -2
- data/app/assets/builds/profiler.css +28 -0
- data/app/assets/builds/profiler.js +40 -4
- data/app/views/layouts/profiler/application.html.erb +1 -0
- data/app/views/layouts/profiler/embedded.html.erb +1 -0
- data/lib/profiler/instrumentation/net_http_instrumentation.rb +20 -7
- data/lib/profiler/job_profiler.rb +1 -0
- data/lib/profiler/mcp/tools/get_profile_detail.rb +40 -4
- data/lib/profiler/mcp/tools/query_jobs.rb +4 -1
- data/lib/profiler/middleware/profiler_middleware.rb +1 -0
- data/lib/profiler/middleware/toolbar_injector.rb +1 -1
- data/lib/profiler/models/profile.rb +6 -7
- data/lib/profiler/storage/sqlite_store.rb +11 -2
- 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: 1f2b977ebc11075e9d00ae802a9d8f038819907c18b17073cf6b3a0fa437ba29
|
|
4
|
+
data.tar.gz: 4565ab7a98d1a8992cfc3bde0646c6dea355b64d79ab66849c99cab2fff024a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48c0c092e6d9145ed04651fbc3e924fa7f3e02d79da737cc6a052e0d9d7b41c590651c086a941d76a983eef29358ee0817b1fd078c41d0a7edd750a948a191f2
|
|
7
|
+
data.tar.gz: 7de21cc82a590ff838ed5130970e6947c69e4910ff2ebb5c93431f4d4deba93389cdf446e370fe50a9fce7097526b16ffcd442a18bea82ef29716b0638746028
|
|
@@ -1027,7 +1027,7 @@
|
|
|
1027
1027
|
second: "2-digit"
|
|
1028
1028
|
});
|
|
1029
1029
|
}
|
|
1030
|
-
function ToolbarApp({ profile, token }) {
|
|
1030
|
+
function ToolbarApp({ profile, token, currentVersion }) {
|
|
1031
1031
|
const cd = profile.collectors_data || {};
|
|
1032
1032
|
const requestData = cd["request"];
|
|
1033
1033
|
const dbData = cd["database"];
|
|
@@ -1291,6 +1291,21 @@
|
|
|
1291
1291
|
]
|
|
1292
1292
|
}
|
|
1293
1293
|
),
|
|
1294
|
+
profile.gem_version && profile.gem_version !== currentVersion ? /* @__PURE__ */ u3(
|
|
1295
|
+
"a",
|
|
1296
|
+
{
|
|
1297
|
+
href: `/_profiler/profiles/${token}`,
|
|
1298
|
+
class: "profiler-toolbar-item profiler-text--warning",
|
|
1299
|
+
title: `Captur\xE9 avec v${profile.gem_version} \u2014 actuel : v${currentVersion}`,
|
|
1300
|
+
children: [
|
|
1301
|
+
"\u26A0\uFE0F v",
|
|
1302
|
+
profile.gem_version
|
|
1303
|
+
]
|
|
1304
|
+
}
|
|
1305
|
+
) : currentVersion ? /* @__PURE__ */ u3("span", { class: "profiler-toolbar-item", style: "cursor:default", children: [
|
|
1306
|
+
"v",
|
|
1307
|
+
currentVersion
|
|
1308
|
+
] }) : null,
|
|
1294
1309
|
/* @__PURE__ */ u3("a", { href: "/_profiler", class: "profiler-toolbar-item", children: "\u2B21 Profiler" })
|
|
1295
1310
|
] });
|
|
1296
1311
|
}
|
|
@@ -1306,6 +1321,7 @@
|
|
|
1306
1321
|
if (!el) return;
|
|
1307
1322
|
const token = el.dataset.token;
|
|
1308
1323
|
if (!token) return;
|
|
1324
|
+
const currentVersion = el.dataset.version ?? "";
|
|
1309
1325
|
applyTheme(el);
|
|
1310
1326
|
window.addEventListener("storage", (e3) => {
|
|
1311
1327
|
if (e3.key === "profiler-theme") applyTheme(el);
|
|
@@ -1314,7 +1330,7 @@
|
|
|
1314
1330
|
if (e3.detail?.theme) el.setAttribute("data-theme", e3.detail.theme);
|
|
1315
1331
|
}));
|
|
1316
1332
|
const renderToolbar = (profile) => {
|
|
1317
|
-
J(/* @__PURE__ */ u3(ToolbarApp, { profile, token }), el);
|
|
1333
|
+
J(/* @__PURE__ */ u3(ToolbarApp, { profile, token, currentVersion }), el);
|
|
1318
1334
|
applyTheme(el);
|
|
1319
1335
|
};
|
|
1320
1336
|
const loadAndRender = () => {
|
|
@@ -521,6 +521,34 @@ tr:hover .btn-row-delete {
|
|
|
521
521
|
border: 1px solid var(--profiler-border);
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
.profiler-version-badge {
|
|
525
|
+
display: inline-flex;
|
|
526
|
+
align-items: center;
|
|
527
|
+
padding: 1px 6px;
|
|
528
|
+
border-radius: var(--profiler-radius-sm);
|
|
529
|
+
font-family: var(--profiler-font-mono);
|
|
530
|
+
font-size: var(--profiler-text-xs);
|
|
531
|
+
color: var(--profiler-text-muted);
|
|
532
|
+
background: var(--profiler-bg-lighter);
|
|
533
|
+
border: 1px solid var(--profiler-border);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.profiler-version-warn {
|
|
537
|
+
margin-left: 6px;
|
|
538
|
+
cursor: help;
|
|
539
|
+
font-size: var(--profiler-text-xs);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.profiler-version-mismatch {
|
|
543
|
+
margin-top: var(--profiler-space-2);
|
|
544
|
+
padding: var(--profiler-space-2) var(--profiler-space-3);
|
|
545
|
+
border-radius: var(--profiler-radius-sm);
|
|
546
|
+
font-size: var(--profiler-text-sm);
|
|
547
|
+
background: var(--profiler-warning-bg);
|
|
548
|
+
color: var(--profiler-warning);
|
|
549
|
+
border: 1px solid rgba(251, 146, 60, 0.3);
|
|
550
|
+
}
|
|
551
|
+
|
|
524
552
|
@keyframes fadeInDown {
|
|
525
553
|
from {
|
|
526
554
|
opacity: 0;
|
|
@@ -396,6 +396,11 @@
|
|
|
396
396
|
return "function" == typeof t3 ? t3(n2) : t3;
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
// app/assets/typescript/profiler/dashboard/utils.ts
|
|
400
|
+
function getGemVersion() {
|
|
401
|
+
return document.querySelector('meta[name="profiler-version"]')?.content ?? "";
|
|
402
|
+
}
|
|
403
|
+
|
|
399
404
|
// app/assets/typescript/profiler/components/dashboard/tabs/shared/utils.ts
|
|
400
405
|
function methodBadge(method) {
|
|
401
406
|
const map = { GET: "info", POST: "success", PUT: "warning", PATCH: "warning", DELETE: "error" };
|
|
@@ -1635,6 +1640,7 @@
|
|
|
1635
1640
|
{ key: "has_exception", label: "Has exception" }
|
|
1636
1641
|
];
|
|
1637
1642
|
function ProfileList() {
|
|
1643
|
+
const currentVersion = getGemVersion();
|
|
1638
1644
|
const params = new URLSearchParams(window.location.search);
|
|
1639
1645
|
const initialSection = () => {
|
|
1640
1646
|
const s3 = params.get("section");
|
|
@@ -1982,7 +1988,11 @@
|
|
|
1982
1988
|
/* @__PURE__ */ u3("span", { class: "h1-emoji", children: "\u{1F50D}" }),
|
|
1983
1989
|
" Rails Profiler"
|
|
1984
1990
|
] }),
|
|
1985
|
-
/* @__PURE__ */ u3("p", { children: "Recent profiled requests and jobs" })
|
|
1991
|
+
/* @__PURE__ */ u3("p", { children: "Recent profiled requests and jobs" }),
|
|
1992
|
+
currentVersion && /* @__PURE__ */ u3("span", { class: "profiler-version-badge", children: [
|
|
1993
|
+
"v",
|
|
1994
|
+
currentVersion
|
|
1995
|
+
] })
|
|
1986
1996
|
] }),
|
|
1987
1997
|
/* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
|
|
1988
1998
|
/* @__PURE__ */ u3("div", { class: "tabs", children: [
|
|
@@ -2092,7 +2102,10 @@
|
|
|
2092
2102
|
/* @__PURE__ */ u3("tbody", { children: sortedProfiles.map((p3) => /* @__PURE__ */ u3("tr", { children: [
|
|
2093
2103
|
/* @__PURE__ */ u3("td", { children: formatTime(p3.started_at) }),
|
|
2094
2104
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: methodClass(p3.method), children: p3.method }) }),
|
|
2095
|
-
/* @__PURE__ */ u3("td", { children:
|
|
2105
|
+
/* @__PURE__ */ u3("td", { children: [
|
|
2106
|
+
/* @__PURE__ */ u3("a", { href: `${BASE}/profiles/${p3.token}`, children: p3.path }),
|
|
2107
|
+
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" })
|
|
2108
|
+
] }),
|
|
2096
2109
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: durationClass(p3.duration), children: [
|
|
2097
2110
|
p3.duration.toFixed(2),
|
|
2098
2111
|
" ms"
|
|
@@ -2169,7 +2182,10 @@
|
|
|
2169
2182
|
const isFailed = p3.status === 500;
|
|
2170
2183
|
return /* @__PURE__ */ u3("tr", { children: [
|
|
2171
2184
|
/* @__PURE__ */ u3("td", { children: formatTime(p3.started_at) }),
|
|
2172
|
-
/* @__PURE__ */ u3("td", { children:
|
|
2185
|
+
/* @__PURE__ */ u3("td", { children: [
|
|
2186
|
+
/* @__PURE__ */ u3("a", { href: `${BASE}/profiles/${p3.token}`, children: p3.path }),
|
|
2187
|
+
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" })
|
|
2188
|
+
] }),
|
|
2173
2189
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: jobData?.queue || "-" }) }),
|
|
2174
2190
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: durationClass(p3.duration), children: [
|
|
2175
2191
|
p3.duration.toFixed(2),
|
|
@@ -4482,7 +4498,17 @@
|
|
|
4482
4498
|
" MB"
|
|
4483
4499
|
] })
|
|
4484
4500
|
] }),
|
|
4485
|
-
/* @__PURE__ */ u3("span", { style: "color:var(--profiler-text-muted)", children: new Date(profile.started_at).toLocaleString("en", { hour12: false, month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" }) })
|
|
4501
|
+
/* @__PURE__ */ u3("span", { style: "color:var(--profiler-text-muted)", children: new Date(profile.started_at).toLocaleString("en", { hour12: false, month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" }) }),
|
|
4502
|
+
profile.gem_version && /* @__PURE__ */ u3("span", { class: "profiler-version-badge", children: [
|
|
4503
|
+
"v",
|
|
4504
|
+
profile.gem_version
|
|
4505
|
+
] })
|
|
4506
|
+
] }),
|
|
4507
|
+
profile.gem_version && profile.gem_version !== getGemVersion() && /* @__PURE__ */ u3("div", { class: "profiler-version-mismatch", children: [
|
|
4508
|
+
"\u26A0\uFE0F Profil captur\xE9 avec la version ",
|
|
4509
|
+
/* @__PURE__ */ u3("strong", { children: profile.gem_version }),
|
|
4510
|
+
" \u2014 version actuelle : ",
|
|
4511
|
+
/* @__PURE__ */ u3("strong", { children: getGemVersion() })
|
|
4486
4512
|
] })
|
|
4487
4513
|
] }),
|
|
4488
4514
|
/* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
|
|
@@ -4628,8 +4654,18 @@
|
|
|
4628
4654
|
(profile.memory / 1024 / 1024).toFixed(2),
|
|
4629
4655
|
" MB"
|
|
4630
4656
|
] })
|
|
4657
|
+
] }),
|
|
4658
|
+
profile.gem_version && /* @__PURE__ */ u3("span", { class: "profiler-version-badge", children: [
|
|
4659
|
+
"v",
|
|
4660
|
+
profile.gem_version
|
|
4631
4661
|
] })
|
|
4632
4662
|
] }),
|
|
4663
|
+
profile.gem_version && profile.gem_version !== getGemVersion() && /* @__PURE__ */ u3("div", { class: "profiler-version-mismatch", children: [
|
|
4664
|
+
"\u26A0\uFE0F Profil captur\xE9 avec la version ",
|
|
4665
|
+
/* @__PURE__ */ u3("strong", { children: profile.gem_version }),
|
|
4666
|
+
" \u2014 version actuelle : ",
|
|
4667
|
+
/* @__PURE__ */ u3("strong", { children: getGemVersion() })
|
|
4668
|
+
] }),
|
|
4633
4669
|
parent && /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2 profiler-mt-2 profiler-text--sm", children: [
|
|
4634
4670
|
/* @__PURE__ */ u3("span", { style: "color:var(--profiler-text-muted)", children: "Triggered by:" }),
|
|
4635
4671
|
parent.profile_type === "http" ? /* @__PURE__ */ u3("span", { children: [
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<%= csrf_meta_tags %>
|
|
7
7
|
<%= csp_meta_tag %>
|
|
8
|
+
<meta name="profiler-version" content="<%= Profiler::VERSION %>">
|
|
8
9
|
|
|
9
10
|
<link rel="stylesheet" href="/_profiler/assets/profiler.css">
|
|
10
11
|
<script src="/_profiler/assets/profiler.js" defer></script>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<%= csrf_meta_tags %>
|
|
7
7
|
<%= csp_meta_tag %>
|
|
8
|
+
<meta name="profiler-version" content="<%= Profiler::VERSION %>">
|
|
8
9
|
|
|
9
10
|
<link rel="stylesheet" href="/_profiler/assets/profiler.css">
|
|
10
11
|
<script src="/_profiler/assets/profiler.js" defer></script>
|
|
@@ -25,6 +25,24 @@ module Profiler
|
|
|
25
25
|
# Fallback: body may be passed as the 2nd argument and only applied
|
|
26
26
|
# to req inside super via req.set_body_internal(body)
|
|
27
27
|
req_body = body.to_s if req_body.empty? && body
|
|
28
|
+
# Fallback: libraries like RestClient set body_stream instead of body
|
|
29
|
+
# and pass nil as the 2nd argument to Net::HTTP#request.
|
|
30
|
+
# Read the stream content, then restore it so the actual request works.
|
|
31
|
+
# RestClient::Payload doesn't implement #rewind, so we fall back to
|
|
32
|
+
# replacing body_stream with a fresh StringIO.
|
|
33
|
+
if req_body.empty? && req.body_stream
|
|
34
|
+
begin
|
|
35
|
+
stream = req.body_stream
|
|
36
|
+
req_body = stream.read.to_s
|
|
37
|
+
if stream.respond_to?(:rewind)
|
|
38
|
+
stream.rewind
|
|
39
|
+
else
|
|
40
|
+
req.body_stream = StringIO.new(req_body)
|
|
41
|
+
end
|
|
42
|
+
rescue
|
|
43
|
+
req_body = ""
|
|
44
|
+
end
|
|
45
|
+
end
|
|
28
46
|
req_headers = req.to_hash.transform_values { |v| v.join(", ") }
|
|
29
47
|
request_id = SecureRandom.hex(8)
|
|
30
48
|
started_at = Time.now.iso8601(3)
|
|
@@ -105,9 +123,6 @@ module Profiler
|
|
|
105
123
|
|
|
106
124
|
SKIP_HOSTS = %w[127.0.0.1 localhost ::1].freeze
|
|
107
125
|
|
|
108
|
-
TEXT_BODY_LIMIT = 512 * 1024 # 512 KB
|
|
109
|
-
BINARY_BODY_LIMIT = 256 * 1024 # 256 KB (before base64)
|
|
110
|
-
|
|
111
126
|
TEXT_CONTENT_TYPES = /\A(text\/|application\/(json|xml|xhtml|javascript|x-www-form-urlencoded)|image\/svg)/i
|
|
112
127
|
BINARY_CONTENT_TYPES = /\A(image\/|application\/pdf|application\/octet-stream|application\/zip|audio\/|video\/)/i
|
|
113
128
|
|
|
@@ -143,12 +158,10 @@ module Profiler
|
|
|
143
158
|
mime = content_type.split(";").first.to_s.strip
|
|
144
159
|
|
|
145
160
|
if mime.match?(BINARY_CONTENT_TYPES)
|
|
146
|
-
|
|
147
|
-
{ body: Base64.strict_encode64(truncated.b), encoding: "base64" }
|
|
161
|
+
{ body: Base64.strict_encode64(body.b), encoding: "base64" }
|
|
148
162
|
else
|
|
149
|
-
# Text (including unknown content types)
|
|
150
163
|
text = body.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
|
|
151
|
-
{ body: text
|
|
164
|
+
{ body: text, encoding: "text" }
|
|
152
165
|
end
|
|
153
166
|
end
|
|
154
167
|
|
|
@@ -60,7 +60,7 @@ module Profiler
|
|
|
60
60
|
lines += section_views(profile) if want.("views")
|
|
61
61
|
lines += section_cache(profile) if want.("cache")
|
|
62
62
|
lines += section_ajax(profile) if want.("ajax")
|
|
63
|
-
lines += section_http(profile)
|
|
63
|
+
lines += section_http(profile, params) if want.("http")
|
|
64
64
|
lines += section_routes(profile) if want.("routes")
|
|
65
65
|
lines += section_dumps(profile) if want.("dumps")
|
|
66
66
|
lines += section_related_jobs(profile) if want.("related_jobs")
|
|
@@ -76,6 +76,13 @@ module Profiler
|
|
|
76
76
|
lines << "**Duration:** #{profile.duration.round(2)} ms"
|
|
77
77
|
lines << "**Memory:** #{(profile.memory / 1024.0 / 1024.0).round(2)} MB" if profile.memory
|
|
78
78
|
lines << "**Time:** #{profile.started_at}"
|
|
79
|
+
if profile.gem_version
|
|
80
|
+
if profile.gem_version != Profiler::VERSION
|
|
81
|
+
lines << "**Gem Version:** #{profile.gem_version} ⚠️ (current: #{Profiler::VERSION})"
|
|
82
|
+
else
|
|
83
|
+
lines << "**Gem Version:** #{profile.gem_version}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
79
86
|
lines << "**Parent Token:** #{profile.parent_token}" if profile.parent_token
|
|
80
87
|
lines << ""
|
|
81
88
|
lines
|
|
@@ -325,7 +332,7 @@ module Profiler
|
|
|
325
332
|
lines
|
|
326
333
|
end
|
|
327
334
|
|
|
328
|
-
def self.section_http(profile)
|
|
335
|
+
def self.section_http(profile, params = {})
|
|
329
336
|
lines = []
|
|
330
337
|
http_data = profile.collector_data("http")
|
|
331
338
|
return lines unless http_data && http_data["total_requests"].to_i > 0
|
|
@@ -339,10 +346,39 @@ module Profiler
|
|
|
339
346
|
|
|
340
347
|
if http_data["requests"] && !http_data["requests"].empty?
|
|
341
348
|
lines << "### Request List"
|
|
342
|
-
http_data["requests"].
|
|
349
|
+
http_data["requests"].each_with_index do |req, index|
|
|
343
350
|
flag = req["duration"] >= threshold ? " [SLOW]" : ""
|
|
344
351
|
err = req["status"] >= 400 || req["status"] == 0 ? " [ERROR]" : ""
|
|
345
|
-
lines << "
|
|
352
|
+
lines << "\n**#{index + 1}. #{req['method']} #{req['url']}** — #{req['status'] == 0 ? 'error' : req['status']} — #{req['duration'].round(2)} ms#{flag}#{err}"
|
|
353
|
+
|
|
354
|
+
if req["request_body"] && !req["request_body"].empty?
|
|
355
|
+
lines << "**Request Body:**"
|
|
356
|
+
formatted = BodyFormatter.format_body(
|
|
357
|
+
profile.token,
|
|
358
|
+
"http_#{index}_request_body",
|
|
359
|
+
req["request_body"],
|
|
360
|
+
req["request_body_encoding"],
|
|
361
|
+
params
|
|
362
|
+
)
|
|
363
|
+
lines << formatted if formatted
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
if req["response_body"] && !req["response_body"].empty?
|
|
367
|
+
lines << "**Response Body:**"
|
|
368
|
+
formatted = BodyFormatter.format_body(
|
|
369
|
+
profile.token,
|
|
370
|
+
"http_#{index}_response_body",
|
|
371
|
+
req["response_body"],
|
|
372
|
+
req["response_body_encoding"],
|
|
373
|
+
params
|
|
374
|
+
)
|
|
375
|
+
lines << formatted if formatted
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
if req["backtrace"] && !req["backtrace"].empty?
|
|
379
|
+
lines << "_Backtrace:_"
|
|
380
|
+
req["backtrace"].each { |frame| lines << " #{frame}" }
|
|
381
|
+
end
|
|
346
382
|
end
|
|
347
383
|
lines << ""
|
|
348
384
|
end
|
|
@@ -4,7 +4,7 @@ module Profiler
|
|
|
4
4
|
module MCP
|
|
5
5
|
module Tools
|
|
6
6
|
class QueryJobs
|
|
7
|
-
ALL_FIELDS = %w[time job_class queue status duration token parent_token].freeze
|
|
7
|
+
ALL_FIELDS = %w[time job_class queue status duration gem_version token parent_token].freeze
|
|
8
8
|
|
|
9
9
|
def self.call(params)
|
|
10
10
|
limit = params["limit"]&.to_i || 20
|
|
@@ -64,6 +64,9 @@ module Profiler
|
|
|
64
64
|
when "queue" then job_data["queue"] || "-"
|
|
65
65
|
when "status" then job_data["status"] || "-"
|
|
66
66
|
when "duration" then "#{profile.duration.round(2)}ms"
|
|
67
|
+
when "gem_version"
|
|
68
|
+
v = profile.gem_version || "-"
|
|
69
|
+
v != Profiler::VERSION ? "#{v} ⚠️" : v
|
|
67
70
|
when "token" then profile.token.to_s
|
|
68
71
|
when "parent_token" then profile.parent_token || "-"
|
|
69
72
|
end
|
|
@@ -62,7 +62,7 @@ module Profiler
|
|
|
62
62
|
def toolbar_html
|
|
63
63
|
<<~HTML
|
|
64
64
|
#{ajax_interceptor_script}
|
|
65
|
-
<div id="profiler-toolbar" data-token="#{@token}"></div>
|
|
65
|
+
<div id="profiler-toolbar" data-token="#{@token}" data-version="#{Profiler::VERSION}"></div>
|
|
66
66
|
<script src="/_profiler/assets/profiler-toolbar.js" defer#{nonce_attr}></script>
|
|
67
67
|
<style>#{toolbar_styles}</style>
|
|
68
68
|
HTML
|
|
@@ -13,7 +13,8 @@ module Profiler
|
|
|
13
13
|
:response_headers, :collectors_data, :collectors_metadata,
|
|
14
14
|
:parent_token, :is_ajax, :profile_type,
|
|
15
15
|
:request_body, :request_body_encoding,
|
|
16
|
-
:response_body, :response_body_encoding
|
|
16
|
+
:response_body, :response_body_encoding,
|
|
17
|
+
:gem_version
|
|
17
18
|
|
|
18
19
|
def initialize(request = nil)
|
|
19
20
|
@token = SecureRandom.hex(16)
|
|
@@ -32,9 +33,6 @@ module Profiler
|
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
TEXT_BODY_LIMIT = 512 * 1024
|
|
36
|
-
BINARY_BODY_LIMIT = 256 * 1024
|
|
37
|
-
|
|
38
36
|
def set_bodies(request_body:, response_body:, req_content_type:, resp_content_type:)
|
|
39
37
|
req = process_body(request_body, req_content_type)
|
|
40
38
|
resp = process_body(response_body, resp_content_type)
|
|
@@ -79,6 +77,7 @@ module Profiler
|
|
|
79
77
|
|
|
80
78
|
{
|
|
81
79
|
profile_type: @profile_type,
|
|
80
|
+
gem_version: @gem_version,
|
|
82
81
|
token: @token,
|
|
83
82
|
path: @path,
|
|
84
83
|
method: @method,
|
|
@@ -130,6 +129,7 @@ module Profiler
|
|
|
130
129
|
profile.parent_token = data[:parent_token]
|
|
131
130
|
profile.is_ajax = data[:is_ajax] || false
|
|
132
131
|
profile.profile_type = data[:profile_type] || "http"
|
|
132
|
+
profile.gem_version = data[:gem_version]
|
|
133
133
|
|
|
134
134
|
# Convert collectors_data keys to strings recursively for consistency
|
|
135
135
|
profile.collectors_data = (data[:collectors_data] || {}).transform_keys(&:to_s).transform_values do |value|
|
|
@@ -159,10 +159,9 @@ module Profiler
|
|
|
159
159
|
return { body: nil, encoding: "text" } if raw.nil? || raw.empty?
|
|
160
160
|
|
|
161
161
|
if binary_content_type?(content_type)
|
|
162
|
-
|
|
163
|
-
{ body: Base64.strict_encode64(truncated), encoding: "base64" }
|
|
162
|
+
{ body: Base64.strict_encode64(raw.b), encoding: "base64" }
|
|
164
163
|
else
|
|
165
|
-
text = raw.encode("UTF-8", invalid: :replace, undef: :replace)
|
|
164
|
+
text = raw.encode("UTF-8", invalid: :replace, undef: :replace)
|
|
166
165
|
if compress_body?(text)
|
|
167
166
|
{ body: Base64.strict_encode64(Zlib::Deflate.deflate(text)), encoding: "gzip+base64" }
|
|
168
167
|
else
|
|
@@ -51,17 +51,18 @@ module Profiler
|
|
|
51
51
|
@db.execute(
|
|
52
52
|
<<~SQL,
|
|
53
53
|
INSERT OR REPLACE INTO profiler_profiles (
|
|
54
|
-
token, profile_type, path, method, status, duration, memory,
|
|
54
|
+
token, profile_type, gem_version, path, method, status, duration, memory,
|
|
55
55
|
started_at, finished_at, parent_token, is_ajax,
|
|
56
56
|
tabs, params, headers, response_headers, collectors_meta
|
|
57
57
|
) VALUES (
|
|
58
|
-
:token, :profile_type, :path, :method, :status, :duration, :memory,
|
|
58
|
+
:token, :profile_type, :gem_version, :path, :method, :status, :duration, :memory,
|
|
59
59
|
:started_at, :finished_at, :parent_token, :is_ajax,
|
|
60
60
|
:tabs, :params, :headers, :response_headers, :collectors_meta
|
|
61
61
|
)
|
|
62
62
|
SQL
|
|
63
63
|
token: token,
|
|
64
64
|
profile_type: data[:profile_type] || "http",
|
|
65
|
+
gem_version: data[:gem_version],
|
|
65
66
|
path: data[:path],
|
|
66
67
|
method: data[:method],
|
|
67
68
|
status: data[:status],
|
|
@@ -176,6 +177,7 @@ module Profiler
|
|
|
176
177
|
CREATE TABLE IF NOT EXISTS profiler_profiles (
|
|
177
178
|
token TEXT PRIMARY KEY,
|
|
178
179
|
profile_type TEXT NOT NULL DEFAULT 'http',
|
|
180
|
+
gem_version TEXT,
|
|
179
181
|
path TEXT,
|
|
180
182
|
method TEXT,
|
|
181
183
|
status INTEGER,
|
|
@@ -202,6 +204,12 @@ module Profiler
|
|
|
202
204
|
CREATE INDEX IF NOT EXISTS idx_profiler_profile_type
|
|
203
205
|
ON profiler_profiles(profile_type);
|
|
204
206
|
SQL
|
|
207
|
+
|
|
208
|
+
begin
|
|
209
|
+
@db.execute("ALTER TABLE profiler_profiles ADD COLUMN gem_version TEXT")
|
|
210
|
+
rescue SQLite3::Exception
|
|
211
|
+
# column already exists
|
|
212
|
+
end
|
|
205
213
|
end
|
|
206
214
|
|
|
207
215
|
def row_to_profile(row, load_blobs: true)
|
|
@@ -223,6 +231,7 @@ module Profiler
|
|
|
223
231
|
Models::Profile.from_hash(
|
|
224
232
|
token: row["token"],
|
|
225
233
|
profile_type: row["profile_type"],
|
|
234
|
+
gem_version: row["gem_version"],
|
|
226
235
|
path: row["path"],
|
|
227
236
|
method: row["method"],
|
|
228
237
|
status: row["status"],
|
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.
|
|
4
|
+
version: 0.20.0
|
|
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-05-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|