rails-profiler 0.17.0 → 0.19.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.js +342 -265
- data/app/controllers/profiler/api/env_vars_controller.rb +21 -1
- data/config/routes.rb +2 -0
- data/lib/profiler/collectors/exception_collector.rb +4 -0
- data/lib/profiler/env_override_store.rb +138 -0
- data/lib/profiler/instrumentation/net_http_instrumentation.rb +3 -0
- data/lib/profiler/instrumentation/sidekiq_middleware.rb +1 -0
- data/lib/profiler/job_profiler.rb +14 -1
- data/lib/profiler/railtie.rb +4 -0
- data/lib/profiler/version.rb +1 -1
- data/lib/profiler.rb +5 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a4e5e84c4946f549552450c0d2d2d2053b273d3bed8dba66fefa17d9c4e5f7f9
|
|
4
|
+
data.tar.gz: 6b4be4f2b3116dbdc4270df5a6f860eabbbf709c3fc296ea34f400aaeb26ce83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66136505b84f792418deb7a29d7d77f36a3cc62cf3bdab9d7defc842f9797b39667c92e18984925a14276880a129ef5b42faa644112ab1c917b61e9f11f8b359
|
|
7
|
+
data.tar.gz: b4ac6c0b37496721a3960732ab0839224e896aa4390757b36d84595200e6441034649f8829573a1fee30cf9c8216c5275e5faa9b0534bc48fe49a20e738d2bf3
|
|
@@ -396,6 +396,24 @@
|
|
|
396
396
|
return "function" == typeof t3 ? t3(n2) : t3;
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
// app/assets/typescript/profiler/components/dashboard/tabs/shared/utils.ts
|
|
400
|
+
function methodBadge(method) {
|
|
401
|
+
const map = { GET: "info", POST: "success", PUT: "warning", PATCH: "warning", DELETE: "error" };
|
|
402
|
+
return map[method] || "default";
|
|
403
|
+
}
|
|
404
|
+
function statusBadge(status) {
|
|
405
|
+
if (status === 0) return "error";
|
|
406
|
+
if (status >= 200 && status < 300) return "success";
|
|
407
|
+
if (status >= 400) return "error";
|
|
408
|
+
return "warning";
|
|
409
|
+
}
|
|
410
|
+
function formatBytes(bytes) {
|
|
411
|
+
if (bytes < 0) return "\u2014";
|
|
412
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
413
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
414
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
415
|
+
}
|
|
416
|
+
|
|
399
417
|
// node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
|
|
400
418
|
var f3 = 0;
|
|
401
419
|
function u3(e3, t3, n2, o3, i3, u4) {
|
|
@@ -407,21 +425,53 @@
|
|
|
407
425
|
return l.vnode && l.vnode(l3), l3;
|
|
408
426
|
}
|
|
409
427
|
|
|
410
|
-
// app/assets/typescript/profiler/components/dashboard/tabs/
|
|
411
|
-
function
|
|
412
|
-
const
|
|
413
|
-
|
|
428
|
+
// app/assets/typescript/profiler/components/dashboard/tabs/shared/HttpComponents.tsx
|
|
429
|
+
function CopyButton({ text }) {
|
|
430
|
+
const [copied, setCopied] = d2(false);
|
|
431
|
+
function copy() {
|
|
432
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
433
|
+
setCopied(true);
|
|
434
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return /* @__PURE__ */ u3("button", { onClick: copy, class: "profiler-body-download-btn profiler-text--xs", style: "cursor:pointer", children: copied ? "Copied!" : "Copy" });
|
|
414
438
|
}
|
|
415
|
-
function
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
439
|
+
function mimeToExt(mime) {
|
|
440
|
+
const m3 = mime.split(";")[0].trim().toLowerCase();
|
|
441
|
+
const map = {
|
|
442
|
+
"application/json": ".json",
|
|
443
|
+
"application/ld+json": ".jsonld",
|
|
444
|
+
"application/xml": ".xml",
|
|
445
|
+
"text/xml": ".xml",
|
|
446
|
+
"text/csv": ".csv",
|
|
447
|
+
"application/csv": ".csv",
|
|
448
|
+
"text/html": ".html",
|
|
449
|
+
"text/plain": ".txt",
|
|
450
|
+
"text/css": ".css",
|
|
451
|
+
"application/javascript": ".js",
|
|
452
|
+
"text/javascript": ".js",
|
|
453
|
+
"image/svg+xml": ".svg",
|
|
454
|
+
"image/png": ".png",
|
|
455
|
+
"image/jpeg": ".jpg",
|
|
456
|
+
"image/gif": ".gif",
|
|
457
|
+
"image/webp": ".webp",
|
|
458
|
+
"image/avif": ".avif",
|
|
459
|
+
"application/pdf": ".pdf",
|
|
460
|
+
"application/zip": ".zip",
|
|
461
|
+
"application/gzip": ".gz",
|
|
462
|
+
"application/octet-stream": ".bin"
|
|
463
|
+
};
|
|
464
|
+
return map[m3] || ".bin";
|
|
420
465
|
}
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
466
|
+
function DownloadTextButton({ text, mime }) {
|
|
467
|
+
const [url, setUrl] = d2(null);
|
|
468
|
+
y2(() => {
|
|
469
|
+
const objectUrl = URL.createObjectURL(new Blob([text], { type: mime }));
|
|
470
|
+
setUrl(objectUrl);
|
|
471
|
+
return () => URL.revokeObjectURL(objectUrl);
|
|
472
|
+
}, [text, mime]);
|
|
473
|
+
if (!url) return null;
|
|
474
|
+
return /* @__PURE__ */ u3("a", { href: url, download: `body${mimeToExt(mime)}`, class: "profiler-body-download-btn profiler-text--xs", children: "Download" });
|
|
425
475
|
}
|
|
426
476
|
function HeadersTable({ headers }) {
|
|
427
477
|
const entries = Object.entries(headers);
|
|
@@ -494,8 +544,6 @@
|
|
|
494
544
|
if (category === "xml") return formatXml(body);
|
|
495
545
|
return body;
|
|
496
546
|
}
|
|
497
|
-
var PREVIEW_LIMIT = 500;
|
|
498
|
-
var CSV_ROW_LIMIT = 10;
|
|
499
547
|
function categoryMime(category) {
|
|
500
548
|
const map = {
|
|
501
549
|
json: "application/json",
|
|
@@ -506,53 +554,8 @@
|
|
|
506
554
|
};
|
|
507
555
|
return map[category] || "text/plain";
|
|
508
556
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const map = {
|
|
512
|
-
"application/json": ".json",
|
|
513
|
-
"application/ld+json": ".jsonld",
|
|
514
|
-
"application/xml": ".xml",
|
|
515
|
-
"text/xml": ".xml",
|
|
516
|
-
"text/csv": ".csv",
|
|
517
|
-
"application/csv": ".csv",
|
|
518
|
-
"text/html": ".html",
|
|
519
|
-
"text/plain": ".txt",
|
|
520
|
-
"text/css": ".css",
|
|
521
|
-
"application/javascript": ".js",
|
|
522
|
-
"text/javascript": ".js",
|
|
523
|
-
"image/svg+xml": ".svg",
|
|
524
|
-
"image/png": ".png",
|
|
525
|
-
"image/jpeg": ".jpg",
|
|
526
|
-
"image/gif": ".gif",
|
|
527
|
-
"image/webp": ".webp",
|
|
528
|
-
"image/avif": ".avif",
|
|
529
|
-
"application/pdf": ".pdf",
|
|
530
|
-
"application/zip": ".zip",
|
|
531
|
-
"application/gzip": ".gz",
|
|
532
|
-
"application/octet-stream": ".bin"
|
|
533
|
-
};
|
|
534
|
-
return map[m3] || ".bin";
|
|
535
|
-
}
|
|
536
|
-
function CopyButton({ text }) {
|
|
537
|
-
const [copied, setCopied] = d2(false);
|
|
538
|
-
function copy() {
|
|
539
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
540
|
-
setCopied(true);
|
|
541
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
return /* @__PURE__ */ u3("button", { onClick: copy, class: "profiler-body-download-btn profiler-text--xs", style: "cursor:pointer", children: copied ? "Copied!" : "Copy" });
|
|
545
|
-
}
|
|
546
|
-
function DownloadTextButton({ text, mime }) {
|
|
547
|
-
const [url, setUrl] = d2(null);
|
|
548
|
-
y2(() => {
|
|
549
|
-
const objectUrl = URL.createObjectURL(new Blob([text], { type: mime }));
|
|
550
|
-
setUrl(objectUrl);
|
|
551
|
-
return () => URL.revokeObjectURL(objectUrl);
|
|
552
|
-
}, [text, mime]);
|
|
553
|
-
if (!url) return null;
|
|
554
|
-
return /* @__PURE__ */ u3("a", { href: url, download: `body${mimeToExt(mime)}`, class: "profiler-body-download-btn profiler-text--xs", children: "Download" });
|
|
555
|
-
}
|
|
557
|
+
var PREVIEW_LIMIT = 500;
|
|
558
|
+
var CSV_ROW_LIMIT = 10;
|
|
556
559
|
function SmartBodyPreview({ body, encoding, headers }) {
|
|
557
560
|
const [expanded, setExpanded] = d2(false);
|
|
558
561
|
const [objectUrl, setObjectUrl] = d2(null);
|
|
@@ -611,7 +614,7 @@
|
|
|
611
614
|
)
|
|
612
615
|
] });
|
|
613
616
|
}
|
|
614
|
-
const formatted = formatTextBody(body, category)
|
|
617
|
+
const formatted = formatTextBody(body, category);
|
|
615
618
|
const preview = expanded ? formatted : formatted.slice(0, PREVIEW_LIMIT);
|
|
616
619
|
const truncated = formatted.length > PREVIEW_LIMIT && !expanded;
|
|
617
620
|
return /* @__PURE__ */ u3("div", { children: [
|
|
@@ -634,6 +637,98 @@
|
|
|
634
637
|
)
|
|
635
638
|
] });
|
|
636
639
|
}
|
|
640
|
+
function HttpReqRespDetail({ request, response, backtrace }) {
|
|
641
|
+
return /* @__PURE__ */ u3("div", { style: "padding:12px 4px 4px;border-top:1px solid rgba(0,0,0,0.08);margin-top:8px", children: [
|
|
642
|
+
/* @__PURE__ */ u3("div", { style: "margin-bottom:16px", children: [
|
|
643
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--sm", style: "font-weight:600;margin-bottom:8px;color:var(--profiler-muted)", children: "REQUEST" }),
|
|
644
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Headers" }),
|
|
645
|
+
/* @__PURE__ */ u3(HeadersTable, { headers: request.headers }),
|
|
646
|
+
request.params && Object.keys(request.params).length > 0 && !request.body && /* @__PURE__ */ u3(k, { children: [
|
|
647
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-top:10px;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Params" }),
|
|
648
|
+
/* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", style: "margin:0", children: JSON.stringify(request.params, null, 2) })
|
|
649
|
+
] }),
|
|
650
|
+
request.body && /* @__PURE__ */ u3(k, { children: [
|
|
651
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-top:10px;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Body" }),
|
|
652
|
+
/* @__PURE__ */ u3(SmartBodyPreview, { body: request.body, encoding: request.body_encoding, headers: request.headers })
|
|
653
|
+
] })
|
|
654
|
+
] }),
|
|
655
|
+
/* @__PURE__ */ u3("div", { children: [
|
|
656
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--sm", style: "font-weight:600;margin-bottom:8px;color:var(--profiler-muted)", children: "RESPONSE" }),
|
|
657
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Headers" }),
|
|
658
|
+
/* @__PURE__ */ u3(HeadersTable, { headers: response.headers }),
|
|
659
|
+
response.body && /* @__PURE__ */ u3(k, { children: [
|
|
660
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-top:10px;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Body" }),
|
|
661
|
+
/* @__PURE__ */ u3(SmartBodyPreview, { body: response.body, encoding: response.body_encoding, headers: response.headers })
|
|
662
|
+
] })
|
|
663
|
+
] }),
|
|
664
|
+
backtrace && backtrace.length > 1 && /* @__PURE__ */ u3("div", { style: "margin-top:12px", children: [
|
|
665
|
+
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Backtrace" }),
|
|
666
|
+
backtrace.map((frame, i3) => /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "padding:2px 0", children: frame }, i3))
|
|
667
|
+
] })
|
|
668
|
+
] });
|
|
669
|
+
}
|
|
670
|
+
function HttpCardHeader({ method, url, copyUrl: copyUrlOverride, status, duration, curlCommand, expandable, open, onToggle }) {
|
|
671
|
+
const [copiedUrl, setCopiedUrl] = d2(false);
|
|
672
|
+
const [copiedCurl, setCopiedCurl] = d2(false);
|
|
673
|
+
function handleCopyUrl(e3) {
|
|
674
|
+
e3.stopPropagation();
|
|
675
|
+
navigator.clipboard.writeText(copyUrlOverride ?? url).then(() => {
|
|
676
|
+
setCopiedUrl(true);
|
|
677
|
+
setTimeout(() => setCopiedUrl(false), 2e3);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
function handleCopyCurl(e3) {
|
|
681
|
+
e3.stopPropagation();
|
|
682
|
+
navigator.clipboard.writeText(curlCommand).then(() => {
|
|
683
|
+
setCopiedCurl(true);
|
|
684
|
+
setTimeout(() => setCopiedCurl(false), 2e3);
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
return /* @__PURE__ */ u3(
|
|
688
|
+
"div",
|
|
689
|
+
{
|
|
690
|
+
class: "profiler-ajax-card__row",
|
|
691
|
+
style: expandable ? "cursor:pointer;user-select:none" : void 0,
|
|
692
|
+
onClick: expandable ? onToggle : void 0,
|
|
693
|
+
children: [
|
|
694
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-3", style: "min-width:0;flex:1", children: [
|
|
695
|
+
expandable && /* @__PURE__ */ u3("span", { style: "font-size:11px;color:var(--profiler-muted)", children: open ? "\u25BE" : "\u25B8" }),
|
|
696
|
+
/* @__PURE__ */ u3("span", { class: `profiler-ajax-card__method badge-${methodBadge(method)}`, children: method }),
|
|
697
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-ajax-card__path", style: "word-break:break-all", children: url }),
|
|
698
|
+
/* @__PURE__ */ u3(
|
|
699
|
+
"button",
|
|
700
|
+
{
|
|
701
|
+
onClick: handleCopyUrl,
|
|
702
|
+
class: "profiler-body-download-btn profiler-text--xs",
|
|
703
|
+
style: "flex-shrink:0;cursor:pointer",
|
|
704
|
+
title: "Copy URL",
|
|
705
|
+
children: copiedUrl ? "Copied!" : "Copy URL"
|
|
706
|
+
}
|
|
707
|
+
),
|
|
708
|
+
/* @__PURE__ */ u3(
|
|
709
|
+
"button",
|
|
710
|
+
{
|
|
711
|
+
onClick: handleCopyCurl,
|
|
712
|
+
class: "profiler-body-download-btn profiler-text--xs",
|
|
713
|
+
style: "flex-shrink:0;cursor:pointer",
|
|
714
|
+
title: "Copy as cURL",
|
|
715
|
+
children: copiedCurl ? "Copied!" : "Copy as cURL"
|
|
716
|
+
}
|
|
717
|
+
)
|
|
718
|
+
] }),
|
|
719
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", style: "flex-shrink:0", children: [
|
|
720
|
+
/* @__PURE__ */ u3("span", { class: `badge-${statusBadge(status)}`, children: status === 0 ? "ERR" : status }),
|
|
721
|
+
/* @__PURE__ */ u3("span", { class: duration >= 500 ? "badge-error" : duration >= 100 ? "badge-warning" : "badge-success", children: [
|
|
722
|
+
duration.toFixed(2),
|
|
723
|
+
" ms"
|
|
724
|
+
] })
|
|
725
|
+
] })
|
|
726
|
+
]
|
|
727
|
+
}
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// app/assets/typescript/profiler/components/dashboard/tabs/HttpTab.tsx
|
|
637
732
|
function waterfallBarColor(status, duration) {
|
|
638
733
|
if (status === 0 || status >= 500) return "var(--profiler-error, #ef4444)";
|
|
639
734
|
if (status >= 400) return "var(--profiler-warning, #f59e0b)";
|
|
@@ -711,55 +806,21 @@
|
|
|
711
806
|
}
|
|
712
807
|
function HttpRequestDetail({ req, index, threshold }) {
|
|
713
808
|
const [open, setOpen] = d2(false);
|
|
714
|
-
const [copiedUrl, setCopiedUrl] = d2(false);
|
|
715
|
-
const [copiedCurl, setCopiedCurl] = d2(false);
|
|
716
809
|
const isError = req.status >= 400 || req.status === 0;
|
|
717
810
|
const isSlow = req.duration >= threshold;
|
|
718
811
|
const cardCls = isError ? "profiler-ajax-card--error" : isSlow ? "profiler-ajax-card--warning" : "profiler-ajax-card--success";
|
|
719
|
-
function copyUrl(e3) {
|
|
720
|
-
e3.stopPropagation();
|
|
721
|
-
navigator.clipboard.writeText(req.url).then(() => {
|
|
722
|
-
setCopiedUrl(true);
|
|
723
|
-
setTimeout(() => setCopiedUrl(false), 2e3);
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
function copyCurl() {
|
|
727
|
-
navigator.clipboard.writeText(buildCurl(req)).then(() => {
|
|
728
|
-
setCopiedCurl(true);
|
|
729
|
-
setTimeout(() => setCopiedCurl(false), 2e3);
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
812
|
return /* @__PURE__ */ u3("div", { class: `profiler-ajax-card ${cardCls}`, style: "margin-bottom:8px", children: [
|
|
733
813
|
/* @__PURE__ */ u3(
|
|
734
|
-
|
|
814
|
+
HttpCardHeader,
|
|
735
815
|
{
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
/* @__PURE__ */ u3(
|
|
745
|
-
"button",
|
|
746
|
-
{
|
|
747
|
-
onClick: copyUrl,
|
|
748
|
-
class: "profiler-body-download-btn profiler-text--xs",
|
|
749
|
-
style: "flex-shrink:0;cursor:pointer",
|
|
750
|
-
title: "Copy URL",
|
|
751
|
-
children: copiedUrl ? "Copied!" : "Copy URL"
|
|
752
|
-
}
|
|
753
|
-
)
|
|
754
|
-
] }),
|
|
755
|
-
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", style: "flex-shrink:0", children: [
|
|
756
|
-
/* @__PURE__ */ u3("span", { class: `badge-${statusBadge(req.status)}`, children: req.status === 0 ? "ERR" : req.status }),
|
|
757
|
-
/* @__PURE__ */ u3("span", { class: req.duration >= 500 ? "badge-error" : req.duration >= 100 ? "badge-warning" : "badge-success", children: [
|
|
758
|
-
req.duration.toFixed(2),
|
|
759
|
-
" ms"
|
|
760
|
-
] })
|
|
761
|
-
] })
|
|
762
|
-
]
|
|
816
|
+
method: req.method,
|
|
817
|
+
url: req.url,
|
|
818
|
+
status: req.status,
|
|
819
|
+
duration: req.duration,
|
|
820
|
+
curlCommand: buildCurl(req),
|
|
821
|
+
expandable: true,
|
|
822
|
+
open,
|
|
823
|
+
onToggle: () => setOpen((o3) => !o3)
|
|
763
824
|
}
|
|
764
825
|
),
|
|
765
826
|
/* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
|
|
@@ -772,31 +833,14 @@
|
|
|
772
833
|
req.backtrace && req.backtrace.length > 0 && /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", style: "margin-left:12px", children: req.backtrace[0] })
|
|
773
834
|
] }),
|
|
774
835
|
req.error && /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--error", children: req.error }) }),
|
|
775
|
-
open && /* @__PURE__ */ u3(
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
/* @__PURE__ */ u3(SmartBodyPreview, { body: req.request_body, encoding: req.request_body_encoding, headers: req.request_headers || {} })
|
|
784
|
-
] })
|
|
785
|
-
] }),
|
|
786
|
-
/* @__PURE__ */ u3("div", { children: [
|
|
787
|
-
/* @__PURE__ */ u3("div", { class: "profiler-text--sm", style: "font-weight:600;margin-bottom:8px;color:var(--profiler-muted)", children: "RESPONSE" }),
|
|
788
|
-
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Headers" }),
|
|
789
|
-
/* @__PURE__ */ u3(HeadersTable, { headers: req.response_headers || {} }),
|
|
790
|
-
req.response_body && /* @__PURE__ */ u3(k, { children: [
|
|
791
|
-
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-top:10px;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Body" }),
|
|
792
|
-
/* @__PURE__ */ u3(SmartBodyPreview, { body: req.response_body, encoding: req.response_body_encoding, headers: req.response_headers || {} })
|
|
793
|
-
] })
|
|
794
|
-
] }),
|
|
795
|
-
req.backtrace && req.backtrace.length > 1 && /* @__PURE__ */ u3("div", { style: "margin-top:12px", children: [
|
|
796
|
-
/* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Backtrace" }),
|
|
797
|
-
req.backtrace.map((frame, i3) => /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "padding:2px 0", children: frame }, i3))
|
|
798
|
-
] })
|
|
799
|
-
] })
|
|
836
|
+
open && /* @__PURE__ */ u3(
|
|
837
|
+
HttpReqRespDetail,
|
|
838
|
+
{
|
|
839
|
+
request: { headers: req.request_headers || {}, body: req.request_body, body_encoding: req.request_body_encoding },
|
|
840
|
+
response: { headers: req.response_headers || {}, body: req.response_body, body_encoding: req.response_body_encoding },
|
|
841
|
+
backtrace: req.backtrace
|
|
842
|
+
}
|
|
843
|
+
)
|
|
800
844
|
] });
|
|
801
845
|
}
|
|
802
846
|
function HttpTab({ httpData }) {
|
|
@@ -946,9 +990,24 @@
|
|
|
946
990
|
throw new Error(body.error ?? `Request failed (${res.status})`);
|
|
947
991
|
}
|
|
948
992
|
}
|
|
949
|
-
function
|
|
993
|
+
async function resetEnvVar(key) {
|
|
994
|
+
const res = await fetch(`/_profiler/api/env_vars/reset?key=${encodeURIComponent(key)}`, {
|
|
995
|
+
method: "DELETE"
|
|
996
|
+
});
|
|
997
|
+
if (!res.ok) {
|
|
998
|
+
const body = await res.json().catch(() => ({}));
|
|
999
|
+
throw new Error(body.error ?? `Request failed (${res.status})`);
|
|
1000
|
+
}
|
|
1001
|
+
return res.json();
|
|
1002
|
+
}
|
|
1003
|
+
async function resetAllEnvVars() {
|
|
1004
|
+
const res = await fetch("/_profiler/api/env_vars/reset_all", { method: "DELETE" });
|
|
1005
|
+
if (!res.ok) throw new Error(`Request failed (${res.status})`);
|
|
1006
|
+
}
|
|
1007
|
+
function EnvTab({ envData, readOnly: forceReadOnly = false }) {
|
|
950
1008
|
const initial = T2(() => ({ ...envData?.variables ?? {} }), []);
|
|
951
1009
|
const [variables, setVariables] = d2(initial);
|
|
1010
|
+
const [overrides, setOverrides] = d2(envData?.overrides ?? {});
|
|
952
1011
|
const [total, setTotal] = d2(envData?.total ?? 0);
|
|
953
1012
|
const [search, setSearch] = d2("");
|
|
954
1013
|
const [collapsedGroups, setCollapsedGroups] = d2(/* @__PURE__ */ new Set());
|
|
@@ -961,7 +1020,7 @@
|
|
|
961
1020
|
const [refreshing, setRefreshing] = d2(false);
|
|
962
1021
|
const [unmaskedKeys, setUnmaskedKeys] = d2(loadUnmasked);
|
|
963
1022
|
const [copiedId, setCopiedId] = d2(null);
|
|
964
|
-
const [readOnly, setReadOnly] = d2(
|
|
1023
|
+
const [readOnly, setReadOnly] = d2(forceReadOnly);
|
|
965
1024
|
const [showImport, setShowImport] = d2(false);
|
|
966
1025
|
const [importContent, setImportContent] = d2("");
|
|
967
1026
|
const editInputRef = A2(null);
|
|
@@ -969,7 +1028,7 @@
|
|
|
969
1028
|
if (editingKey !== null) editInputRef.current?.focus();
|
|
970
1029
|
}, [editingKey]);
|
|
971
1030
|
y2(() => {
|
|
972
|
-
refresh();
|
|
1031
|
+
if (!forceReadOnly) refresh();
|
|
973
1032
|
}, []);
|
|
974
1033
|
const showFlash = (type, message) => {
|
|
975
1034
|
setFlash({ type, message });
|
|
@@ -1009,6 +1068,7 @@
|
|
|
1009
1068
|
const data = await res.json();
|
|
1010
1069
|
setVariables(data.variables);
|
|
1011
1070
|
setTotal(data.total);
|
|
1071
|
+
setOverrides(data.overrides ?? {});
|
|
1012
1072
|
showFlash("success", `Refreshed \u2014 ${data.total} variables`);
|
|
1013
1073
|
} catch (e3) {
|
|
1014
1074
|
showFlash("error", e3.message ?? "Failed to refresh");
|
|
@@ -1122,6 +1182,45 @@
|
|
|
1122
1182
|
if (e3.key === "Enter") saveEdit();
|
|
1123
1183
|
if (e3.key === "Escape") cancelEdit();
|
|
1124
1184
|
};
|
|
1185
|
+
const resetVar = async (key) => {
|
|
1186
|
+
setSaving(true);
|
|
1187
|
+
try {
|
|
1188
|
+
const data = await resetEnvVar(key);
|
|
1189
|
+
setOverrides((prev) => {
|
|
1190
|
+
const n2 = { ...prev };
|
|
1191
|
+
delete n2[key];
|
|
1192
|
+
return n2;
|
|
1193
|
+
});
|
|
1194
|
+
setVariables((prev) => {
|
|
1195
|
+
const n2 = { ...prev };
|
|
1196
|
+
if (data.value === null) {
|
|
1197
|
+
delete n2[key];
|
|
1198
|
+
} else {
|
|
1199
|
+
n2[key] = data.value;
|
|
1200
|
+
}
|
|
1201
|
+
return n2;
|
|
1202
|
+
});
|
|
1203
|
+
showFlash("success", `${key} reset to original`);
|
|
1204
|
+
} catch (e3) {
|
|
1205
|
+
showFlash("error", e3.message ?? "Failed to reset");
|
|
1206
|
+
} finally {
|
|
1207
|
+
setSaving(false);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
const doResetAll = async () => {
|
|
1211
|
+
setSaving(true);
|
|
1212
|
+
try {
|
|
1213
|
+
await resetAllEnvVars();
|
|
1214
|
+
setOverrides({});
|
|
1215
|
+
await refresh();
|
|
1216
|
+
showFlash("success", "All overrides reset");
|
|
1217
|
+
} catch (e3) {
|
|
1218
|
+
showFlash("error", e3.message ?? "Failed to reset all");
|
|
1219
|
+
} finally {
|
|
1220
|
+
setSaving(false);
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
const overrideCount = Object.keys(overrides).length;
|
|
1125
1224
|
const allEntries = Object.entries(variables);
|
|
1126
1225
|
const filteredEntries = T2(() => {
|
|
1127
1226
|
const q2 = search.toLowerCase().trim();
|
|
@@ -1161,11 +1260,27 @@
|
|
|
1161
1260
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-mb-4", style: "align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;", children: [
|
|
1162
1261
|
/* @__PURE__ */ u3("h2", { class: "profiler-section__header", style: "margin:0;", children: "Environment Variables" }),
|
|
1163
1262
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
|
|
1164
|
-
/* @__PURE__ */ u3(
|
|
1165
|
-
|
|
1263
|
+
!forceReadOnly && /* @__PURE__ */ u3(k, { children: [
|
|
1264
|
+
/* @__PURE__ */ u3("button", { onClick: refresh, disabled: refreshing, style: headerBtn, children: refreshing ? "\u2026" : "\u21BA Refresh" }),
|
|
1265
|
+
/* @__PURE__ */ u3("button", { onClick: () => setShowImport((v3) => !v3), style: `${headerBtn}${showImport ? "border-color:var(--profiler-accent);color:var(--profiler-accent);" : ""}`, children: "\u2B06 Import" })
|
|
1266
|
+
] }),
|
|
1166
1267
|
/* @__PURE__ */ u3("button", { onClick: exportEnv, style: headerBtn, children: "\u2B07 Export" }),
|
|
1167
1268
|
/* @__PURE__ */ u3("button", { onClick: toggleAllMasks, style: headerBtn, children: allRevealed ? "\u{1F648} Mask all" : "\u{1F441} Reveal all" }),
|
|
1168
|
-
/* @__PURE__ */ u3(
|
|
1269
|
+
!forceReadOnly && overrideCount > 0 && /* @__PURE__ */ u3(
|
|
1270
|
+
"button",
|
|
1271
|
+
{
|
|
1272
|
+
onClick: doResetAll,
|
|
1273
|
+
disabled: saving,
|
|
1274
|
+
title: "Remove all Sidekiq overrides and restore original values",
|
|
1275
|
+
style: `${headerBtn}border-color:var(--profiler-warning,#f59e0b);color:var(--profiler-warning,#f59e0b);`,
|
|
1276
|
+
children: [
|
|
1277
|
+
"\u21A9 Reset all (",
|
|
1278
|
+
overrideCount,
|
|
1279
|
+
")"
|
|
1280
|
+
]
|
|
1281
|
+
}
|
|
1282
|
+
),
|
|
1283
|
+
!forceReadOnly && /* @__PURE__ */ u3(
|
|
1169
1284
|
"button",
|
|
1170
1285
|
{
|
|
1171
1286
|
onClick: () => setReadOnly((v3) => !v3),
|
|
@@ -1176,7 +1291,11 @@
|
|
|
1176
1291
|
)
|
|
1177
1292
|
] })
|
|
1178
1293
|
] }),
|
|
1179
|
-
/* @__PURE__ */ u3("div", { style: "background:var(--profiler-
|
|
1294
|
+
forceReadOnly ? /* @__PURE__ */ u3("div", { style: "background:var(--profiler-bg-subtle,rgba(0,0,0,0.03));border:1px solid var(--profiler-border);border-radius:6px;padding:8px 12px;margin-bottom:16px;font-size:12px;color:var(--profiler-text-muted);", children: [
|
|
1295
|
+
"Snapshot taken at request time \u2014 modify env vars in ",
|
|
1296
|
+
/* @__PURE__ */ u3("a", { href: "/_profiler?section=env", style: "color:var(--profiler-accent);", children: "/_profiler?section=env" }),
|
|
1297
|
+
"."
|
|
1298
|
+
] }) : /* @__PURE__ */ u3("div", { style: "background:var(--profiler-warning-bg,rgba(245,158,11,0.1));border:1px solid var(--profiler-warning,#f59e0b);border-radius:6px;padding:8px 12px;margin-bottom:16px;font-size:12px;color:var(--profiler-warning,#f59e0b);", children: "\u26A0 Changes affect the current process only \u2014 for development use." }),
|
|
1180
1299
|
flash && /* @__PURE__ */ u3("div", { style: `background:${flash.type === "success" ? "var(--profiler-success-bg,rgba(34,197,94,0.1))" : "var(--profiler-error-bg,rgba(239,68,68,0.08))"};border:1px solid ${flash.type === "success" ? "var(--profiler-success,#22c55e)" : "var(--profiler-error,#ef4444)"};border-radius:6px;padding:6px 12px;margin-bottom:12px;font-size:12px;color:${flash.type === "success" ? "var(--profiler-success,#22c55e)" : "var(--profiler-error,#ef4444)"};`, children: [
|
|
1181
1300
|
flash.type === "success" ? "\u2713" : "\u2717",
|
|
1182
1301
|
" ",
|
|
@@ -1271,7 +1390,8 @@
|
|
|
1271
1390
|
const isCopiedKey = copiedId === `__key__${key}`;
|
|
1272
1391
|
const wasModified = initial[key] !== void 0 && initial[key] !== value;
|
|
1273
1392
|
const wasAdded = initial[key] === void 0;
|
|
1274
|
-
const
|
|
1393
|
+
const override = overrides[key];
|
|
1394
|
+
const diffStyle = override ? "border-left:3px solid var(--profiler-accent);" : wasModified ? "border-left:3px solid #f59e0b;" : wasAdded ? "border-left:3px solid var(--profiler-success,#22c55e);" : "";
|
|
1275
1395
|
return /* @__PURE__ */ u3("tr", { style: diffStyle, children: [
|
|
1276
1396
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("div", { class: "profiler-flex", style: "align-items:center;gap:4px;", children: [
|
|
1277
1397
|
/* @__PURE__ */ u3("code", { class: "profiler-text--xs profiler-text--mono", children: key }),
|
|
@@ -1324,7 +1444,13 @@
|
|
|
1324
1444
|
}
|
|
1325
1445
|
)
|
|
1326
1446
|
] }),
|
|
1327
|
-
|
|
1447
|
+
override && /* @__PURE__ */ u3("div", { style: "font-size:10px;color:var(--profiler-accent);margin-top:2px;font-family:monospace;", children: [
|
|
1448
|
+
"workers \u2190 ",
|
|
1449
|
+
override.value === "__profiler_deleted__" ? "(deleted)" : override.value,
|
|
1450
|
+
" \xB7 original: ",
|
|
1451
|
+
override.original ?? "(unset)"
|
|
1452
|
+
] }),
|
|
1453
|
+
!override && wasModified && /* @__PURE__ */ u3("div", { style: "font-size:10px;color:#f59e0b;margin-top:2px;font-family:monospace;", children: [
|
|
1328
1454
|
"was: ",
|
|
1329
1455
|
initial[key]
|
|
1330
1456
|
] })
|
|
@@ -1362,7 +1488,13 @@
|
|
|
1362
1488
|
}
|
|
1363
1489
|
)
|
|
1364
1490
|
] }),
|
|
1365
|
-
|
|
1491
|
+
override && /* @__PURE__ */ u3("div", { style: "font-size:10px;color:var(--profiler-accent);margin-top:2px;font-family:monospace;", children: [
|
|
1492
|
+
"workers \u2190 ",
|
|
1493
|
+
override.value === "__profiler_deleted__" ? "(deleted)" : unmasked ? override.value : "\u2022".repeat(Math.min((override.value ?? "").length, 20)),
|
|
1494
|
+
" \xB7 original: ",
|
|
1495
|
+
override.original == null ? "(unset)" : unmasked ? override.original : "\u2022".repeat(Math.min(override.original.length, 20))
|
|
1496
|
+
] }),
|
|
1497
|
+
!override && wasModified && /* @__PURE__ */ u3("div", { style: "font-size:10px;color:#f59e0b;margin-top:2px;font-family:monospace;", children: [
|
|
1366
1498
|
"was: ",
|
|
1367
1499
|
unmasked ? isQuoted(initial[key]) ? `"${initial[key]}"` : initial[key] : "\u2022".repeat(Math.min(initial[key].length, 20))
|
|
1368
1500
|
] })
|
|
@@ -1370,11 +1502,18 @@
|
|
|
1370
1502
|
!readOnly && /* @__PURE__ */ u3("td", { style: "text-align:right;white-space:nowrap;", children: isEditing ? /* @__PURE__ */ u3(k, { children: [
|
|
1371
1503
|
/* @__PURE__ */ u3("button", { onClick: saveEdit, disabled: saving, title: "Save", style: "background:none;border:none;cursor:pointer;color:var(--profiler-success,#22c55e);font-size:14px;padding:0 4px;", children: "\u2713" }),
|
|
1372
1504
|
/* @__PURE__ */ u3("button", { onClick: cancelEdit, disabled: saving, title: "Cancel", style: "background:none;border:none;cursor:pointer;color:var(--profiler-text-muted);font-size:14px;padding:0 4px;", children: "\u2717" })
|
|
1373
|
-
] }) : isBool(value) ? /* @__PURE__ */ u3(k, { children: [
|
|
1374
|
-
/* @__PURE__ */ u3("button", { onClick: () => startEdit(key), disabled: saving, style: "background:none;border:none;cursor:pointer;color:var(--profiler-accent);font-size:11px;padding:0 4px;", children: "Edit" }),
|
|
1375
|
-
/* @__PURE__ */ u3("button", { onClick: () => deleteVar(key), disabled: saving, style: "background:none;border:none;cursor:pointer;color:var(--profiler-error,#ef4444);font-size:11px;padding:0 4px;", children: "Delete" })
|
|
1376
1505
|
] }) : /* @__PURE__ */ u3(k, { children: [
|
|
1377
1506
|
/* @__PURE__ */ u3("button", { onClick: () => startEdit(key), disabled: saving, style: "background:none;border:none;cursor:pointer;color:var(--profiler-accent);font-size:11px;padding:0 4px;", children: "Edit" }),
|
|
1507
|
+
override && /* @__PURE__ */ u3(
|
|
1508
|
+
"button",
|
|
1509
|
+
{
|
|
1510
|
+
onClick: () => resetVar(key),
|
|
1511
|
+
disabled: saving,
|
|
1512
|
+
title: `Reset to original: ${override.original ?? "(unset)"}`,
|
|
1513
|
+
style: "background:none;border:none;cursor:pointer;color:var(--profiler-warning,#f59e0b);font-size:11px;padding:0 4px;",
|
|
1514
|
+
children: "\u21A9 Reset"
|
|
1515
|
+
}
|
|
1516
|
+
),
|
|
1378
1517
|
/* @__PURE__ */ u3("button", { onClick: () => deleteVar(key), disabled: saving, style: "background:none;border:none;cursor:pointer;color:var(--profiler-error,#ef4444);font-size:11px;padding:0 4px;", children: "Delete" })
|
|
1379
1518
|
] }) })
|
|
1380
1519
|
] }, key);
|
|
@@ -2100,107 +2239,43 @@
|
|
|
2100
2239
|
return parts.join(" \\\n");
|
|
2101
2240
|
}
|
|
2102
2241
|
function RequestTab({ profile }) {
|
|
2103
|
-
const [copied, setCopied] = d2(false);
|
|
2104
|
-
const curl = buildCurl2(profile);
|
|
2105
2242
|
const routeData = profile.collectors_data?.request ?? {};
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Status" }),
|
|
2125
|
-
/* @__PURE__ */ u3("td", { children: profile.status })
|
|
2126
|
-
] }),
|
|
2127
|
-
/* @__PURE__ */ u3("tr", { children: [
|
|
2128
|
-
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Duration" }),
|
|
2129
|
-
/* @__PURE__ */ u3("td", { children: [
|
|
2130
|
-
profile.duration.toFixed(2),
|
|
2131
|
-
" ms"
|
|
2132
|
-
] })
|
|
2133
|
-
] }),
|
|
2134
|
-
routeData.controller_action && /* @__PURE__ */ u3("tr", { children: [
|
|
2135
|
-
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Controller#Action" }),
|
|
2136
|
-
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: routeData.controller_action })
|
|
2137
|
-
] }),
|
|
2138
|
-
routeData.route_name && /* @__PURE__ */ u3("tr", { children: [
|
|
2139
|
-
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Route Name" }),
|
|
2140
|
-
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: routeData.route_name })
|
|
2141
|
-
] }),
|
|
2142
|
-
routeData.route_pattern && /* @__PURE__ */ u3("tr", { children: [
|
|
2143
|
-
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Route Pattern" }),
|
|
2144
|
-
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: routeData.route_pattern })
|
|
2145
|
-
] }),
|
|
2146
|
-
routeData.route_params && Object.keys(routeData.route_params).length > 0 && /* @__PURE__ */ u3("tr", { children: [
|
|
2147
|
-
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Route Params" }),
|
|
2148
|
-
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: Object.entries(routeData.route_params).map(([k3, v3]) => `${k3}: ${v3}`).join(", ") })
|
|
2243
|
+
const hasParams = profile.params && Object.keys(profile.params).length > 0;
|
|
2244
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-ajax-card profiler-ajax-card--success", style: "margin-bottom:8px;background:var(--profiler-bg-elevated);transform:translateX(3px);box-shadow:var(--profiler-shadow-sm);transition:none", children: [
|
|
2245
|
+
/* @__PURE__ */ u3(
|
|
2246
|
+
HttpCardHeader,
|
|
2247
|
+
{
|
|
2248
|
+
method: profile.method,
|
|
2249
|
+
url: profile.path,
|
|
2250
|
+
copyUrl: `http://localhost:3000${profile.path}`,
|
|
2251
|
+
status: profile.status,
|
|
2252
|
+
duration: profile.duration,
|
|
2253
|
+
curlCommand: buildCurl2(profile)
|
|
2254
|
+
}
|
|
2255
|
+
),
|
|
2256
|
+
(routeData.controller_action || routeData.route_pattern) && /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
|
|
2257
|
+
routeData.controller_action && /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", style: "margin-right:16px", children: routeData.controller_action }),
|
|
2258
|
+
routeData.route_pattern && /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", style: "font-family:monospace", children: [
|
|
2259
|
+
routeData.route_name ? `${routeData.route_name} \xB7 ` : "",
|
|
2260
|
+
routeData.route_pattern
|
|
2149
2261
|
] })
|
|
2150
2262
|
] }),
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
] }, k3)) })
|
|
2157
|
-
] }),
|
|
2158
|
-
profile.params && Object.keys(profile.params).length > 0 && /* @__PURE__ */ u3(k, { children: [
|
|
2159
|
-
/* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Request Params" }),
|
|
2160
|
-
/* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", children: JSON.stringify(profile.params, null, 2) })
|
|
2161
|
-
] }),
|
|
2162
|
-
profile.request_body && /* @__PURE__ */ u3(k, { children: [
|
|
2163
|
-
/* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Request Body" }),
|
|
2164
|
-
/* @__PURE__ */ u3(
|
|
2165
|
-
SmartBodyPreview,
|
|
2166
|
-
{
|
|
2263
|
+
/* @__PURE__ */ u3(
|
|
2264
|
+
HttpReqRespDetail,
|
|
2265
|
+
{
|
|
2266
|
+
request: {
|
|
2267
|
+
headers: profile.headers ?? {},
|
|
2167
2268
|
body: profile.request_body,
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
profile.response_headers && Object.keys(profile.response_headers).length > 0 && /* @__PURE__ */ u3(k, { children: [
|
|
2174
|
-
/* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Response Headers" }),
|
|
2175
|
-
/* @__PURE__ */ u3("table", { children: Object.entries(profile.response_headers).map(([k3, v3]) => /* @__PURE__ */ u3("tr", { children: [
|
|
2176
|
-
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", style: "width: 200px;", children: k3 }),
|
|
2177
|
-
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: String(v3) })
|
|
2178
|
-
] }, k3)) })
|
|
2179
|
-
] }),
|
|
2180
|
-
profile.response_body && /* @__PURE__ */ u3(k, { children: [
|
|
2181
|
-
/* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Response Body" }),
|
|
2182
|
-
/* @__PURE__ */ u3(
|
|
2183
|
-
SmartBodyPreview,
|
|
2184
|
-
{
|
|
2269
|
+
body_encoding: profile.request_body_encoding,
|
|
2270
|
+
params: hasParams ? profile.params : void 0
|
|
2271
|
+
},
|
|
2272
|
+
response: {
|
|
2273
|
+
headers: profile.response_headers ?? {},
|
|
2185
2274
|
body: profile.response_body,
|
|
2186
|
-
|
|
2187
|
-
headers: profile.response_headers ?? {}
|
|
2275
|
+
body_encoding: profile.response_body_encoding
|
|
2188
2276
|
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
/* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Curl Command" }),
|
|
2192
|
-
/* @__PURE__ */ u3("div", { style: "position: relative;", children: [
|
|
2193
|
-
/* @__PURE__ */ u3(
|
|
2194
|
-
"button",
|
|
2195
|
-
{
|
|
2196
|
-
onClick: copyToClipboard,
|
|
2197
|
-
class: "profiler-body-download-btn",
|
|
2198
|
-
style: "position: absolute; top: 8px; right: 8px; z-index: 1;",
|
|
2199
|
-
children: copied ? "Copied!" : "Copy"
|
|
2200
|
-
}
|
|
2201
|
-
),
|
|
2202
|
-
/* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", style: "padding-right: 80px;", children: curl })
|
|
2203
|
-
] })
|
|
2277
|
+
}
|
|
2278
|
+
)
|
|
2204
2279
|
] });
|
|
2205
2280
|
}
|
|
2206
2281
|
|
|
@@ -3081,12 +3156,6 @@
|
|
|
3081
3156
|
custom: "Custom",
|
|
3082
3157
|
method: "Method"
|
|
3083
3158
|
};
|
|
3084
|
-
function formatBytes3(bytes) {
|
|
3085
|
-
if (bytes < 0) return "\u2014";
|
|
3086
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
3087
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3088
|
-
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
3089
|
-
}
|
|
3090
3159
|
function FlameGraphTab({ flamegraphData, perfData, functionProfileData }) {
|
|
3091
3160
|
const canvasRef = A2(null);
|
|
3092
3161
|
const containerRef = A2(null);
|
|
@@ -3592,7 +3661,7 @@
|
|
|
3592
3661
|
] }),
|
|
3593
3662
|
showMemory && /* @__PURE__ */ u3("div", { class: "stat-item", children: [
|
|
3594
3663
|
/* @__PURE__ */ u3("span", { class: "stat-label", children: "Memory" }),
|
|
3595
|
-
/* @__PURE__ */ u3("span", { class: "stat-value", children:
|
|
3664
|
+
/* @__PURE__ */ u3("span", { class: "stat-value", children: formatBytes(data.total_memory_bytes ?? 0) })
|
|
3596
3665
|
] })
|
|
3597
3666
|
] }),
|
|
3598
3667
|
rootCalls.length > 0 && /* @__PURE__ */ u3(
|
|
@@ -3698,7 +3767,7 @@
|
|
|
3698
3767
|
" ",
|
|
3699
3768
|
/* @__PURE__ */ u3("small", { children: "obj" })
|
|
3700
3769
|
] }),
|
|
3701
|
-
showMemory && /* @__PURE__ */ u3("td", { class: "profiler-text--right profiler-text--muted", children:
|
|
3770
|
+
showMemory && /* @__PURE__ */ u3("td", { class: "profiler-text--right profiler-text--muted", children: formatBytes(fn.memory_bytes) })
|
|
3702
3771
|
]
|
|
3703
3772
|
},
|
|
3704
3773
|
i3
|
|
@@ -3835,15 +3904,6 @@
|
|
|
3835
3904
|
}
|
|
3836
3905
|
|
|
3837
3906
|
// app/assets/typescript/profiler/components/dashboard/tabs/AjaxTab.tsx
|
|
3838
|
-
function methodBadge2(method) {
|
|
3839
|
-
const map = { GET: "info", POST: "success", PUT: "warning", PATCH: "warning", DELETE: "error" };
|
|
3840
|
-
return map[method] || "default";
|
|
3841
|
-
}
|
|
3842
|
-
function statusBadge2(status) {
|
|
3843
|
-
if (status >= 200 && status < 300) return "success";
|
|
3844
|
-
if (status >= 400) return "error";
|
|
3845
|
-
return "warning";
|
|
3846
|
-
}
|
|
3847
3907
|
function AjaxTab({ ajaxData }) {
|
|
3848
3908
|
if (!ajaxData?.requests?.length) {
|
|
3849
3909
|
return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
|
|
@@ -3879,7 +3939,7 @@
|
|
|
3879
3939
|
/* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm", children: [
|
|
3880
3940
|
/* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-3", children: "By Method" }),
|
|
3881
3941
|
Object.entries(ajaxData.by_method).map(([method, count]) => /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
|
|
3882
|
-
/* @__PURE__ */ u3("span", { class: `badge-${
|
|
3942
|
+
/* @__PURE__ */ u3("span", { class: `badge-${methodBadge(method)}`, children: method }),
|
|
3883
3943
|
/* @__PURE__ */ u3("strong", { children: count })
|
|
3884
3944
|
] }, method))
|
|
3885
3945
|
] }),
|
|
@@ -3895,11 +3955,11 @@
|
|
|
3895
3955
|
ajaxData.requests.map((req, index) => /* @__PURE__ */ u3("div", { class: `profiler-ajax-card profiler-ajax-card--${req.status >= 200 && req.status < 300 ? "success" : "error"}`, children: [
|
|
3896
3956
|
/* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
|
|
3897
3957
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-3", children: [
|
|
3898
|
-
/* @__PURE__ */ u3("span", { class: `profiler-ajax-card__method badge-${
|
|
3958
|
+
/* @__PURE__ */ u3("span", { class: `profiler-ajax-card__method badge-${methodBadge(req.method)}`, children: req.method }),
|
|
3899
3959
|
/* @__PURE__ */ u3("strong", { class: "profiler-ajax-card__path", children: req.path })
|
|
3900
3960
|
] }),
|
|
3901
3961
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
|
|
3902
|
-
/* @__PURE__ */ u3("span", { class: `badge-${
|
|
3962
|
+
/* @__PURE__ */ u3("span", { class: `badge-${statusBadge(req.status)}`, children: req.status }),
|
|
3903
3963
|
/* @__PURE__ */ u3("span", { class: req.duration >= 500 ? "badge-error" : req.duration >= 100 ? "badge-warning" : "badge-success", children: [
|
|
3904
3964
|
req.duration.toFixed(2),
|
|
3905
3965
|
" ms"
|
|
@@ -4420,7 +4480,7 @@
|
|
|
4420
4480
|
activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] }),
|
|
4421
4481
|
activeTab === "i18n" && /* @__PURE__ */ u3(I18nTab, { i18nData: cd["i18n"] }),
|
|
4422
4482
|
activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs }),
|
|
4423
|
-
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"] })
|
|
4483
|
+
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"], readOnly: true })
|
|
4424
4484
|
] })
|
|
4425
4485
|
] }),
|
|
4426
4486
|
!embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
|
|
@@ -4483,12 +4543,15 @@
|
|
|
4483
4543
|
|
|
4484
4544
|
// app/assets/typescript/profiler/components/dashboard/JobProfileDashboard.tsx
|
|
4485
4545
|
function JobProfileDashboard({ profile, initialTab, embedded }) {
|
|
4486
|
-
const validTabs = ["job", "database", "cache", "http", "jobs"];
|
|
4487
|
-
const defaultTab = validTabs.includes(initialTab) ? initialTab : "job";
|
|
4488
|
-
const [activeTab, setActiveTab] = d2(defaultTab);
|
|
4489
4546
|
const cd = profile.collectors_data || {};
|
|
4490
4547
|
const hasHttp = cd["http"]?.total_requests > 0;
|
|
4491
4548
|
const hasJobs = (profile.child_jobs?.length ?? 0) > 0;
|
|
4549
|
+
const hasDumps = (cd["dump"]?.count ?? 0) > 0;
|
|
4550
|
+
const hasLogs = (cd["logs"]?.total ?? 0) > 0;
|
|
4551
|
+
const hasException = !!cd["exception"]?.exception_class;
|
|
4552
|
+
const validTabs = ["job", "database", "cache", "http", "jobs", "dump", "logs", "exception", "env", "timeline"];
|
|
4553
|
+
const defaultTab = validTabs.includes(initialTab) ? initialTab : "job";
|
|
4554
|
+
const [activeTab, setActiveTab] = d2(hasException ? "exception" : defaultTab);
|
|
4492
4555
|
const jobData = cd["job"];
|
|
4493
4556
|
const isFailed = jobData?.status === "failed";
|
|
4494
4557
|
const parent = profile.parent_profile;
|
|
@@ -4550,22 +4613,36 @@
|
|
|
4550
4613
|
] }),
|
|
4551
4614
|
/* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
|
|
4552
4615
|
/* @__PURE__ */ u3("div", { class: "tabs", children: [
|
|
4616
|
+
hasException && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("exception"), onClick: handleTabClick("exception"), style: "color:var(--profiler-error,#ef4444);", children: "\u{1F4A5} Exception" }),
|
|
4553
4617
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("job"), onClick: handleTabClick("job"), children: "Job" }),
|
|
4554
4618
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("database"), onClick: handleTabClick("database"), children: "Database" }),
|
|
4555
4619
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
|
|
4556
4620
|
hasHttp && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("http"), onClick: handleTabClick("http"), children: "Outbound HTTP" }),
|
|
4621
|
+
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("timeline"), onClick: handleTabClick("timeline"), children: "Timeline" }),
|
|
4557
4622
|
hasJobs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("jobs"), onClick: handleTabClick("jobs"), children: [
|
|
4558
4623
|
"Jobs (",
|
|
4559
4624
|
profile.child_jobs.length,
|
|
4560
4625
|
")"
|
|
4561
|
-
] })
|
|
4626
|
+
] }),
|
|
4627
|
+
hasDumps && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("dump"), onClick: handleTabClick("dump"), children: [
|
|
4628
|
+
"Dumps (",
|
|
4629
|
+
cd["dump"].count,
|
|
4630
|
+
")"
|
|
4631
|
+
] }),
|
|
4632
|
+
hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" }),
|
|
4633
|
+
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("env"), onClick: handleTabClick("env"), children: "Env" })
|
|
4562
4634
|
] }),
|
|
4563
4635
|
/* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
|
|
4636
|
+
activeTab === "exception" && /* @__PURE__ */ u3(ExceptionTab, { exceptionData: cd["exception"] }),
|
|
4564
4637
|
activeTab === "job" && /* @__PURE__ */ u3(JobTab, { jobData: cd["job"] }),
|
|
4565
4638
|
activeTab === "database" && /* @__PURE__ */ u3(DatabaseTab, { dbData: cd["database"], token: profile.token }),
|
|
4566
4639
|
activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
|
|
4567
4640
|
activeTab === "http" && /* @__PURE__ */ u3(HttpTab, { httpData: cd["http"] }),
|
|
4568
|
-
activeTab === "
|
|
4641
|
+
activeTab === "timeline" && /* @__PURE__ */ u3(FlameGraphTab, { flamegraphData: cd["flamegraph"], perfData: cd["performance"], functionProfileData: cd["function_profile"] }),
|
|
4642
|
+
activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs }),
|
|
4643
|
+
activeTab === "dump" && /* @__PURE__ */ u3(DumpsTab, { dumpData: cd["dump"] }),
|
|
4644
|
+
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] }),
|
|
4645
|
+
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"], readOnly: true })
|
|
4569
4646
|
] })
|
|
4570
4647
|
] }),
|
|
4571
4648
|
!embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
|
|
@@ -7,7 +7,8 @@ module Profiler
|
|
|
7
7
|
|
|
8
8
|
def show
|
|
9
9
|
variables = ENV.to_h.sort.to_h
|
|
10
|
-
|
|
10
|
+
overrides = Profiler.env_override_store.all_overrides
|
|
11
|
+
render json: { variables: variables, total: variables.size, overrides: overrides }
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def update
|
|
@@ -22,12 +23,31 @@ module Profiler
|
|
|
22
23
|
|
|
23
24
|
if value.nil? || value.to_s.empty?
|
|
24
25
|
ENV.delete(key)
|
|
26
|
+
Profiler.env_override_store.delete(key)
|
|
25
27
|
render json: { key: key, value: nil, deleted: true }
|
|
26
28
|
else
|
|
27
29
|
ENV[key] = value.to_s
|
|
30
|
+
Profiler.env_override_store.set(key, value.to_s)
|
|
28
31
|
render json: { key: key, value: ENV[key] }
|
|
29
32
|
end
|
|
30
33
|
end
|
|
34
|
+
|
|
35
|
+
def reset_override
|
|
36
|
+
key = params[:key].to_s.strip
|
|
37
|
+
|
|
38
|
+
if key.blank?
|
|
39
|
+
render json: { error: "Key cannot be blank" }, status: :unprocessable_entity
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Profiler.env_override_store.reset(key)
|
|
44
|
+
render json: { key: key, value: ENV[key], reset: true }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reset_all
|
|
48
|
+
Profiler.env_override_store.reset_all
|
|
49
|
+
render json: { reset: true }
|
|
50
|
+
end
|
|
31
51
|
end
|
|
32
52
|
end
|
|
33
53
|
end
|
data/config/routes.rb
CHANGED
|
@@ -35,5 +35,7 @@ Profiler::Engine.routes.draw do
|
|
|
35
35
|
post "explain", to: "explain#create"
|
|
36
36
|
resource :function_profiling, only: [:show, :update], controller: "function_profiling"
|
|
37
37
|
resource :env_vars, only: [:show, :update], controller: "env_vars"
|
|
38
|
+
delete "env_vars/reset", to: "env_vars#reset_override"
|
|
39
|
+
delete "env_vars/reset_all", to: "env_vars#reset_all"
|
|
38
40
|
end
|
|
39
41
|
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Profiler
|
|
7
|
+
class EnvOverrideStore
|
|
8
|
+
DELETED_SENTINEL = "__profiler_deleted__"
|
|
9
|
+
RESTORE_SENTINEL = "__profiler_restore__"
|
|
10
|
+
|
|
11
|
+
# File format: { "KEY" => { "value" => "...", "original" => "..." } }
|
|
12
|
+
# Sentinels for "value":
|
|
13
|
+
# DELETED_SENTINEL → ENV.delete(key)
|
|
14
|
+
# RESTORE_SENTINEL → restore ENV[key] to "original" (cleaned up after apply!)
|
|
15
|
+
|
|
16
|
+
def set(key, value)
|
|
17
|
+
overrides = load_overrides
|
|
18
|
+
original = original_for(overrides, key)
|
|
19
|
+
overrides[key] = { "value" => value.to_s, "original" => original }
|
|
20
|
+
save_overrides(overrides)
|
|
21
|
+
rescue => e
|
|
22
|
+
warn "[Profiler] EnvOverrideStore: failed to set #{key}: #{e.message}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def delete(key)
|
|
26
|
+
overrides = load_overrides
|
|
27
|
+
original = original_for(overrides, key)
|
|
28
|
+
overrides[key] = { "value" => DELETED_SENTINEL, "original" => original }
|
|
29
|
+
save_overrides(overrides)
|
|
30
|
+
rescue => e
|
|
31
|
+
warn "[Profiler] EnvOverrideStore: failed to delete #{key}: #{e.message}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reset(key)
|
|
35
|
+
overrides = load_overrides
|
|
36
|
+
entry = overrides[key]
|
|
37
|
+
return unless entry
|
|
38
|
+
|
|
39
|
+
original = entry["original"]
|
|
40
|
+
# Keep a RESTORE entry so Sidekiq workers pick it up on next job
|
|
41
|
+
overrides[key] = { "value" => RESTORE_SENTINEL, "original" => original }
|
|
42
|
+
save_overrides(overrides)
|
|
43
|
+
|
|
44
|
+
# Apply immediately to the current (web) process
|
|
45
|
+
original.nil? ? ENV.delete(key) : ENV[key] = original
|
|
46
|
+
rescue => e
|
|
47
|
+
warn "[Profiler] EnvOverrideStore: failed to reset #{key}: #{e.message}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reset_all
|
|
51
|
+
overrides = load_overrides
|
|
52
|
+
restore_overrides = {}
|
|
53
|
+
|
|
54
|
+
overrides.each do |key, entry|
|
|
55
|
+
original = entry.is_a?(Hash) ? entry["original"] : nil
|
|
56
|
+
# Apply immediately to the current (web) process
|
|
57
|
+
original.nil? ? ENV.delete(key) : ENV[key] = original
|
|
58
|
+
# Leave a RESTORE sentinel for Sidekiq workers to pick up
|
|
59
|
+
restore_overrides[key] = { "value" => RESTORE_SENTINEL, "original" => original }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
restore_overrides.empty? ? clear : save_overrides(restore_overrides)
|
|
63
|
+
rescue => e
|
|
64
|
+
warn "[Profiler] EnvOverrideStore: failed to reset all: #{e.message}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def apply!
|
|
68
|
+
overrides = load_overrides
|
|
69
|
+
restore_keys = []
|
|
70
|
+
|
|
71
|
+
overrides.each do |key, entry|
|
|
72
|
+
value = entry.is_a?(Hash) ? entry["value"] : entry
|
|
73
|
+
original = entry.is_a?(Hash) ? entry["original"] : nil
|
|
74
|
+
|
|
75
|
+
case value
|
|
76
|
+
when DELETED_SENTINEL
|
|
77
|
+
ENV.delete(key)
|
|
78
|
+
when RESTORE_SENTINEL
|
|
79
|
+
original.nil? ? ENV.delete(key) : ENV[key] = original
|
|
80
|
+
restore_keys << key
|
|
81
|
+
else
|
|
82
|
+
ENV[key] = value
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if restore_keys.any?
|
|
87
|
+
restore_keys.each { |k| overrides.delete(k) }
|
|
88
|
+
save_overrides(overrides)
|
|
89
|
+
end
|
|
90
|
+
rescue => e
|
|
91
|
+
warn "[Profiler] EnvOverrideStore: failed to apply overrides: #{e.message}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns active overrides (excludes RESTORE entries — already being reverted)
|
|
95
|
+
def all_overrides
|
|
96
|
+
load_overrides.reject do |_, entry|
|
|
97
|
+
value = entry.is_a?(Hash) ? entry["value"] : entry
|
|
98
|
+
value == RESTORE_SENTINEL
|
|
99
|
+
end.transform_values do |entry|
|
|
100
|
+
entry.is_a?(Hash) ? entry : { "value" => entry, "original" => nil }
|
|
101
|
+
end
|
|
102
|
+
rescue => e
|
|
103
|
+
warn "[Profiler] EnvOverrideStore: failed to load overrides: #{e.message}"
|
|
104
|
+
{}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def clear
|
|
108
|
+
FileUtils.rm_f(override_file_path)
|
|
109
|
+
rescue => e
|
|
110
|
+
warn "[Profiler] EnvOverrideStore: failed to clear: #{e.message}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def original_for(overrides, key)
|
|
116
|
+
overrides.key?(key) ? overrides[key]["original"] : ENV[key]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def override_file_path
|
|
120
|
+
Profiler.configuration.tmp_path.join("env_overrides.json")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def load_overrides
|
|
124
|
+
path = override_file_path
|
|
125
|
+
return {} unless File.exist?(path)
|
|
126
|
+
|
|
127
|
+
JSON.parse(File.read(path))
|
|
128
|
+
rescue JSON::ParserError
|
|
129
|
+
{}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def save_overrides(overrides)
|
|
133
|
+
path = override_file_path
|
|
134
|
+
FileUtils.mkdir_p(path.dirname)
|
|
135
|
+
File.write(path, JSON.generate(overrides))
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -22,6 +22,9 @@ module Profiler
|
|
|
22
22
|
|
|
23
23
|
url = build_url(host, port, req.path, use_ssl?)
|
|
24
24
|
req_body = req.body.to_s
|
|
25
|
+
# Fallback: body may be passed as the 2nd argument and only applied
|
|
26
|
+
# to req inside super via req.set_body_internal(body)
|
|
27
|
+
req_body = body.to_s if req_body.empty? && body
|
|
25
28
|
req_headers = req.to_hash.transform_values { |v| v.join(", ") }
|
|
26
29
|
request_id = SecureRandom.hex(8)
|
|
27
30
|
started_at = Time.now.iso8601(3)
|
|
@@ -6,13 +6,23 @@ require_relative "collectors/job_collector"
|
|
|
6
6
|
require_relative "collectors/database_collector"
|
|
7
7
|
require_relative "collectors/cache_collector"
|
|
8
8
|
require_relative "collectors/http_collector"
|
|
9
|
+
require_relative "collectors/dump_collector"
|
|
10
|
+
require_relative "collectors/log_collector"
|
|
11
|
+
require_relative "collectors/exception_collector"
|
|
12
|
+
require_relative "collectors/env_collector"
|
|
13
|
+
require_relative "collectors/flamegraph_collector"
|
|
9
14
|
|
|
10
15
|
module Profiler
|
|
11
16
|
class JobProfiler
|
|
12
17
|
JOB_COLLECTOR_CLASSES = [
|
|
13
18
|
Collectors::DatabaseCollector,
|
|
14
19
|
Collectors::CacheCollector,
|
|
15
|
-
Collectors::HttpCollector
|
|
20
|
+
Collectors::HttpCollector,
|
|
21
|
+
Collectors::DumpCollector,
|
|
22
|
+
Collectors::LogCollector,
|
|
23
|
+
Collectors::ExceptionCollector,
|
|
24
|
+
Collectors::EnvCollector,
|
|
25
|
+
Collectors::FlameGraphCollector
|
|
16
26
|
].freeze
|
|
17
27
|
|
|
18
28
|
def self.profile(job_class:, job_id:, queue:, arguments:, executions:, parent_token: nil, &block)
|
|
@@ -55,6 +65,8 @@ module Profiler
|
|
|
55
65
|
collectors = [job_collector] + JOB_COLLECTOR_CLASSES.map { |klass| klass.new(profile) }
|
|
56
66
|
collectors.each { |c| c.subscribe if c.respond_to?(:subscribe) }
|
|
57
67
|
|
|
68
|
+
exception_collector = collectors.find { |c| c.is_a?(Collectors::ExceptionCollector) }
|
|
69
|
+
|
|
58
70
|
memory_before = current_memory if Profiler.configuration.track_memory
|
|
59
71
|
|
|
60
72
|
job_status = "completed"
|
|
@@ -68,6 +80,7 @@ module Profiler
|
|
|
68
80
|
rescue => e
|
|
69
81
|
job_status = "failed"
|
|
70
82
|
error_message = "#{e.class}: #{e.message}"
|
|
83
|
+
exception_collector&.capture(e)
|
|
71
84
|
raise
|
|
72
85
|
ensure
|
|
73
86
|
Profiler::CurrentContext.token = previous_token
|
data/lib/profiler/railtie.rb
CHANGED
|
@@ -6,6 +6,10 @@ module Profiler
|
|
|
6
6
|
class Railtie < Rails::Railtie
|
|
7
7
|
config.profiler = ActiveSupport::OrderedOptions.new
|
|
8
8
|
|
|
9
|
+
initializer "profiler.apply_env_overrides" do
|
|
10
|
+
Profiler.env_override_store.apply!
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
initializer "profiler.set_configs" do |app|
|
|
10
14
|
# Set default configuration for Rails environment
|
|
11
15
|
Profiler.configure do |config|
|
data/lib/profiler/version.rb
CHANGED
data/lib/profiler.rb
CHANGED
|
@@ -25,6 +25,10 @@ module Profiler
|
|
|
25
25
|
@storage ||= configuration.storage_backend
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def env_override_store
|
|
29
|
+
@env_override_store ||= EnvOverrideStore.new
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def enabled?
|
|
29
33
|
configuration.enabled
|
|
30
34
|
end
|
|
@@ -100,5 +104,6 @@ require_relative "profiler/collectors/routes_collector"
|
|
|
100
104
|
require_relative "profiler/collectors/i18n_collector"
|
|
101
105
|
require_relative "profiler/collectors/env_collector"
|
|
102
106
|
|
|
107
|
+
require_relative "profiler/env_override_store"
|
|
103
108
|
require_relative "profiler/railtie" if defined?(Rails::Railtie)
|
|
104
109
|
require_relative "profiler/engine" if defined?(Rails::Engine)
|
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.19.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-04-
|
|
11
|
+
date: 2026-04-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -143,6 +143,7 @@ files:
|
|
|
143
143
|
- lib/profiler/configuration.rb
|
|
144
144
|
- lib/profiler/current_context.rb
|
|
145
145
|
- lib/profiler/engine.rb
|
|
146
|
+
- lib/profiler/env_override_store.rb
|
|
146
147
|
- lib/profiler/explain_runner.rb
|
|
147
148
|
- lib/profiler/instrumentation/active_job_instrumentation.rb
|
|
148
149
|
- lib/profiler/instrumentation/net_http_instrumentation.rb
|