rails-profiler 0.2.0 → 0.3.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 +65 -0
- data/app/assets/builds/profiler.css +43 -0
- data/app/assets/builds/profiler.js +135 -2
- 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 +2 -1
- data/lib/profiler/version.rb +1 -1
- data/lib/profiler.rb +1 -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: 84a94ffe07e5c055045a375cc92f69638618f23a169717b532cadcecc98ac95f
|
|
4
|
+
data.tar.gz: c756cfb2bd7eff713976aaadc7e0d19375fc6747b47c6c421753dec3cc4bccdd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e971cfd7febeafd91af85f6dbf922b0d092f76bb3c77223b9d00d1b682af6c33a4e1fa50905d426dd9326b8e1d13e22e09da3c9c32e05a194c9d9e539969a206
|
|
7
|
+
data.tar.gz: f1a8b324dcae79a81bc63fa8a319f815c0610dbaa606c5db1c41c4d5226a69812a4946cb85987aa7e7a452a098478d3f61c61b17178b2dc396d745c7e77b26f1
|
|
@@ -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,38 @@
|
|
|
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
|
+
|
|
897
949
|
// app/assets/typescript/profiler/components/toolbar/ToolbarApp.tsx
|
|
898
950
|
function statusClass2(status) {
|
|
899
951
|
if (status >= 200 && status < 300) return "profiler-text--success";
|
|
@@ -926,6 +978,7 @@
|
|
|
926
978
|
const httpData = cd["http"];
|
|
927
979
|
const logData = cd["logs"];
|
|
928
980
|
const exceptionData = cd["exception"];
|
|
981
|
+
const routesData = cd["routes"];
|
|
929
982
|
const reqClass = statusClass2(profile.status);
|
|
930
983
|
const durClass = durationClass(profile.duration);
|
|
931
984
|
const dbClass = (dbData?.slow_queries ?? 0) > 0 ? "profiler-text--error" : "profiler-text--success";
|
|
@@ -1121,6 +1174,18 @@
|
|
|
1121
1174
|
]
|
|
1122
1175
|
}
|
|
1123
1176
|
),
|
|
1177
|
+
routesData && routesData.total > 0 && /* @__PURE__ */ u3(
|
|
1178
|
+
ToolbarItem,
|
|
1179
|
+
{
|
|
1180
|
+
href: `/_profiler/profiles/${token}?tab=routes`,
|
|
1181
|
+
panelLarge: true,
|
|
1182
|
+
panel: /* @__PURE__ */ u3(RoutesPanel, { routesData }),
|
|
1183
|
+
children: [
|
|
1184
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "ROUTE" }),
|
|
1185
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: routesData.matched?.pattern ?? "\u2014" })
|
|
1186
|
+
]
|
|
1187
|
+
}
|
|
1188
|
+
),
|
|
1124
1189
|
/* @__PURE__ */ u3("a", { href: "/_profiler", class: "profiler-toolbar-item", children: "\u2B21 Profiler" })
|
|
1125
1190
|
] });
|
|
1126
1191
|
}
|
|
@@ -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,119 @@
|
|
|
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
|
+
|
|
2292
2422
|
// app/assets/typescript/profiler/components/dashboard/ProfileDashboard.tsx
|
|
2293
2423
|
function ProfileDashboard({ profile, initialTab, embedded }) {
|
|
2294
2424
|
const cd = profile.collectors_data || {};
|
|
@@ -2296,6 +2426,7 @@
|
|
|
2296
2426
|
const hasHttp = cd["http"]?.total_requests > 0;
|
|
2297
2427
|
const hasException = !!cd["exception"]?.exception_class;
|
|
2298
2428
|
const hasLogs = (cd["logs"]?.count ?? 0) > 0;
|
|
2429
|
+
const hasRoutes = (cd["routes"]?.total ?? 0) > 0;
|
|
2299
2430
|
const [activeTab, setActiveTab] = d2(hasException ? "exception" : initialTab);
|
|
2300
2431
|
const handleTabClick = (tab) => (e3) => {
|
|
2301
2432
|
e3.preventDefault();
|
|
@@ -2348,7 +2479,8 @@
|
|
|
2348
2479
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("timeline"), onClick: handleTabClick("timeline"), children: "Timeline" }),
|
|
2349
2480
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("views"), onClick: handleTabClick("views"), children: "Views" }),
|
|
2350
2481
|
/* @__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" })
|
|
2482
|
+
hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" }),
|
|
2483
|
+
hasRoutes && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("routes"), onClick: handleTabClick("routes"), children: "Routes" })
|
|
2352
2484
|
] }),
|
|
2353
2485
|
/* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
|
|
2354
2486
|
activeTab === "exception" && /* @__PURE__ */ u3(ExceptionTab, { exceptionData: cd["exception"] }),
|
|
@@ -2360,7 +2492,8 @@
|
|
|
2360
2492
|
activeTab === "timeline" && /* @__PURE__ */ u3(FlameGraphTab, { flamegraphData: cd["flamegraph"], perfData: cd["performance"] }),
|
|
2361
2493
|
activeTab === "views" && /* @__PURE__ */ u3(ViewsTab, { viewData: cd["view"] }),
|
|
2362
2494
|
activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
|
|
2363
|
-
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] })
|
|
2495
|
+
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] }),
|
|
2496
|
+
activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] })
|
|
2364
2497
|
] })
|
|
2365
2498
|
] }),
|
|
2366
2499
|
!embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
|
|
@@ -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,8 @@ 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
|
|
48
49
|
]
|
|
49
50
|
end
|
|
50
51
|
end
|
data/lib/profiler/version.rb
CHANGED
data/lib/profiler.rb
CHANGED
|
@@ -65,6 +65,7 @@ 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"
|
|
68
69
|
|
|
69
70
|
require_relative "profiler/railtie" if defined?(Rails::Railtie)
|
|
70
71
|
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sébastien Duplessy
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -133,6 +133,7 @@ files:
|
|
|
133
133
|
- lib/profiler/collectors/log_collector.rb
|
|
134
134
|
- lib/profiler/collectors/performance_collector.rb
|
|
135
135
|
- lib/profiler/collectors/request_collector.rb
|
|
136
|
+
- lib/profiler/collectors/routes_collector.rb
|
|
136
137
|
- lib/profiler/collectors/view_collector.rb
|
|
137
138
|
- lib/profiler/configuration.rb
|
|
138
139
|
- lib/profiler/engine.rb
|