rails-profiler 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/profiler-toolbar.js +122 -0
- data/app/assets/builds/profiler.css +43 -0
- data/app/assets/builds/profiler.js +195 -2
- data/lib/profiler/collectors/i18n_collector.rb +100 -0
- data/lib/profiler/collectors/job_collector.rb +1 -1
- data/lib/profiler/collectors/request_collector.rb +34 -0
- data/lib/profiler/collectors/routes_collector.rb +89 -0
- data/lib/profiler/mcp/resources/recent_requests.rb +3 -1
- data/lib/profiler/mcp/tools/get_profile_detail.rb +17 -0
- data/lib/profiler/railtie.rb +3 -1
- data/lib/profiler/version.rb +1 -1
- data/lib/profiler.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e97a77a23dca55d709a1512c3c2ab3b95f0e6b15ed01b7769852587b962be8a
|
|
4
|
+
data.tar.gz: 131c40f561129e8835c41a0b3ba456431bb11fe791c8b0add6396fbbfe0be293
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7565ae06d41a7bd2d574610962e20fc3c4a6fcba5bda89482e840fb09af57c686eff9e8f4ccb02bfde05d53e0b5cc8019622ee7ea4db44fc85d6f5a0bc09c8c6
|
|
7
|
+
data.tar.gz: bea8aee55cc58b41fc6d01c2ad4dffcdd9ecfdfc4617a45ba16b019577ad5209a2bcae5c9d226d0d5e31af1fbb3e508db8a7e3eae25c5c87f920dcad3e48d1f8
|
|
@@ -484,6 +484,10 @@
|
|
|
484
484
|
const headers = requestData.headers;
|
|
485
485
|
const responseBody = requestData.response_body;
|
|
486
486
|
const responseBodyEncoding = requestData.response_body_encoding;
|
|
487
|
+
const controllerAction = requestData.controller_action;
|
|
488
|
+
const routeName = requestData.route_name;
|
|
489
|
+
const routePattern = requestData.route_pattern;
|
|
490
|
+
const routeParams = requestData.route_params;
|
|
487
491
|
return /* @__PURE__ */ u3(k, { children: [
|
|
488
492
|
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-header", children: [
|
|
489
493
|
"Request & Response",
|
|
@@ -503,6 +507,22 @@
|
|
|
503
507
|
/* @__PURE__ */ u3("span", { children: "Status" }),
|
|
504
508
|
/* @__PURE__ */ u3("strong", { class: cls, children: profile.status })
|
|
505
509
|
] }),
|
|
510
|
+
controllerAction && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
511
|
+
/* @__PURE__ */ u3("span", { children: "Controller#Action" }),
|
|
512
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono", children: controllerAction })
|
|
513
|
+
] }),
|
|
514
|
+
routeName && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
515
|
+
/* @__PURE__ */ u3("span", { children: "Route Name" }),
|
|
516
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono", children: routeName })
|
|
517
|
+
] }),
|
|
518
|
+
routePattern && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
519
|
+
/* @__PURE__ */ u3("span", { children: "Route Pattern" }),
|
|
520
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono", children: routePattern })
|
|
521
|
+
] }),
|
|
522
|
+
routeParams && Object.keys(routeParams).length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
523
|
+
/* @__PURE__ */ u3("span", { children: "Route Params" }),
|
|
524
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono", children: Object.entries(routeParams).map(([k3, v3]) => `${k3}: ${v3}`).join(", ") })
|
|
525
|
+
] }),
|
|
506
526
|
profile.params && Object.keys(profile.params).length > 0 && /* @__PURE__ */ u3(k, { children: [
|
|
507
527
|
/* @__PURE__ */ u3("div", { class: "profiler-section__header profiler-mt-3", children: "Parameters" }),
|
|
508
528
|
Object.entries(profile.params).map(([key, value]) => /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
@@ -894,6 +914,78 @@
|
|
|
894
914
|
] });
|
|
895
915
|
}
|
|
896
916
|
|
|
917
|
+
// app/assets/typescript/profiler/components/toolbar/panels/RoutesPanel.tsx
|
|
918
|
+
function RoutesPanel({ routesData }) {
|
|
919
|
+
const { total, matched } = routesData;
|
|
920
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
921
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-header", children: [
|
|
922
|
+
"Routes",
|
|
923
|
+
/* @__PURE__ */ u3("span", { class: "profiler-float-right", children: [
|
|
924
|
+
total,
|
|
925
|
+
" routes"
|
|
926
|
+
] })
|
|
927
|
+
] }),
|
|
928
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-content", children: matched ? /* @__PURE__ */ u3(k, { children: [
|
|
929
|
+
/* @__PURE__ */ u3("div", { class: "profiler-section__header", children: "Matched Route" }),
|
|
930
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
931
|
+
/* @__PURE__ */ u3("span", { children: "Pattern" }),
|
|
932
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono", children: matched.pattern })
|
|
933
|
+
] }),
|
|
934
|
+
matched.name && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
935
|
+
/* @__PURE__ */ u3("span", { children: "Name" }),
|
|
936
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono", children: [
|
|
937
|
+
matched.name,
|
|
938
|
+
"_path"
|
|
939
|
+
] })
|
|
940
|
+
] }),
|
|
941
|
+
matched.controller_action && /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: [
|
|
942
|
+
/* @__PURE__ */ u3("span", { children: "Controller#Action" }),
|
|
943
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--xs profiler-text--mono", children: matched.controller_action })
|
|
944
|
+
] })
|
|
945
|
+
] }) : /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", children: /* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: "No route matched" }) }) })
|
|
946
|
+
] });
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// app/assets/typescript/profiler/components/toolbar/panels/I18nPanel.tsx
|
|
950
|
+
function I18nPanel({ i18nData }) {
|
|
951
|
+
const missing = i18nData.lookups.filter((l3) => l3.missing);
|
|
952
|
+
const ok = i18nData.lookups.filter((l3) => !l3.missing);
|
|
953
|
+
const prioritized = [...missing, ...ok].slice(0, 5);
|
|
954
|
+
const remaining = i18nData.total - 5;
|
|
955
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
956
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-header", children: [
|
|
957
|
+
"I18n",
|
|
958
|
+
/* @__PURE__ */ u3("span", { style: "margin-left:6px;font-weight:400;color:var(--profiler-muted,#6b7280);", children: [
|
|
959
|
+
"[",
|
|
960
|
+
i18nData.locale,
|
|
961
|
+
"]"
|
|
962
|
+
] }),
|
|
963
|
+
i18nData.missing_count > 0 && /* @__PURE__ */ u3("span", { style: "color:var(--profiler-error,#ef4444);margin-left:8px;", children: [
|
|
964
|
+
i18nData.missing_count,
|
|
965
|
+
" missing"
|
|
966
|
+
] })
|
|
967
|
+
] }),
|
|
968
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-content", children: [
|
|
969
|
+
prioritized.map((entry, i3) => /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", style: "align-items:flex-start;gap:6px;", children: [
|
|
970
|
+
/* @__PURE__ */ u3(
|
|
971
|
+
"span",
|
|
972
|
+
{
|
|
973
|
+
class: "profiler-text--xs profiler-text--truncate",
|
|
974
|
+
style: `flex:1;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;${entry.missing ? "color:var(--profiler-error,#ef4444);" : ""}`,
|
|
975
|
+
children: entry.key
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
entry.missing && /* @__PURE__ */ u3("span", { class: "profiler-text--xs", style: "color:var(--profiler-error,#ef4444);font-weight:600;", children: "\u26A0" })
|
|
979
|
+
] }, i3)),
|
|
980
|
+
remaining > 0 && /* @__PURE__ */ u3("div", { class: "profiler-more", children: [
|
|
981
|
+
"+ ",
|
|
982
|
+
remaining,
|
|
983
|
+
" more keys"
|
|
984
|
+
] })
|
|
985
|
+
] })
|
|
986
|
+
] });
|
|
987
|
+
}
|
|
988
|
+
|
|
897
989
|
// app/assets/typescript/profiler/components/toolbar/ToolbarApp.tsx
|
|
898
990
|
function statusClass2(status) {
|
|
899
991
|
if (status >= 200 && status < 300) return "profiler-text--success";
|
|
@@ -926,6 +1018,8 @@
|
|
|
926
1018
|
const httpData = cd["http"];
|
|
927
1019
|
const logData = cd["logs"];
|
|
928
1020
|
const exceptionData = cd["exception"];
|
|
1021
|
+
const routesData = cd["routes"];
|
|
1022
|
+
const i18nData = cd["i18n"];
|
|
929
1023
|
const reqClass = statusClass2(profile.status);
|
|
930
1024
|
const durClass = durationClass(profile.duration);
|
|
931
1025
|
const dbClass = (dbData?.slow_queries ?? 0) > 0 ? "profiler-text--error" : "profiler-text--success";
|
|
@@ -1121,6 +1215,34 @@
|
|
|
1121
1215
|
]
|
|
1122
1216
|
}
|
|
1123
1217
|
),
|
|
1218
|
+
routesData && routesData.total > 0 && /* @__PURE__ */ u3(
|
|
1219
|
+
ToolbarItem,
|
|
1220
|
+
{
|
|
1221
|
+
href: `/_profiler/profiles/${token}?tab=routes`,
|
|
1222
|
+
panelLarge: true,
|
|
1223
|
+
panel: /* @__PURE__ */ u3(RoutesPanel, { routesData }),
|
|
1224
|
+
children: [
|
|
1225
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "ROUTE" }),
|
|
1226
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: routesData.matched?.pattern ?? "\u2014" })
|
|
1227
|
+
]
|
|
1228
|
+
}
|
|
1229
|
+
),
|
|
1230
|
+
i18nData && i18nData.total > 0 && /* @__PURE__ */ u3(
|
|
1231
|
+
ToolbarItem,
|
|
1232
|
+
{
|
|
1233
|
+
href: `/_profiler/profiles/${token}?tab=i18n`,
|
|
1234
|
+
className: i18nData.missing_count > 0 ? "profiler-text--error" : "profiler-text--muted",
|
|
1235
|
+
panel: /* @__PURE__ */ u3(I18nPanel, { i18nData }),
|
|
1236
|
+
children: [
|
|
1237
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "I18N" }),
|
|
1238
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: i18nData.locale }),
|
|
1239
|
+
i18nData.missing_count > 0 && /* @__PURE__ */ u3("span", { class: "profiler-text--error profiler-text--xs", children: [
|
|
1240
|
+
"\u26A0 ",
|
|
1241
|
+
i18nData.missing_count
|
|
1242
|
+
] })
|
|
1243
|
+
]
|
|
1244
|
+
}
|
|
1245
|
+
),
|
|
1124
1246
|
/* @__PURE__ */ u3("a", { href: "/_profiler", class: "profiler-toolbar-item", children: "\u2B21 Profiler" })
|
|
1125
1247
|
] });
|
|
1126
1248
|
}
|
|
@@ -1107,6 +1107,49 @@ tr:hover .btn-row-delete {
|
|
|
1107
1107
|
color: #fff;
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
|
+
.profiler-flex-table {
|
|
1111
|
+
display: flex;
|
|
1112
|
+
flex-direction: column;
|
|
1113
|
+
}
|
|
1114
|
+
.profiler-flex-table__header {
|
|
1115
|
+
display: flex;
|
|
1116
|
+
align-items: center;
|
|
1117
|
+
gap: var(--profiler-space-3);
|
|
1118
|
+
padding: var(--profiler-space-2) var(--profiler-space-3);
|
|
1119
|
+
background: var(--profiler-bg-light);
|
|
1120
|
+
border-bottom: 1px solid var(--profiler-border-strong);
|
|
1121
|
+
font-weight: 600;
|
|
1122
|
+
font-size: var(--profiler-text-xs);
|
|
1123
|
+
text-transform: uppercase;
|
|
1124
|
+
letter-spacing: 0.08em;
|
|
1125
|
+
color: var(--profiler-text-muted);
|
|
1126
|
+
}
|
|
1127
|
+
.profiler-flex-table__row {
|
|
1128
|
+
display: flex;
|
|
1129
|
+
align-items: center;
|
|
1130
|
+
gap: var(--profiler-space-3);
|
|
1131
|
+
padding: var(--profiler-space-3);
|
|
1132
|
+
border-bottom: 1px solid var(--profiler-border);
|
|
1133
|
+
color: var(--profiler-text);
|
|
1134
|
+
}
|
|
1135
|
+
.profiler-flex-table__row--matched {
|
|
1136
|
+
background: var(--profiler-success-bg);
|
|
1137
|
+
border-left: 2px solid var(--profiler-success);
|
|
1138
|
+
}
|
|
1139
|
+
.profiler-flex-table__cell {
|
|
1140
|
+
flex: 1;
|
|
1141
|
+
min-width: 0;
|
|
1142
|
+
overflow: hidden;
|
|
1143
|
+
text-overflow: ellipsis;
|
|
1144
|
+
white-space: nowrap;
|
|
1145
|
+
}
|
|
1146
|
+
.profiler-flex-table__cell--fixed {
|
|
1147
|
+
flex: 0 0 auto;
|
|
1148
|
+
}
|
|
1149
|
+
.profiler-flex-table__cell--muted {
|
|
1150
|
+
color: var(--profiler-text-muted);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1110
1153
|
#profiler-toolbar {
|
|
1111
1154
|
position: fixed;
|
|
1112
1155
|
bottom: 0;
|
|
@@ -1174,6 +1174,7 @@
|
|
|
1174
1174
|
function RequestTab({ profile }) {
|
|
1175
1175
|
const [copied, setCopied] = d2(false);
|
|
1176
1176
|
const curl = buildCurl(profile);
|
|
1177
|
+
const routeData = profile.collectors_data?.request ?? {};
|
|
1177
1178
|
function copyToClipboard() {
|
|
1178
1179
|
navigator.clipboard.writeText(curl).then(() => {
|
|
1179
1180
|
setCopied(true);
|
|
@@ -1201,6 +1202,22 @@
|
|
|
1201
1202
|
profile.duration.toFixed(2),
|
|
1202
1203
|
" ms"
|
|
1203
1204
|
] })
|
|
1205
|
+
] }),
|
|
1206
|
+
routeData.controller_action && /* @__PURE__ */ u3("tr", { children: [
|
|
1207
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Controller#Action" }),
|
|
1208
|
+
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: routeData.controller_action })
|
|
1209
|
+
] }),
|
|
1210
|
+
routeData.route_name && /* @__PURE__ */ u3("tr", { children: [
|
|
1211
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Route Name" }),
|
|
1212
|
+
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: routeData.route_name })
|
|
1213
|
+
] }),
|
|
1214
|
+
routeData.route_pattern && /* @__PURE__ */ u3("tr", { children: [
|
|
1215
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Route Pattern" }),
|
|
1216
|
+
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: routeData.route_pattern })
|
|
1217
|
+
] }),
|
|
1218
|
+
routeData.route_params && Object.keys(routeData.route_params).length > 0 && /* @__PURE__ */ u3("tr", { children: [
|
|
1219
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Route Params" }),
|
|
1220
|
+
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: Object.entries(routeData.route_params).map(([k3, v3]) => `${k3}: ${v3}`).join(", ") })
|
|
1204
1221
|
] })
|
|
1205
1222
|
] }),
|
|
1206
1223
|
profile.headers && Object.keys(profile.headers).length > 0 && /* @__PURE__ */ u3(k, { children: [
|
|
@@ -2289,6 +2306,176 @@
|
|
|
2289
2306
|
] });
|
|
2290
2307
|
}
|
|
2291
2308
|
|
|
2309
|
+
// app/assets/typescript/profiler/components/dashboard/tabs/RoutesTab.tsx
|
|
2310
|
+
var VERB_COLORS = {
|
|
2311
|
+
GET: "var(--profiler-success)",
|
|
2312
|
+
POST: "var(--profiler-accent)",
|
|
2313
|
+
PUT: "var(--profiler-warning)",
|
|
2314
|
+
PATCH: "var(--profiler-warning)",
|
|
2315
|
+
DELETE: "var(--profiler-error)"
|
|
2316
|
+
};
|
|
2317
|
+
function VerbBadge({ verb }) {
|
|
2318
|
+
const color = VERB_COLORS[verb] ?? "var(--profiler-text-muted)";
|
|
2319
|
+
return /* @__PURE__ */ u3("span", { style: {
|
|
2320
|
+
display: "inline-block",
|
|
2321
|
+
minWidth: "52px",
|
|
2322
|
+
textAlign: "center",
|
|
2323
|
+
padding: "1px 6px",
|
|
2324
|
+
borderRadius: "4px",
|
|
2325
|
+
fontSize: "10px",
|
|
2326
|
+
fontWeight: 700,
|
|
2327
|
+
fontFamily: "monospace",
|
|
2328
|
+
border: `1px solid ${color}`,
|
|
2329
|
+
color
|
|
2330
|
+
}, children: verb });
|
|
2331
|
+
}
|
|
2332
|
+
function RoutesTab({ routesData }) {
|
|
2333
|
+
const [filter, setFilter] = d2("");
|
|
2334
|
+
const [verbFilter, setVerbFilter] = d2("ALL");
|
|
2335
|
+
const { total, matched, routes } = routesData;
|
|
2336
|
+
const verbs = ["ALL", ...Array.from(new Set(routes.map((r3) => r3.verb))).sort()];
|
|
2337
|
+
const filtered = routes.filter((route) => {
|
|
2338
|
+
const matchesVerb = verbFilter === "ALL" || route.verb === verbFilter;
|
|
2339
|
+
const q2 = filter.toLowerCase();
|
|
2340
|
+
const matchesText = !q2 || (route.pattern ?? "").toLowerCase().includes(q2) || (route.name ?? "").toLowerCase().includes(q2) || (route.controller_action ?? "").toLowerCase().includes(q2);
|
|
2341
|
+
return matchesVerb && matchesText;
|
|
2342
|
+
});
|
|
2343
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
2344
|
+
/* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: "Routes" }),
|
|
2345
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4", children: /* @__PURE__ */ u3("div", { class: "profiler-stat-card", children: [
|
|
2346
|
+
/* @__PURE__ */ u3("div", { class: "profiler-stat-card__value", children: total }),
|
|
2347
|
+
/* @__PURE__ */ u3("div", { class: "profiler-stat-card__label", children: "Total routes" })
|
|
2348
|
+
] }) }),
|
|
2349
|
+
matched && /* @__PURE__ */ u3(k, { children: [
|
|
2350
|
+
/* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-4", children: "Matched Route" }),
|
|
2351
|
+
/* @__PURE__ */ u3("table", { class: "profiler-mb-4", children: [
|
|
2352
|
+
/* @__PURE__ */ u3("tr", { children: [
|
|
2353
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", style: "width:120px;", children: "Verb" }),
|
|
2354
|
+
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3(VerbBadge, { verb: matched.verb }) })
|
|
2355
|
+
] }),
|
|
2356
|
+
/* @__PURE__ */ u3("tr", { children: [
|
|
2357
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Pattern" }),
|
|
2358
|
+
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: matched.pattern })
|
|
2359
|
+
] }),
|
|
2360
|
+
matched.name && /* @__PURE__ */ u3("tr", { children: [
|
|
2361
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Route Name" }),
|
|
2362
|
+
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: [
|
|
2363
|
+
matched.name,
|
|
2364
|
+
"_path"
|
|
2365
|
+
] })
|
|
2366
|
+
] }),
|
|
2367
|
+
matched.controller_action && /* @__PURE__ */ u3("tr", { children: [
|
|
2368
|
+
/* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Controller#Action" }),
|
|
2369
|
+
/* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: matched.controller_action })
|
|
2370
|
+
] })
|
|
2371
|
+
] })
|
|
2372
|
+
] }),
|
|
2373
|
+
/* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-4", children: "All Routes" }),
|
|
2374
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2 profiler-mb-3", style: "flex-wrap:wrap;align-items:center;", children: [
|
|
2375
|
+
/* @__PURE__ */ u3(
|
|
2376
|
+
"input",
|
|
2377
|
+
{
|
|
2378
|
+
type: "text",
|
|
2379
|
+
class: "profiler-filter-input",
|
|
2380
|
+
placeholder: "Filter by path, name or controller\u2026",
|
|
2381
|
+
value: filter,
|
|
2382
|
+
onInput: (e3) => setFilter(e3.target.value),
|
|
2383
|
+
style: "flex:1;width:auto;min-width:200px;"
|
|
2384
|
+
}
|
|
2385
|
+
),
|
|
2386
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-1", children: verbs.map((v3) => /* @__PURE__ */ u3(
|
|
2387
|
+
"button",
|
|
2388
|
+
{
|
|
2389
|
+
onClick: () => setVerbFilter(v3),
|
|
2390
|
+
style: {
|
|
2391
|
+
padding: "3px 8px",
|
|
2392
|
+
fontSize: "11px",
|
|
2393
|
+
fontWeight: 600,
|
|
2394
|
+
borderRadius: "4px",
|
|
2395
|
+
border: "1px solid var(--profiler-border)",
|
|
2396
|
+
background: verbFilter === v3 ? "var(--profiler-accent)" : "transparent",
|
|
2397
|
+
color: verbFilter === v3 ? "#fff" : "var(--profiler-text-muted)",
|
|
2398
|
+
cursor: "pointer"
|
|
2399
|
+
},
|
|
2400
|
+
children: v3
|
|
2401
|
+
},
|
|
2402
|
+
v3
|
|
2403
|
+
)) })
|
|
2404
|
+
] }),
|
|
2405
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex-table", children: [
|
|
2406
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex-table__header", children: [
|
|
2407
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell--fixed", children: "Verb" }),
|
|
2408
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell", children: "Pattern" }),
|
|
2409
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell", children: "Name" }),
|
|
2410
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell", children: "Controller#Action" })
|
|
2411
|
+
] }),
|
|
2412
|
+
filtered.map((route, i3) => /* @__PURE__ */ u3("div", { class: `profiler-flex-table__row${route.matched ? " profiler-flex-table__row--matched" : ""}`, children: [
|
|
2413
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell--fixed", children: /* @__PURE__ */ u3(VerbBadge, { verb: route.verb }) }),
|
|
2414
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell profiler-text--mono profiler-text--xs", title: route.pattern, children: route.pattern }),
|
|
2415
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell profiler-flex-table__cell--muted profiler-text--mono profiler-text--xs", title: route.name ? `${route.name}_path` : "", children: route.name ? `${route.name}_path` : "\u2014" }),
|
|
2416
|
+
/* @__PURE__ */ u3("span", { class: "profiler-flex-table__cell profiler-flex-table__cell--muted profiler-text--mono profiler-text--xs", title: route.controller_action ?? "", children: route.controller_action ?? "\u2014" })
|
|
2417
|
+
] }, i3))
|
|
2418
|
+
] })
|
|
2419
|
+
] });
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
// app/assets/typescript/profiler/components/dashboard/tabs/I18nTab.tsx
|
|
2423
|
+
function I18nTab({ i18nData }) {
|
|
2424
|
+
const [filter, setFilter] = d2("ALL");
|
|
2425
|
+
if (!i18nData?.lookups?.length) {
|
|
2426
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
|
|
2427
|
+
/* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u{1F310}" }),
|
|
2428
|
+
/* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No translation lookups captured" }),
|
|
2429
|
+
/* @__PURE__ */ u3("div", { class: "profiler-empty__description", children: /* @__PURE__ */ u3("p", { children: [
|
|
2430
|
+
"Calls to ",
|
|
2431
|
+
/* @__PURE__ */ u3("code", { children: "I18n.t" }),
|
|
2432
|
+
" during this request will appear here."
|
|
2433
|
+
] }) })
|
|
2434
|
+
] });
|
|
2435
|
+
}
|
|
2436
|
+
const filtered = filter === "MISSING" ? i18nData.lookups.filter((l3) => l3.missing) : i18nData.lookups;
|
|
2437
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
2438
|
+
/* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
|
|
2439
|
+
"I18n Lookups (",
|
|
2440
|
+
i18nData.total,
|
|
2441
|
+
")"
|
|
2442
|
+
] }),
|
|
2443
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
|
|
2444
|
+
/* @__PURE__ */ u3("span", { children: [
|
|
2445
|
+
"Locale: ",
|
|
2446
|
+
/* @__PURE__ */ u3("strong", { children: i18nData.locale })
|
|
2447
|
+
] }),
|
|
2448
|
+
i18nData.missing_count > 0 && /* @__PURE__ */ u3("span", { children: [
|
|
2449
|
+
"Missing: ",
|
|
2450
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--error", children: i18nData.missing_count })
|
|
2451
|
+
] })
|
|
2452
|
+
] }),
|
|
2453
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2 profiler-mb-4", children: ["ALL", "MISSING"].map((f4) => /* @__PURE__ */ u3(
|
|
2454
|
+
"button",
|
|
2455
|
+
{
|
|
2456
|
+
onClick: () => setFilter(f4),
|
|
2457
|
+
class: `btn btn-sm ${filter === f4 ? "btn-primary" : "btn-secondary"}`,
|
|
2458
|
+
children: f4
|
|
2459
|
+
},
|
|
2460
|
+
f4
|
|
2461
|
+
)) }),
|
|
2462
|
+
filtered.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-text--muted profiler-text--sm", children: "No missing translations." }) : /* @__PURE__ */ u3("table", { class: "profiler-table", children: [
|
|
2463
|
+
/* @__PURE__ */ u3("thead", { children: /* @__PURE__ */ u3("tr", { children: [
|
|
2464
|
+
/* @__PURE__ */ u3("th", { children: "Key" }),
|
|
2465
|
+
/* @__PURE__ */ u3("th", { children: "Locale" }),
|
|
2466
|
+
/* @__PURE__ */ u3("th", { children: "Value" }),
|
|
2467
|
+
/* @__PURE__ */ u3("th", { children: "Status" })
|
|
2468
|
+
] }) }),
|
|
2469
|
+
/* @__PURE__ */ u3("tbody", { children: filtered.map((entry, index) => /* @__PURE__ */ u3("tr", { style: entry.missing ? "background:var(--profiler-error-bg,rgba(239,68,68,0.08));" : "", children: [
|
|
2470
|
+
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("code", { class: `profiler-text--xs profiler-text--mono${entry.missing ? " profiler-text--error" : ""}`, children: entry.key }) }),
|
|
2471
|
+
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", children: entry.locale }) }),
|
|
2472
|
+
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: `profiler-text--xs${entry.missing ? " profiler-text--error" : ""}`, children: entry.value }) }),
|
|
2473
|
+
/* @__PURE__ */ u3("td", { children: entry.missing ? /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--error", style: "font-weight:600;", children: "\u26A0 missing" }) : /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--success", children: "\u2713" }) })
|
|
2474
|
+
] }, index)) })
|
|
2475
|
+
] })
|
|
2476
|
+
] });
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2292
2479
|
// app/assets/typescript/profiler/components/dashboard/ProfileDashboard.tsx
|
|
2293
2480
|
function ProfileDashboard({ profile, initialTab, embedded }) {
|
|
2294
2481
|
const cd = profile.collectors_data || {};
|
|
@@ -2296,6 +2483,8 @@
|
|
|
2296
2483
|
const hasHttp = cd["http"]?.total_requests > 0;
|
|
2297
2484
|
const hasException = !!cd["exception"]?.exception_class;
|
|
2298
2485
|
const hasLogs = (cd["logs"]?.count ?? 0) > 0;
|
|
2486
|
+
const hasRoutes = (cd["routes"]?.total ?? 0) > 0;
|
|
2487
|
+
const hasI18n = (cd["i18n"]?.total ?? 0) > 0;
|
|
2299
2488
|
const [activeTab, setActiveTab] = d2(hasException ? "exception" : initialTab);
|
|
2300
2489
|
const handleTabClick = (tab) => (e3) => {
|
|
2301
2490
|
e3.preventDefault();
|
|
@@ -2348,7 +2537,9 @@
|
|
|
2348
2537
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("timeline"), onClick: handleTabClick("timeline"), children: "Timeline" }),
|
|
2349
2538
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("views"), onClick: handleTabClick("views"), children: "Views" }),
|
|
2350
2539
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
|
|
2351
|
-
hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" })
|
|
2540
|
+
hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" }),
|
|
2541
|
+
hasRoutes && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("routes"), onClick: handleTabClick("routes"), children: "Routes" }),
|
|
2542
|
+
hasI18n && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("i18n"), onClick: handleTabClick("i18n"), children: "I18n" })
|
|
2352
2543
|
] }),
|
|
2353
2544
|
/* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
|
|
2354
2545
|
activeTab === "exception" && /* @__PURE__ */ u3(ExceptionTab, { exceptionData: cd["exception"] }),
|
|
@@ -2360,7 +2551,9 @@
|
|
|
2360
2551
|
activeTab === "timeline" && /* @__PURE__ */ u3(FlameGraphTab, { flamegraphData: cd["flamegraph"], perfData: cd["performance"] }),
|
|
2361
2552
|
activeTab === "views" && /* @__PURE__ */ u3(ViewsTab, { viewData: cd["view"] }),
|
|
2362
2553
|
activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
|
|
2363
|
-
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] })
|
|
2554
|
+
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] }),
|
|
2555
|
+
activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] }),
|
|
2556
|
+
activeTab === "i18n" && /* @__PURE__ */ u3(I18nTab, { i18nData: cd["i18n"] })
|
|
2364
2557
|
] })
|
|
2365
2558
|
] }),
|
|
2366
2559
|
!embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_collector"
|
|
4
|
+
|
|
5
|
+
module Profiler
|
|
6
|
+
module I18nLookupTracker
|
|
7
|
+
def translate(key, **options)
|
|
8
|
+
result = super
|
|
9
|
+
if (collector = Thread.current[:profiler_i18n_collector])
|
|
10
|
+
missing = result.is_a?(String) && result.downcase.include?("translation missing:")
|
|
11
|
+
collector.record_lookup(key, options[:locale] || I18n.locale, result, missing)
|
|
12
|
+
end
|
|
13
|
+
result
|
|
14
|
+
rescue I18n::MissingTranslationData => e
|
|
15
|
+
if (collector = Thread.current[:profiler_i18n_collector])
|
|
16
|
+
collector.record_lookup(key, options[:locale] || I18n.locale, nil, true)
|
|
17
|
+
end
|
|
18
|
+
raise
|
|
19
|
+
end
|
|
20
|
+
alias_method :t, :translate
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Collectors
|
|
24
|
+
class I18nCollector < BaseCollector
|
|
25
|
+
def initialize(profile)
|
|
26
|
+
super
|
|
27
|
+
@lookups = []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def icon
|
|
31
|
+
"🌐"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def priority
|
|
35
|
+
45
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def tab_config
|
|
39
|
+
{
|
|
40
|
+
key: "i18n",
|
|
41
|
+
label: "I18n",
|
|
42
|
+
icon: icon,
|
|
43
|
+
priority: priority,
|
|
44
|
+
enabled: true,
|
|
45
|
+
default_active: false
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def subscribe
|
|
50
|
+
return unless defined?(I18n)
|
|
51
|
+
|
|
52
|
+
unless I18n.singleton_class.ancestors.include?(Profiler::I18nLookupTracker)
|
|
53
|
+
I18n.singleton_class.prepend(Profiler::I18nLookupTracker)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Thread.current[:profiler_i18n_collector] = self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def collect
|
|
60
|
+
Thread.current[:profiler_i18n_collector] = nil
|
|
61
|
+
|
|
62
|
+
missing_count = @lookups.count { |l| l[:missing] }
|
|
63
|
+
|
|
64
|
+
store_data(
|
|
65
|
+
locale: I18n.locale.to_s,
|
|
66
|
+
total: @lookups.size,
|
|
67
|
+
missing_count: missing_count,
|
|
68
|
+
lookups: @lookups
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def record_lookup(key, locale, result, missing = false)
|
|
73
|
+
value = missing ? "[missing]" : truncate(result.to_s)
|
|
74
|
+
|
|
75
|
+
@lookups << {
|
|
76
|
+
key: key.to_s,
|
|
77
|
+
locale: locale.to_s,
|
|
78
|
+
value: value,
|
|
79
|
+
missing: missing
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def toolbar_summary
|
|
84
|
+
missing = @lookups.count { |l| l[:missing] }
|
|
85
|
+
locale = I18n.locale.to_s
|
|
86
|
+
total = @lookups.size
|
|
87
|
+
|
|
88
|
+
color = missing > 0 ? "red" : "green"
|
|
89
|
+
|
|
90
|
+
{ text: "#{locale} · #{total} keys", color: color }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def truncate(str, max = 100)
|
|
96
|
+
str.length > max ? "#{str[0, max]}…" : str
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -42,6 +42,7 @@ module Profiler
|
|
|
42
42
|
finished_at: @profile.finished_at&.iso8601
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
data.merge!(collect_route_info)
|
|
45
46
|
store_data(data)
|
|
46
47
|
end
|
|
47
48
|
|
|
@@ -64,6 +65,39 @@ module Profiler
|
|
|
64
65
|
|
|
65
66
|
private
|
|
66
67
|
|
|
68
|
+
def collect_route_info
|
|
69
|
+
return {} unless defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
|
70
|
+
|
|
71
|
+
path = @profile.path
|
|
72
|
+
method = @profile.method
|
|
73
|
+
|
|
74
|
+
recognized = Rails.application.routes.recognize_path(path, method: method)
|
|
75
|
+
|
|
76
|
+
controller = recognized[:controller]
|
|
77
|
+
action = recognized[:action]
|
|
78
|
+
|
|
79
|
+
controller_action = if controller && action
|
|
80
|
+
"#{controller.split('/').map { |s| ActiveSupport::Inflector.camelize(s) }.join('::')}Controller##{action}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
route_params = recognized.except(:controller, :action, :format)
|
|
84
|
+
|
|
85
|
+
route_name, matched_route = Rails.application.routes.named_routes.find do |_name, route|
|
|
86
|
+
route.defaults[:controller] == controller &&
|
|
87
|
+
route.defaults[:action] == action &&
|
|
88
|
+
route.path.match(path)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
route_name: route_name ? "#{route_name}_path" : nil,
|
|
93
|
+
route_pattern: matched_route&.path&.spec&.to_s&.sub(/\(\.:format\)$/, ""),
|
|
94
|
+
route_params: route_params,
|
|
95
|
+
controller_action: controller_action
|
|
96
|
+
}
|
|
97
|
+
rescue StandardError
|
|
98
|
+
{}
|
|
99
|
+
end
|
|
100
|
+
|
|
67
101
|
def format_memory(bytes)
|
|
68
102
|
return "0 B" unless bytes
|
|
69
103
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_collector"
|
|
4
|
+
|
|
5
|
+
module Profiler
|
|
6
|
+
module Collectors
|
|
7
|
+
class RoutesCollector < BaseCollector
|
|
8
|
+
def icon
|
|
9
|
+
"🗺️"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def priority
|
|
13
|
+
12
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def tab_config
|
|
17
|
+
{
|
|
18
|
+
key: "routes",
|
|
19
|
+
label: "Routes",
|
|
20
|
+
icon: icon,
|
|
21
|
+
priority: priority,
|
|
22
|
+
enabled: true,
|
|
23
|
+
default_active: false
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def collect
|
|
28
|
+
unless defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
|
29
|
+
return store_data({ routes: [], total: 0 })
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
matched_controller, matched_action = recognize_current_route
|
|
33
|
+
routes = build_routes_list(matched_controller, matched_action)
|
|
34
|
+
matched_route = routes.find { |r| r[:matched] }
|
|
35
|
+
|
|
36
|
+
store_data({
|
|
37
|
+
total: routes.size,
|
|
38
|
+
matched: matched_route,
|
|
39
|
+
routes: routes
|
|
40
|
+
})
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def toolbar_summary
|
|
44
|
+
data = panel_content
|
|
45
|
+
matched = data[:matched]
|
|
46
|
+
{ text: matched ? matched[:pattern] : "—", color: "blue" }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def recognize_current_route
|
|
52
|
+
recognized = Rails.application.routes.recognize_path(@profile.path, method: @profile.method)
|
|
53
|
+
[recognized[:controller], recognized[:action]]
|
|
54
|
+
rescue StandardError
|
|
55
|
+
[nil, nil]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_routes_list(matched_controller, matched_action)
|
|
59
|
+
Rails.application.routes.routes.filter_map do |route|
|
|
60
|
+
next if route.respond_to?(:internal) && route.internal
|
|
61
|
+
|
|
62
|
+
controller = route.defaults[:controller]
|
|
63
|
+
action = route.defaults[:action]
|
|
64
|
+
next if controller.nil?
|
|
65
|
+
next if controller.start_with?("rails/", "profiler/")
|
|
66
|
+
|
|
67
|
+
name = route.name
|
|
68
|
+
pattern = route.path.spec.to_s.sub(/\(\.:format\)$/, "")
|
|
69
|
+
v = route.verb
|
|
70
|
+
verb = (v && !v.empty?) ? v : "ANY"
|
|
71
|
+
|
|
72
|
+
controller_action = if controller && action
|
|
73
|
+
"#{controller.split("/").map { |s| ActiveSupport::Inflector.camelize(s) }.join("::")}Controller##{action}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
{
|
|
77
|
+
name: name,
|
|
78
|
+
pattern: pattern,
|
|
79
|
+
verb: verb,
|
|
80
|
+
controller_action: controller_action,
|
|
81
|
+
matched: controller == matched_controller && action == matched_action
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
rescue StandardError
|
|
85
|
+
[]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -16,7 +16,9 @@ module Profiler
|
|
|
16
16
|
duration: profile.duration&.round(2),
|
|
17
17
|
memory: profile.memory ? (profile.memory / 1024.0 / 1024.0).round(2) : nil,
|
|
18
18
|
timestamp: profile.started_at&.iso8601,
|
|
19
|
-
query_count: profile.collector_data("database")&.dig("total_queries") || 0
|
|
19
|
+
query_count: profile.collector_data("database")&.dig("total_queries") || 0,
|
|
20
|
+
matched_route: profile.collector_data("routes")&.dig("matched", "pattern"),
|
|
21
|
+
controller_action: profile.collector_data("request")&.dig("controller_action")
|
|
20
22
|
}
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -266,6 +266,23 @@ module Profiler
|
|
|
266
266
|
end
|
|
267
267
|
end
|
|
268
268
|
|
|
269
|
+
# Routes section
|
|
270
|
+
routes_data = profile.collector_data("routes")
|
|
271
|
+
if routes_data && routes_data["total"].to_i > 0
|
|
272
|
+
lines << "## Routes"
|
|
273
|
+
lines << "- Total routes: #{routes_data['total']}"
|
|
274
|
+
|
|
275
|
+
matched = routes_data["matched"]
|
|
276
|
+
if matched
|
|
277
|
+
lines << "- **Matched:** `#{matched['verb']} #{matched['pattern']}`"
|
|
278
|
+
lines << " - Route name: #{matched['name']}_path" if matched["name"]
|
|
279
|
+
lines << " - Controller#Action: #{matched['controller_action']}" if matched["controller_action"]
|
|
280
|
+
else
|
|
281
|
+
lines << "- No route matched"
|
|
282
|
+
end
|
|
283
|
+
lines << ""
|
|
284
|
+
end
|
|
285
|
+
|
|
269
286
|
# Dumps section
|
|
270
287
|
dump_data = profile.collector_data("dump")
|
|
271
288
|
if dump_data && dump_data["count"].to_i > 0
|
data/lib/profiler/railtie.rb
CHANGED
|
@@ -44,7 +44,9 @@ module Profiler
|
|
|
44
44
|
Profiler::Collectors::CacheCollector,
|
|
45
45
|
Profiler::Collectors::HttpCollector,
|
|
46
46
|
Profiler::Collectors::FlameGraphCollector,
|
|
47
|
-
Profiler::Collectors::LogCollector
|
|
47
|
+
Profiler::Collectors::LogCollector,
|
|
48
|
+
Profiler::Collectors::RoutesCollector,
|
|
49
|
+
Profiler::Collectors::I18nCollector
|
|
48
50
|
]
|
|
49
51
|
end
|
|
50
52
|
end
|
data/lib/profiler/version.rb
CHANGED
data/lib/profiler.rb
CHANGED
|
@@ -65,6 +65,8 @@ require_relative "profiler/collectors/http_collector"
|
|
|
65
65
|
require_relative "profiler/collectors/flamegraph_collector"
|
|
66
66
|
require_relative "profiler/collectors/log_collector"
|
|
67
67
|
require_relative "profiler/collectors/exception_collector"
|
|
68
|
+
require_relative "profiler/collectors/routes_collector"
|
|
69
|
+
require_relative "profiler/collectors/i18n_collector"
|
|
68
70
|
|
|
69
71
|
require_relative "profiler/railtie" if defined?(Rails::Railtie)
|
|
70
72
|
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.4.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-03
|
|
11
|
+
date: 2026-04-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -129,10 +129,12 @@ files:
|
|
|
129
129
|
- lib/profiler/collectors/exception_collector.rb
|
|
130
130
|
- lib/profiler/collectors/flamegraph_collector.rb
|
|
131
131
|
- lib/profiler/collectors/http_collector.rb
|
|
132
|
+
- lib/profiler/collectors/i18n_collector.rb
|
|
132
133
|
- lib/profiler/collectors/job_collector.rb
|
|
133
134
|
- lib/profiler/collectors/log_collector.rb
|
|
134
135
|
- lib/profiler/collectors/performance_collector.rb
|
|
135
136
|
- lib/profiler/collectors/request_collector.rb
|
|
137
|
+
- lib/profiler/collectors/routes_collector.rb
|
|
136
138
|
- lib/profiler/collectors/view_collector.rb
|
|
137
139
|
- lib/profiler/configuration.rb
|
|
138
140
|
- lib/profiler/engine.rb
|