rails-profiler 0.21.0 → 0.22.1
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 +81 -0
- data/app/assets/builds/profiler.js +244 -1
- data/lib/profiler/collectors/flamegraph_collector.rb +12 -7
- data/lib/profiler/collectors/http_collector.rb +71 -27
- data/lib/profiler/collectors/mailer_collector.rb +315 -0
- data/lib/profiler/configuration.rb +5 -0
- data/lib/profiler/instrumentation/net_http_instrumentation.rb +26 -36
- data/lib/profiler/instrumentation/thread_context_propagation.rb +34 -0
- data/lib/profiler/job_profiler.rb +6 -1
- data/lib/profiler/mcp/server.rb +18 -1
- data/lib/profiler/mcp/tools/get_profile_detail.rb +51 -0
- data/lib/profiler/mcp/tools/query_mailers.rb +114 -0
- data/lib/profiler/railtie.rb +2 -1
- data/lib/profiler/version.rb +1 -1
- data/lib/profiler.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87931b523c1fcf03710fe558f619591bc2a953aa50ad76ccf414357964515a5d
|
|
4
|
+
data.tar.gz: c07a2ae3e5104529f72d4ce04caf67aa04647946c4ba5e98fc68ca1c7b10c320
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffb96a532e285ff59fe55059a2a0615c397ff82838479da8804709e14e7ec1802a0b759ffdd32ed64bb9166ea185efd8da06956cab3078c43bf4e6120b8d7c56
|
|
7
|
+
data.tar.gz: 22de884813f0d456a53c2388bb3849ff343bbd4349951419ab2f04f3c1977ce0e1681ba5c8c5349fff3cb894d6f2a8b34de9cf13f92acce08921ec3298fe816c
|
|
@@ -1010,6 +1010,60 @@
|
|
|
1010
1010
|
] });
|
|
1011
1011
|
}
|
|
1012
1012
|
|
|
1013
|
+
// app/assets/typescript/profiler/components/toolbar/panels/MailerPanel.tsx
|
|
1014
|
+
function MailerPanel({ mailerData }) {
|
|
1015
|
+
const hasErrors = mailerData.failed > 0 || mailerData.loop_warnings.length > 0;
|
|
1016
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
1017
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-header", children: [
|
|
1018
|
+
"Mailers",
|
|
1019
|
+
/* @__PURE__ */ u3("span", { class: "profiler-float-right", children: [
|
|
1020
|
+
mailerData.total,
|
|
1021
|
+
" email",
|
|
1022
|
+
mailerData.total !== 1 ? "s" : ""
|
|
1023
|
+
] })
|
|
1024
|
+
] }),
|
|
1025
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-content", children: [
|
|
1026
|
+
mailerData.deliver_now > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
1027
|
+
/* @__PURE__ */ u3("span", { children: "deliver_now" }),
|
|
1028
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.deliver_now })
|
|
1029
|
+
] }),
|
|
1030
|
+
mailerData.deliver_later > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
1031
|
+
/* @__PURE__ */ u3("span", { children: "deliver_later" }),
|
|
1032
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.deliver_later })
|
|
1033
|
+
] }),
|
|
1034
|
+
(mailerData.queued_count ?? 0) > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
1035
|
+
/* @__PURE__ */ u3("span", { children: "Queued" }),
|
|
1036
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.queued_count })
|
|
1037
|
+
] }),
|
|
1038
|
+
mailerData.multi_part_count > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
1039
|
+
/* @__PURE__ */ u3("span", { children: "Multi-part" }),
|
|
1040
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.multi_part_count })
|
|
1041
|
+
] }),
|
|
1042
|
+
mailerData.failed > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
1043
|
+
/* @__PURE__ */ u3("span", { children: "Errors" }),
|
|
1044
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--error", children: mailerData.failed })
|
|
1045
|
+
] }),
|
|
1046
|
+
mailerData.loop_warnings.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
1047
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--warning", children: "\u26A0\uFE0F Loop detected" }),
|
|
1048
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--warning", children: mailerData.loop_warnings.length })
|
|
1049
|
+
] }),
|
|
1050
|
+
[...mailerData.emails, ...mailerData.errors].slice(0, 3).map((email, i3) => /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row profiler-text--sm", children: [
|
|
1051
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: [
|
|
1052
|
+
email.mailer_class,
|
|
1053
|
+
"#",
|
|
1054
|
+
email.action
|
|
1055
|
+
] }),
|
|
1056
|
+
/* @__PURE__ */ u3("span", { class: email.error ? "profiler-text--error" : "profiler-text--success", children: email.error ? "\u274C" : "\u2705" })
|
|
1057
|
+
] }, i3)),
|
|
1058
|
+
mailerData.total > 3 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row profiler-text--muted profiler-text--xs", children: [
|
|
1059
|
+
"+",
|
|
1060
|
+
mailerData.total - 3,
|
|
1061
|
+
" more\u2026"
|
|
1062
|
+
] })
|
|
1063
|
+
] })
|
|
1064
|
+
] });
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1013
1067
|
// app/assets/typescript/profiler/components/toolbar/ToolbarApp.tsx
|
|
1014
1068
|
function statusClass2(status) {
|
|
1015
1069
|
if (status >= 200 && status < 300) return "profiler-text--success";
|
|
@@ -1045,6 +1099,7 @@
|
|
|
1045
1099
|
const routesData = cd["routes"];
|
|
1046
1100
|
const i18nData = cd["i18n"];
|
|
1047
1101
|
const envData = cd["env"];
|
|
1102
|
+
const mailerData = cd["mailer"];
|
|
1048
1103
|
const childJobs = profile.child_jobs ?? [];
|
|
1049
1104
|
const reqClass = statusClass2(profile.status);
|
|
1050
1105
|
const durClass = durationClass(profile.duration);
|
|
@@ -1283,6 +1338,32 @@
|
|
|
1283
1338
|
]
|
|
1284
1339
|
}
|
|
1285
1340
|
),
|
|
1341
|
+
mailerData && (mailerData.total > 0 || (mailerData.queued_count ?? 0) > 0) && /* @__PURE__ */ u3(
|
|
1342
|
+
ToolbarItem,
|
|
1343
|
+
{
|
|
1344
|
+
href: `/_profiler/profiles/${token}?tab=mailer`,
|
|
1345
|
+
className: mailerData.failed > 0 ? "profiler-text--error" : "profiler-text--success",
|
|
1346
|
+
panelLarge: true,
|
|
1347
|
+
panel: /* @__PURE__ */ u3(MailerPanel, { mailerData }),
|
|
1348
|
+
children: [
|
|
1349
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "MAIL" }),
|
|
1350
|
+
mailerData.total > 0 && /* @__PURE__ */ u3("span", { children: mailerData.total }),
|
|
1351
|
+
mailerData.total === 0 && (mailerData.queued_count ?? 0) > 0 && /* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: [
|
|
1352
|
+
mailerData.queued_count,
|
|
1353
|
+
"q"
|
|
1354
|
+
] }),
|
|
1355
|
+
mailerData.total > 0 && (mailerData.queued_count ?? 0) > 0 && /* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: [
|
|
1356
|
+
"+",
|
|
1357
|
+
mailerData.queued_count,
|
|
1358
|
+
"q"
|
|
1359
|
+
] }),
|
|
1360
|
+
mailerData.failed > 0 && /* @__PURE__ */ u3("span", { class: "profiler-text--error profiler-text--xs", children: [
|
|
1361
|
+
"\u25B2 ",
|
|
1362
|
+
mailerData.failed
|
|
1363
|
+
] })
|
|
1364
|
+
]
|
|
1365
|
+
}
|
|
1366
|
+
),
|
|
1286
1367
|
envData && envData.total > 0 && /* @__PURE__ */ u3(
|
|
1287
1368
|
ToolbarItem,
|
|
1288
1369
|
{
|
|
@@ -4449,6 +4449,243 @@
|
|
|
4449
4449
|
] });
|
|
4450
4450
|
}
|
|
4451
4451
|
|
|
4452
|
+
// app/assets/typescript/profiler/components/dashboard/tabs/MailerTab.tsx
|
|
4453
|
+
function BodyPreview({ email }) {
|
|
4454
|
+
const hasHtml = !!email.body_html;
|
|
4455
|
+
const hasText = !!email.body_text;
|
|
4456
|
+
if (!email.body_captured) {
|
|
4457
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-mt-3 profiler-text--xs profiler-text--muted", children: [
|
|
4458
|
+
"Body not captured \u2014 enable ",
|
|
4459
|
+
/* @__PURE__ */ u3("code", { children: "config.capture_mail_body = true" }),
|
|
4460
|
+
" in your profiler initializer"
|
|
4461
|
+
] });
|
|
4462
|
+
}
|
|
4463
|
+
if (!hasHtml && !hasText) return null;
|
|
4464
|
+
const [mode, setMode] = d2(hasHtml ? "preview" : "text");
|
|
4465
|
+
const switchMode = (m3) => (e3) => {
|
|
4466
|
+
e3.stopPropagation();
|
|
4467
|
+
setMode(m3);
|
|
4468
|
+
};
|
|
4469
|
+
const activeStyle = { borderColor: "var(--profiler-accent)", color: "var(--profiler-accent)" };
|
|
4470
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-mt-3", children: [
|
|
4471
|
+
/* @__PURE__ */ u3("div", { class: "profiler-mb-2 profiler-flex profiler-flex--gap-2", style: { alignItems: "center" }, children: [
|
|
4472
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "Body" }),
|
|
4473
|
+
hasHtml && /* @__PURE__ */ u3(k, { children: [
|
|
4474
|
+
/* @__PURE__ */ u3("button", { class: "profiler-btn profiler-btn--sm", style: mode === "preview" ? activeStyle : {}, onClick: switchMode("preview"), children: "HTML preview" }),
|
|
4475
|
+
/* @__PURE__ */ u3("button", { class: "profiler-btn profiler-btn--sm", style: mode === "source" ? activeStyle : {}, onClick: switchMode("source"), children: "HTML source" })
|
|
4476
|
+
] }),
|
|
4477
|
+
hasText && /* @__PURE__ */ u3("button", { class: "profiler-btn profiler-btn--sm", style: mode === "text" ? activeStyle : {}, onClick: switchMode("text"), children: "Plain text" })
|
|
4478
|
+
] }),
|
|
4479
|
+
mode === "preview" && hasHtml && /* @__PURE__ */ u3(
|
|
4480
|
+
"iframe",
|
|
4481
|
+
{
|
|
4482
|
+
srcdoc: email.body_html,
|
|
4483
|
+
sandbox: "allow-same-origin",
|
|
4484
|
+
style: { width: "100%", height: "300px", border: "1px solid var(--profiler-border)", borderRadius: "var(--profiler-radius-md)", background: "#fff", display: "block" }
|
|
4485
|
+
}
|
|
4486
|
+
),
|
|
4487
|
+
(mode === "source" || mode === "text") && /* @__PURE__ */ u3("pre", { style: { maxHeight: "300px", overflow: "auto", background: "var(--profiler-bg-lighter, rgba(0,0,0,0.2))", padding: "12px", borderRadius: "var(--profiler-radius-md)", fontSize: "12px", margin: 0, whiteSpace: "pre-wrap", wordBreak: "break-all", border: "1px solid var(--profiler-border)" }, children: mode === "source" ? email.body_html : email.body_text })
|
|
4488
|
+
] });
|
|
4489
|
+
}
|
|
4490
|
+
function AssignsSection({ assigns }) {
|
|
4491
|
+
const entries = Object.entries(assigns);
|
|
4492
|
+
if (entries.length === 0) return null;
|
|
4493
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-mt-3", children: [
|
|
4494
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted profiler-mb-1", style: { textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Variables" }),
|
|
4495
|
+
entries.map(([key, value]) => /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4496
|
+
/* @__PURE__ */ u3("span", { children: /* @__PURE__ */ u3("code", { children: [
|
|
4497
|
+
"@",
|
|
4498
|
+
key
|
|
4499
|
+
] }) }),
|
|
4500
|
+
/* @__PURE__ */ u3("code", { style: { textAlign: "right", wordBreak: "break-all", maxWidth: "60%" }, children: value })
|
|
4501
|
+
] }, key))
|
|
4502
|
+
] });
|
|
4503
|
+
}
|
|
4504
|
+
function EmailDetail({ email }) {
|
|
4505
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-mt-3", onClick: (e3) => e3.stopPropagation(), children: [
|
|
4506
|
+
email.subject && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4507
|
+
/* @__PURE__ */ u3("span", { children: "Subject" }),
|
|
4508
|
+
/* @__PURE__ */ u3("span", { children: email.subject })
|
|
4509
|
+
] }),
|
|
4510
|
+
email.to && email.to.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4511
|
+
/* @__PURE__ */ u3("span", { children: "To" }),
|
|
4512
|
+
/* @__PURE__ */ u3("span", { style: { textAlign: "right", wordBreak: "break-all" }, children: email.to.join(", ") })
|
|
4513
|
+
] }),
|
|
4514
|
+
email.from && email.from.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4515
|
+
/* @__PURE__ */ u3("span", { children: "From" }),
|
|
4516
|
+
/* @__PURE__ */ u3("span", { style: { textAlign: "right" }, children: email.from.join(", ") })
|
|
4517
|
+
] }),
|
|
4518
|
+
email.cc && email.cc.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4519
|
+
/* @__PURE__ */ u3("span", { children: "CC" }),
|
|
4520
|
+
/* @__PURE__ */ u3("span", { style: { textAlign: "right" }, children: email.cc.join(", ") })
|
|
4521
|
+
] }),
|
|
4522
|
+
email.bcc && email.bcc.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4523
|
+
/* @__PURE__ */ u3("span", { children: "BCC" }),
|
|
4524
|
+
/* @__PURE__ */ u3("span", { style: { textAlign: "right" }, children: email.bcc.join(", ") })
|
|
4525
|
+
] }),
|
|
4526
|
+
email.reply_to && email.reply_to.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4527
|
+
/* @__PURE__ */ u3("span", { children: "Reply-To" }),
|
|
4528
|
+
/* @__PURE__ */ u3("span", { style: { textAlign: "right" }, children: email.reply_to.join(", ") })
|
|
4529
|
+
] }),
|
|
4530
|
+
email.template && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4531
|
+
/* @__PURE__ */ u3("span", { children: "Template" }),
|
|
4532
|
+
/* @__PURE__ */ u3("code", { children: email.template })
|
|
4533
|
+
] }),
|
|
4534
|
+
email.parts && email.parts.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4535
|
+
/* @__PURE__ */ u3("span", { children: "Parts" }),
|
|
4536
|
+
/* @__PURE__ */ u3("span", { style: { textAlign: "right" }, children: email.parts.join(", ") })
|
|
4537
|
+
] }),
|
|
4538
|
+
email.attachments && email.attachments.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4539
|
+
/* @__PURE__ */ u3("span", { children: "Attachments" }),
|
|
4540
|
+
/* @__PURE__ */ u3("span", { style: { textAlign: "right" }, children: email.attachments.map((a3) => `${a3.filename} (${(a3.size / 1024).toFixed(1)} KB)`).join(", ") })
|
|
4541
|
+
] }),
|
|
4542
|
+
/* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4543
|
+
/* @__PURE__ */ u3("span", { children: "Render" }),
|
|
4544
|
+
/* @__PURE__ */ u3("span", { class: "profiler-query-card__duration", children: email.duration_ms != null ? `${email.duration_ms} ms` : "\u2014" })
|
|
4545
|
+
] }),
|
|
4546
|
+
email.delivery_ms != null && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4547
|
+
/* @__PURE__ */ u3("span", { children: "Delivery" }),
|
|
4548
|
+
/* @__PURE__ */ u3("span", { class: "profiler-query-card__duration", children: [
|
|
4549
|
+
email.delivery_ms,
|
|
4550
|
+
" ms"
|
|
4551
|
+
] })
|
|
4552
|
+
] }),
|
|
4553
|
+
email.message_id && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4554
|
+
/* @__PURE__ */ u3("span", { children: "Message-ID" }),
|
|
4555
|
+
/* @__PURE__ */ u3("code", { style: { fontSize: "11px", wordBreak: "break-all", maxWidth: "70%", textAlign: "right" }, children: email.message_id })
|
|
4556
|
+
] }),
|
|
4557
|
+
email.error && /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
4558
|
+
/* @__PURE__ */ u3("span", { children: "Error" }),
|
|
4559
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--error", style: { textAlign: "right" }, children: email.error })
|
|
4560
|
+
] }),
|
|
4561
|
+
email.assigns && Object.keys(email.assigns).length > 0 && /* @__PURE__ */ u3(AssignsSection, { assigns: email.assigns }),
|
|
4562
|
+
/* @__PURE__ */ u3(BodyPreview, { email })
|
|
4563
|
+
] });
|
|
4564
|
+
}
|
|
4565
|
+
function EmailRow({ email, onClick, isExpanded }) {
|
|
4566
|
+
const cardClass = ["profiler-query-card", email.error ? "profiler-query-card--slow" : ""].filter(Boolean).join(" ");
|
|
4567
|
+
return /* @__PURE__ */ u3("div", { class: cardClass, style: { cursor: "pointer", marginBottom: "6px" }, onClick, children: [
|
|
4568
|
+
/* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
|
|
4569
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", style: { minWidth: 0, flex: 1 }, children: /* @__PURE__ */ u3("span", { class: "profiler-text--sm", style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
|
|
4570
|
+
/* @__PURE__ */ u3("strong", { children: email.mailer_class }),
|
|
4571
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: [
|
|
4572
|
+
"#",
|
|
4573
|
+
email.action
|
|
4574
|
+
] }),
|
|
4575
|
+
email.subject && /* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: [
|
|
4576
|
+
" \u2014 ",
|
|
4577
|
+
email.subject.slice(0, 60),
|
|
4578
|
+
email.subject.length > 60 ? "\u2026" : ""
|
|
4579
|
+
] })
|
|
4580
|
+
] }) }),
|
|
4581
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", style: { flexShrink: 0 }, children: [
|
|
4582
|
+
email.delivery_mode && /* @__PURE__ */ u3("span", { class: "badge-info profiler-text--xs", children: email.delivery_mode }),
|
|
4583
|
+
email.duration_ms != null && /* @__PURE__ */ u3("span", { class: "profiler-query-card__duration profiler-text--xs", children: [
|
|
4584
|
+
email.duration_ms,
|
|
4585
|
+
" ms"
|
|
4586
|
+
] }),
|
|
4587
|
+
email.error ? /* @__PURE__ */ u3("span", { class: "badge-error profiler-text--xs", children: "Error" }) : /* @__PURE__ */ u3("span", { class: "badge-success profiler-text--xs", children: "Sent" }),
|
|
4588
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: isExpanded ? "\u25B2" : "\u25BC" })
|
|
4589
|
+
] })
|
|
4590
|
+
] }),
|
|
4591
|
+
isExpanded && /* @__PURE__ */ u3(EmailDetail, { email })
|
|
4592
|
+
] });
|
|
4593
|
+
}
|
|
4594
|
+
function QueuedRow({ email }) {
|
|
4595
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-query-card", style: { marginBottom: "6px", borderLeft: "3px solid var(--profiler-info, #38bdf8)" }, children: [
|
|
4596
|
+
/* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
|
|
4597
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--sm", children: [
|
|
4598
|
+
/* @__PURE__ */ u3("strong", { children: email.mailer_class }),
|
|
4599
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: [
|
|
4600
|
+
"#",
|
|
4601
|
+
email.action
|
|
4602
|
+
] })
|
|
4603
|
+
] }),
|
|
4604
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", style: { flexShrink: 0 }, children: [
|
|
4605
|
+
/* @__PURE__ */ u3("span", { class: "badge-info profiler-text--xs", children: "queued" }),
|
|
4606
|
+
email.delivery_method && /* @__PURE__ */ u3("span", { class: "badge-default profiler-text--xs", children: email.delivery_method }),
|
|
4607
|
+
email.duration_ms != null && /* @__PURE__ */ u3("span", { class: "profiler-query-card__duration profiler-text--xs", children: [
|
|
4608
|
+
email.duration_ms,
|
|
4609
|
+
" ms"
|
|
4610
|
+
] })
|
|
4611
|
+
] })
|
|
4612
|
+
] }),
|
|
4613
|
+
email.assigns && Object.keys(email.assigns).length > 0 && /* @__PURE__ */ u3(AssignsSection, { assigns: email.assigns })
|
|
4614
|
+
] });
|
|
4615
|
+
}
|
|
4616
|
+
function MailerTab({ mailerData }) {
|
|
4617
|
+
const [expandedIndex, setExpandedIndex] = d2(null);
|
|
4618
|
+
const [expandedErrorIndex, setExpandedErrorIndex] = d2(null);
|
|
4619
|
+
const queuedCount = mailerData?.queued_count ?? 0;
|
|
4620
|
+
if (!mailerData || mailerData.total === 0 && queuedCount === 0) {
|
|
4621
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "No emails sent during this request" }) });
|
|
4622
|
+
}
|
|
4623
|
+
const toggleRow = (i3) => setExpandedIndex(expandedIndex === i3 ? null : i3);
|
|
4624
|
+
const toggleErrorRow = (i3) => setExpandedErrorIndex(expandedErrorIndex === i3 ? null : i3);
|
|
4625
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
4626
|
+
/* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: "Mailer Deliveries" }),
|
|
4627
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
|
|
4628
|
+
/* @__PURE__ */ u3("span", { children: [
|
|
4629
|
+
"Total: ",
|
|
4630
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.total })
|
|
4631
|
+
] }),
|
|
4632
|
+
mailerData.deliver_now > 0 && /* @__PURE__ */ u3("span", { children: [
|
|
4633
|
+
"deliver_now: ",
|
|
4634
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.deliver_now })
|
|
4635
|
+
] }),
|
|
4636
|
+
mailerData.deliver_later > 0 && /* @__PURE__ */ u3("span", { children: [
|
|
4637
|
+
"deliver_later: ",
|
|
4638
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.deliver_later })
|
|
4639
|
+
] }),
|
|
4640
|
+
queuedCount > 0 && /* @__PURE__ */ u3("span", { children: [
|
|
4641
|
+
"queued: ",
|
|
4642
|
+
/* @__PURE__ */ u3("strong", { children: queuedCount })
|
|
4643
|
+
] }),
|
|
4644
|
+
mailerData.multi_part_count > 0 && /* @__PURE__ */ u3("span", { children: [
|
|
4645
|
+
"Multi-part: ",
|
|
4646
|
+
/* @__PURE__ */ u3("strong", { children: mailerData.multi_part_count })
|
|
4647
|
+
] }),
|
|
4648
|
+
mailerData.failed > 0 && /* @__PURE__ */ u3("span", { children: [
|
|
4649
|
+
"Errors: ",
|
|
4650
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--error", children: mailerData.failed })
|
|
4651
|
+
] })
|
|
4652
|
+
] }),
|
|
4653
|
+
mailerData.loop_warnings.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-alert-banner profiler-alert-banner--warning profiler-mb-4", children: [
|
|
4654
|
+
/* @__PURE__ */ u3("span", { class: "profiler-alert-banner__icon", children: "\u26A0\uFE0F" }),
|
|
4655
|
+
/* @__PURE__ */ u3("div", { style: { flex: 1 }, children: [
|
|
4656
|
+
/* @__PURE__ */ u3("strong", { children: "Send loop detected" }),
|
|
4657
|
+
" \u2014 ",
|
|
4658
|
+
mailerData.loop_warnings.length,
|
|
4659
|
+
" pattern",
|
|
4660
|
+
mailerData.loop_warnings.length > 1 ? "s" : "",
|
|
4661
|
+
mailerData.loop_warnings.map((w3, i3) => /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted profiler-mt-1", children: w3.message }, i3))
|
|
4662
|
+
] })
|
|
4663
|
+
] }),
|
|
4664
|
+
mailerData.truncated && /* @__PURE__ */ u3("div", { class: "profiler-alert-banner profiler-alert-banner--warning profiler-mb-4", children: [
|
|
4665
|
+
/* @__PURE__ */ u3("span", { class: "profiler-alert-banner__icon", children: "\u26A0\uFE0F" }),
|
|
4666
|
+
/* @__PURE__ */ u3("span", { children: "Showing first 50 emails only \u2014 additional emails were truncated" })
|
|
4667
|
+
] }),
|
|
4668
|
+
mailerData.emails.length > 0 && /* @__PURE__ */ u3(k, { children: [
|
|
4669
|
+
/* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mt-4 profiler-mb-3", children: [
|
|
4670
|
+
"Emails",
|
|
4671
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--sm", style: { fontWeight: "normal", marginLeft: "8px" }, children: "click to expand" })
|
|
4672
|
+
] }),
|
|
4673
|
+
mailerData.emails.map((email, i3) => /* @__PURE__ */ u3(EmailRow, { email, onClick: () => toggleRow(i3), isExpanded: expandedIndex === i3 }, i3))
|
|
4674
|
+
] }),
|
|
4675
|
+
mailerData.queued && mailerData.queued.length > 0 && /* @__PURE__ */ u3(k, { children: [
|
|
4676
|
+
/* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mt-4 profiler-mb-2", children: [
|
|
4677
|
+
"Queued",
|
|
4678
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--sm", style: { fontWeight: "normal", marginLeft: "8px" }, children: "deliver_later \u2014 will be sent in a background job" })
|
|
4679
|
+
] }),
|
|
4680
|
+
mailerData.queued.map((email, i3) => /* @__PURE__ */ u3(QueuedRow, { email }, `q-${i3}`))
|
|
4681
|
+
] }),
|
|
4682
|
+
mailerData.errors.length > 0 && /* @__PURE__ */ u3(k, { children: [
|
|
4683
|
+
/* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mt-6 profiler-mb-3 profiler-text--error", children: "Delivery Errors" }),
|
|
4684
|
+
mailerData.errors.map((email, i3) => /* @__PURE__ */ u3(EmailRow, { email, onClick: () => toggleErrorRow(i3), isExpanded: expandedErrorIndex === i3 }, `err-${i3}`))
|
|
4685
|
+
] })
|
|
4686
|
+
] });
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4452
4689
|
// app/assets/typescript/profiler/components/dashboard/ProfileDashboard.tsx
|
|
4453
4690
|
function ProfileDashboard({ profile, initialTab, embedded }) {
|
|
4454
4691
|
const cd = profile.collectors_data || {};
|
|
@@ -4459,6 +4696,7 @@
|
|
|
4459
4696
|
const hasRoutes = (cd["routes"]?.total ?? 0) > 0;
|
|
4460
4697
|
const hasI18n = (cd["i18n"]?.total ?? 0) > 0;
|
|
4461
4698
|
const hasJobs = (profile.child_jobs?.length ?? 0) > 0;
|
|
4699
|
+
const hasMailers = (cd["mailer"]?.total ?? 0) > 0;
|
|
4462
4700
|
const [activeTab, setActiveTab] = d2(hasException ? "exception" : initialTab);
|
|
4463
4701
|
const handleTabClick = (tab) => (e3) => {
|
|
4464
4702
|
e3.preventDefault();
|
|
@@ -4530,6 +4768,7 @@
|
|
|
4530
4768
|
profile.child_jobs.length,
|
|
4531
4769
|
")"
|
|
4532
4770
|
] }),
|
|
4771
|
+
hasMailers && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("mailer"), onClick: handleTabClick("mailer"), children: "Mailers" }),
|
|
4533
4772
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("env"), onClick: handleTabClick("env"), children: "Env" })
|
|
4534
4773
|
] }),
|
|
4535
4774
|
/* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
|
|
@@ -4546,6 +4785,7 @@
|
|
|
4546
4785
|
activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] }),
|
|
4547
4786
|
activeTab === "i18n" && /* @__PURE__ */ u3(I18nTab, { i18nData: cd["i18n"] }),
|
|
4548
4787
|
activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs }),
|
|
4788
|
+
activeTab === "mailer" && /* @__PURE__ */ u3(MailerTab, { mailerData: cd["mailer"] }),
|
|
4549
4789
|
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"], readOnly: true })
|
|
4550
4790
|
] })
|
|
4551
4791
|
] }),
|
|
@@ -4615,7 +4855,8 @@
|
|
|
4615
4855
|
const hasDumps = (cd["dump"]?.count ?? 0) > 0;
|
|
4616
4856
|
const hasLogs = (cd["logs"]?.total ?? 0) > 0;
|
|
4617
4857
|
const hasException = !!cd["exception"]?.exception_class;
|
|
4618
|
-
const
|
|
4858
|
+
const hasMailers = (cd["mailer"]?.total ?? 0) > 0;
|
|
4859
|
+
const validTabs = ["job", "database", "cache", "http", "jobs", "dump", "logs", "exception", "env", "timeline", "mailer"];
|
|
4619
4860
|
const defaultTab = validTabs.includes(initialTab) ? initialTab : "job";
|
|
4620
4861
|
const [activeTab, setActiveTab] = d2(hasException ? "exception" : defaultTab);
|
|
4621
4862
|
const jobData = cd["job"];
|
|
@@ -4706,6 +4947,7 @@
|
|
|
4706
4947
|
")"
|
|
4707
4948
|
] }),
|
|
4708
4949
|
hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" }),
|
|
4950
|
+
hasMailers && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("mailer"), onClick: handleTabClick("mailer"), children: "Mailers" }),
|
|
4709
4951
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("env"), onClick: handleTabClick("env"), children: "Env" })
|
|
4710
4952
|
] }),
|
|
4711
4953
|
/* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
|
|
@@ -4718,6 +4960,7 @@
|
|
|
4718
4960
|
activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs }),
|
|
4719
4961
|
activeTab === "dump" && /* @__PURE__ */ u3(DumpsTab, { dumpData: cd["dump"] }),
|
|
4720
4962
|
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] }),
|
|
4963
|
+
activeTab === "mailer" && /* @__PURE__ */ u3(MailerTab, { mailerData: cd["mailer"] }),
|
|
4721
4964
|
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"], readOnly: true })
|
|
4722
4965
|
] })
|
|
4723
4966
|
] }),
|
|
@@ -10,6 +10,7 @@ module Profiler
|
|
|
10
10
|
super
|
|
11
11
|
@events = []
|
|
12
12
|
@subscriptions = []
|
|
13
|
+
@mutex = Mutex.new
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def icon
|
|
@@ -38,7 +39,7 @@ module Profiler
|
|
|
38
39
|
|
|
39
40
|
# Controller action
|
|
40
41
|
@subscriptions << ActiveSupport::Notifications.monotonic_subscribe("process_action.action_controller") do |_name, started, finished, _unique_id, payload|
|
|
41
|
-
|
|
42
|
+
add_event Models::TimelineEvent.new(
|
|
42
43
|
name: "#{payload[:controller]}##{payload[:action]}",
|
|
43
44
|
started_at: started,
|
|
44
45
|
finished_at: finished,
|
|
@@ -57,7 +58,7 @@ module Profiler
|
|
|
57
58
|
# Template rendering
|
|
58
59
|
@subscriptions << ActiveSupport::Notifications.monotonic_subscribe("render_template.action_view") do |_name, started, finished, _unique_id, payload|
|
|
59
60
|
identifier = short_identifier(payload[:identifier])
|
|
60
|
-
|
|
61
|
+
add_event Models::TimelineEvent.new(
|
|
61
62
|
name: "Render: #{identifier}",
|
|
62
63
|
started_at: started,
|
|
63
64
|
finished_at: finished,
|
|
@@ -69,7 +70,7 @@ module Profiler
|
|
|
69
70
|
# Partial rendering
|
|
70
71
|
@subscriptions << ActiveSupport::Notifications.monotonic_subscribe("render_partial.action_view") do |_name, started, finished, _unique_id, payload|
|
|
71
72
|
identifier = short_identifier(payload[:identifier])
|
|
72
|
-
|
|
73
|
+
add_event Models::TimelineEvent.new(
|
|
73
74
|
name: "Partial: #{identifier}",
|
|
74
75
|
started_at: started,
|
|
75
76
|
finished_at: finished,
|
|
@@ -84,7 +85,7 @@ module Profiler
|
|
|
84
85
|
next if payload[:sql] =~ /^(BEGIN|COMMIT|ROLLBACK|SAVEPOINT)/i
|
|
85
86
|
|
|
86
87
|
sql = payload[:sql].to_s
|
|
87
|
-
|
|
88
|
+
add_event Models::TimelineEvent.new(
|
|
88
89
|
name: sql.length > 80 ? "#{sql[0, 80]}..." : sql,
|
|
89
90
|
started_at: started,
|
|
90
91
|
finished_at: finished,
|
|
@@ -98,7 +99,7 @@ module Profiler
|
|
|
98
99
|
@subscriptions << ActiveSupport::Notifications.monotonic_subscribe(event_name) do |name, started, finished, _unique_id, payload|
|
|
99
100
|
op = name.split(".").first.sub("cache_", "")
|
|
100
101
|
key = payload[:key].to_s
|
|
101
|
-
|
|
102
|
+
add_event Models::TimelineEvent.new(
|
|
102
103
|
name: "cache_#{op}: #{key.length > 60 ? "#{key[0, 60]}..." : key}",
|
|
103
104
|
started_at: started,
|
|
104
105
|
finished_at: finished,
|
|
@@ -111,7 +112,7 @@ module Profiler
|
|
|
111
112
|
|
|
112
113
|
# Called by Profiler.measure to record custom instrumentation events
|
|
113
114
|
def record_custom_event(label:, started_at:, finished_at:, metadata: {})
|
|
114
|
-
|
|
115
|
+
add_event Models::TimelineEvent.new(
|
|
115
116
|
name: label,
|
|
116
117
|
started_at: started_at,
|
|
117
118
|
finished_at: finished_at,
|
|
@@ -122,7 +123,7 @@ module Profiler
|
|
|
122
123
|
|
|
123
124
|
# Called by NetHttpInstrumentation to record outbound HTTP events
|
|
124
125
|
def record_http_event(started_at:, finished_at:, url:, method:, status:)
|
|
125
|
-
|
|
126
|
+
add_event Models::TimelineEvent.new(
|
|
126
127
|
name: "HTTP #{method} #{url}",
|
|
127
128
|
started_at: started_at,
|
|
128
129
|
finished_at: finished_at,
|
|
@@ -182,6 +183,10 @@ module Profiler
|
|
|
182
183
|
roots
|
|
183
184
|
end
|
|
184
185
|
|
|
186
|
+
def add_event(event)
|
|
187
|
+
@mutex.synchronize { @events << event }
|
|
188
|
+
end
|
|
189
|
+
|
|
185
190
|
def short_identifier(identifier)
|
|
186
191
|
return identifier.to_s unless identifier.to_s.include?("/")
|
|
187
192
|
|
|
@@ -9,6 +9,8 @@ module Profiler
|
|
|
9
9
|
def initialize(profile)
|
|
10
10
|
super
|
|
11
11
|
@requests = []
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@collected = false
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def icon
|
|
@@ -40,47 +42,88 @@ module Profiler
|
|
|
40
42
|
def collect
|
|
41
43
|
Thread.current[:profiler_http_collector] = nil
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
data = @mutex.synchronize do
|
|
46
|
+
@collected = true
|
|
47
|
+
build_data(@requests)
|
|
48
|
+
end
|
|
49
|
+
store_data(data)
|
|
50
|
+
end
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
# Called from NetHttpInstrumentation before the actual HTTP call.
|
|
53
|
+
# Returns the mutable entry so the caller can update it on completion.
|
|
54
|
+
def register_pending(payload)
|
|
55
|
+
entry = payload.merge(in_flight: true, status: 0, duration: nil,
|
|
56
|
+
response_headers: {}, response_body: nil,
|
|
57
|
+
response_body_encoding: "text", response_size: 0)
|
|
58
|
+
@mutex.synchronize { @requests << entry }
|
|
59
|
+
save_if_collected
|
|
60
|
+
entry
|
|
54
61
|
end
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
# Called from NetHttpInstrumentation after the HTTP response is received.
|
|
64
|
+
def complete_request(entry, **data)
|
|
65
|
+
@mutex.synchronize { entry.merge!(data.merge(in_flight: false)) }
|
|
66
|
+
save_if_collected
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Called from NetHttpInstrumentation when the HTTP call raises.
|
|
70
|
+
def fail_request(entry, error:, duration:)
|
|
71
|
+
@mutex.synchronize { entry.merge!(in_flight: false, status: 0, duration: duration, error: error) }
|
|
72
|
+
save_if_collected
|
|
58
73
|
end
|
|
59
74
|
|
|
60
75
|
def toolbar_summary
|
|
61
|
-
|
|
76
|
+
requests = @mutex.synchronize { @requests.dup }
|
|
77
|
+
total = requests.size
|
|
62
78
|
return { text: "0 HTTP", color: "green" } if total == 0
|
|
63
79
|
|
|
64
80
|
threshold = Profiler.configuration.slow_http_threshold
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
in_flight = requests.count { |r| r[:in_flight] }
|
|
82
|
+
errors = requests.count { |r| !r[:in_flight] && (r[:status] >= 400 || r[:status] == 0) }
|
|
83
|
+
slow = requests.count { |r| !r[:in_flight] && r[:duration] && r[:duration] >= threshold }
|
|
84
|
+
duration = requests.sum { |r| r[:duration].to_f }.round(2)
|
|
68
85
|
|
|
69
86
|
color = if errors > 0 || slow > 0
|
|
70
87
|
"red"
|
|
71
|
-
elsif total > 10
|
|
88
|
+
elsif in_flight > 0 || total > 10
|
|
72
89
|
"orange"
|
|
73
90
|
else
|
|
74
91
|
"green"
|
|
75
92
|
end
|
|
76
93
|
|
|
77
|
-
|
|
94
|
+
text = in_flight > 0 ? "#{total} HTTP (#{in_flight} pending, #{duration}ms)" : "#{total} HTTP (#{duration}ms)"
|
|
95
|
+
{ text: text, color: color }
|
|
78
96
|
end
|
|
79
97
|
|
|
80
98
|
private
|
|
81
99
|
|
|
82
|
-
def
|
|
83
|
-
|
|
100
|
+
def build_data(requests)
|
|
101
|
+
threshold = Profiler.configuration.slow_http_threshold
|
|
102
|
+
{
|
|
103
|
+
total_requests: requests.size,
|
|
104
|
+
total_duration: requests.sum { |r| r[:duration].to_f }.round(2),
|
|
105
|
+
slow_requests: requests.count { |r| !r[:in_flight] && r[:duration] && r[:duration] >= threshold },
|
|
106
|
+
error_requests: requests.count { |r| !r[:in_flight] && (r[:status] >= 400 || r[:status] == 0) },
|
|
107
|
+
by_host: group_by_host(requests),
|
|
108
|
+
by_status: group_by_status(requests),
|
|
109
|
+
requests: requests.map { |r| r.transform_keys(&:to_s) }
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Rebuilds and persists HTTP data after collect has already run.
|
|
114
|
+
# Called when fire-and-forget threads register or complete requests post-collect.
|
|
115
|
+
def save_if_collected
|
|
116
|
+
data = @mutex.synchronize do
|
|
117
|
+
return unless @collected
|
|
118
|
+
|
|
119
|
+
build_data(@requests)
|
|
120
|
+
end
|
|
121
|
+
store_data(data)
|
|
122
|
+
Profiler.storage.save(@profile.token, @profile)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def group_by_host(requests)
|
|
126
|
+
requests.each_with_object(Hash.new(0)) do |req, h|
|
|
84
127
|
host = begin
|
|
85
128
|
URI.parse(req[:url]).host || "unknown"
|
|
86
129
|
rescue URI::InvalidURIError
|
|
@@ -90,16 +133,17 @@ module Profiler
|
|
|
90
133
|
end
|
|
91
134
|
end
|
|
92
135
|
|
|
93
|
-
def group_by_status
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
136
|
+
def group_by_status(requests)
|
|
137
|
+
requests.each_with_object(Hash.new(0)) do |req, h|
|
|
138
|
+
key = if req[:in_flight]
|
|
139
|
+
"pending"
|
|
140
|
+
elsif req[:status] == 0
|
|
97
141
|
"error"
|
|
98
|
-
elsif status < 300
|
|
142
|
+
elsif req[:status] < 300
|
|
99
143
|
"2xx"
|
|
100
|
-
elsif status < 400
|
|
144
|
+
elsif req[:status] < 400
|
|
101
145
|
"3xx"
|
|
102
|
-
elsif status < 500
|
|
146
|
+
elsif req[:status] < 500
|
|
103
147
|
"4xx"
|
|
104
148
|
else
|
|
105
149
|
"5xx"
|