rails-profiler 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/profiler.js +185 -33
- data/app/controllers/profiler/api/env_vars_controller.rb +28 -3
- 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/sidekiq_middleware.rb +1 -0
- data/lib/profiler/job_profiler.rb +14 -1
- data/lib/profiler/middleware/profiler_middleware.rb +8 -10
- data/lib/profiler/railtie.rb +4 -0
- data/lib/profiler/storage/sqlite_store.rb +3 -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: 713e1b01bcd916191e3ae2b89124f4637eeb95d7b9837d350d2ee76a575a2f14
|
|
4
|
+
data.tar.gz: 9f1becf53b9b39145a4ebdf81981793aeb5f0494cce1e6872facdcf11c0a597a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 298e772fd273b734126c095e57186e36d7f6fbaf26c7bb7db9899d02a38431231e4ecbde42f0b1a103deac26c29f48a596a065b5dd93f16a900b24d66e69a280
|
|
7
|
+
data.tar.gz: 78f23d51a52d0467e3acf9d912cbcbeebec03cf245632d88c7306d00d7c00645dda2be0a6208f3e58f4c815ea3a9efe0cb4e6783a3c6c4be44b784fd72e35f37
|
|
@@ -989,10 +989,26 @@
|
|
|
989
989
|
const body = await res.json().catch(() => ({}));
|
|
990
990
|
throw new Error(body.error ?? `Request failed (${res.status})`);
|
|
991
991
|
}
|
|
992
|
+
return res.json();
|
|
992
993
|
}
|
|
993
|
-
function
|
|
994
|
-
const
|
|
994
|
+
async function resetEnvVar(key) {
|
|
995
|
+
const res = await fetch(`/_profiler/api/env_vars/reset?key=${encodeURIComponent(key)}`, {
|
|
996
|
+
method: "DELETE"
|
|
997
|
+
});
|
|
998
|
+
if (!res.ok) {
|
|
999
|
+
const body = await res.json().catch(() => ({}));
|
|
1000
|
+
throw new Error(body.error ?? `Request failed (${res.status})`);
|
|
1001
|
+
}
|
|
1002
|
+
return res.json();
|
|
1003
|
+
}
|
|
1004
|
+
async function resetAllEnvVars() {
|
|
1005
|
+
const res = await fetch("/_profiler/api/env_vars/reset_all", { method: "DELETE" });
|
|
1006
|
+
if (!res.ok) throw new Error(`Request failed (${res.status})`);
|
|
1007
|
+
}
|
|
1008
|
+
function EnvTab({ envData, readOnly: forceReadOnly = false }) {
|
|
1009
|
+
const [initial, setInitial] = d2(() => ({ ...envData?.variables ?? {} }));
|
|
995
1010
|
const [variables, setVariables] = d2(initial);
|
|
1011
|
+
const [overrides, setOverrides] = d2(envData?.overrides ?? {});
|
|
996
1012
|
const [total, setTotal] = d2(envData?.total ?? 0);
|
|
997
1013
|
const [search, setSearch] = d2("");
|
|
998
1014
|
const [collapsedGroups, setCollapsedGroups] = d2(/* @__PURE__ */ new Set());
|
|
@@ -1005,7 +1021,7 @@
|
|
|
1005
1021
|
const [refreshing, setRefreshing] = d2(false);
|
|
1006
1022
|
const [unmaskedKeys, setUnmaskedKeys] = d2(loadUnmasked);
|
|
1007
1023
|
const [copiedId, setCopiedId] = d2(null);
|
|
1008
|
-
const [readOnly, setReadOnly] = d2(
|
|
1024
|
+
const [readOnly, setReadOnly] = d2(forceReadOnly);
|
|
1009
1025
|
const [showImport, setShowImport] = d2(false);
|
|
1010
1026
|
const [importContent, setImportContent] = d2("");
|
|
1011
1027
|
const editInputRef = A2(null);
|
|
@@ -1013,7 +1029,7 @@
|
|
|
1013
1029
|
if (editingKey !== null) editInputRef.current?.focus();
|
|
1014
1030
|
}, [editingKey]);
|
|
1015
1031
|
y2(() => {
|
|
1016
|
-
refresh();
|
|
1032
|
+
if (!forceReadOnly) refresh();
|
|
1017
1033
|
}, []);
|
|
1018
1034
|
const showFlash = (type, message) => {
|
|
1019
1035
|
setFlash({ type, message });
|
|
@@ -1053,7 +1069,9 @@
|
|
|
1053
1069
|
const data = await res.json();
|
|
1054
1070
|
setVariables(data.variables);
|
|
1055
1071
|
setTotal(data.total);
|
|
1072
|
+
setOverrides(data.overrides ?? {});
|
|
1056
1073
|
showFlash("success", `Refreshed \u2014 ${data.total} variables`);
|
|
1074
|
+
return data;
|
|
1057
1075
|
} catch (e3) {
|
|
1058
1076
|
showFlash("error", e3.message ?? "Failed to refresh");
|
|
1059
1077
|
} finally {
|
|
@@ -1100,11 +1118,25 @@
|
|
|
1100
1118
|
};
|
|
1101
1119
|
const saveEdit = async () => {
|
|
1102
1120
|
if (!editingKey) return;
|
|
1121
|
+
const key = editingKey;
|
|
1122
|
+
if (editValue === variables[key]) {
|
|
1123
|
+
setEditingKey(null);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1103
1126
|
setSaving(true);
|
|
1104
1127
|
try {
|
|
1105
|
-
await patchEnvVar(
|
|
1106
|
-
setVariables((prev) => ({ ...prev, [
|
|
1107
|
-
|
|
1128
|
+
const data = await patchEnvVar(key, editValue);
|
|
1129
|
+
setVariables((prev) => ({ ...prev, [key]: editValue }));
|
|
1130
|
+
setOverrides((prev) => {
|
|
1131
|
+
const n2 = { ...prev };
|
|
1132
|
+
if (data.override) {
|
|
1133
|
+
n2[key] = data.override;
|
|
1134
|
+
} else {
|
|
1135
|
+
delete n2[key];
|
|
1136
|
+
}
|
|
1137
|
+
return n2;
|
|
1138
|
+
});
|
|
1139
|
+
showFlash("success", `${key} updated`);
|
|
1108
1140
|
setEditingKey(null);
|
|
1109
1141
|
} catch (e3) {
|
|
1110
1142
|
showFlash("error", e3.message ?? "Failed to update");
|
|
@@ -1115,12 +1147,21 @@
|
|
|
1115
1147
|
const deleteVar = async (key) => {
|
|
1116
1148
|
setSaving(true);
|
|
1117
1149
|
try {
|
|
1118
|
-
await patchEnvVar(key, null);
|
|
1150
|
+
const data = await patchEnvVar(key, null);
|
|
1119
1151
|
setVariables((prev) => {
|
|
1120
1152
|
const n2 = { ...prev };
|
|
1121
1153
|
delete n2[key];
|
|
1122
1154
|
return n2;
|
|
1123
1155
|
});
|
|
1156
|
+
setOverrides((prev) => {
|
|
1157
|
+
const n2 = { ...prev };
|
|
1158
|
+
if (data.override) {
|
|
1159
|
+
n2[key] = data.override;
|
|
1160
|
+
} else {
|
|
1161
|
+
delete n2[key];
|
|
1162
|
+
}
|
|
1163
|
+
return n2;
|
|
1164
|
+
});
|
|
1124
1165
|
setUnmaskedKeys((prev) => {
|
|
1125
1166
|
const n2 = new Set(prev);
|
|
1126
1167
|
n2.delete(key);
|
|
@@ -1138,8 +1179,17 @@
|
|
|
1138
1179
|
const next = /^(true|yes)$/i.test(current) ? "false" : "true";
|
|
1139
1180
|
setSaving(true);
|
|
1140
1181
|
try {
|
|
1141
|
-
await patchEnvVar(key, next);
|
|
1182
|
+
const data = await patchEnvVar(key, next);
|
|
1142
1183
|
setVariables((prev) => ({ ...prev, [key]: next }));
|
|
1184
|
+
setOverrides((prev) => {
|
|
1185
|
+
const n2 = { ...prev };
|
|
1186
|
+
if (data.override) {
|
|
1187
|
+
n2[key] = data.override;
|
|
1188
|
+
} else {
|
|
1189
|
+
delete n2[key];
|
|
1190
|
+
}
|
|
1191
|
+
return n2;
|
|
1192
|
+
});
|
|
1143
1193
|
} catch (e3) {
|
|
1144
1194
|
showFlash("error", e3.message ?? "Failed to update");
|
|
1145
1195
|
} finally {
|
|
@@ -1151,8 +1201,17 @@
|
|
|
1151
1201
|
if (!key) return;
|
|
1152
1202
|
setSaving(true);
|
|
1153
1203
|
try {
|
|
1154
|
-
await patchEnvVar(key, newValue);
|
|
1204
|
+
const data = await patchEnvVar(key, newValue);
|
|
1155
1205
|
setVariables((prev) => ({ ...prev, [key]: newValue }));
|
|
1206
|
+
setOverrides((prev) => {
|
|
1207
|
+
const n2 = { ...prev };
|
|
1208
|
+
if (data.override) {
|
|
1209
|
+
n2[key] = data.override;
|
|
1210
|
+
} else {
|
|
1211
|
+
delete n2[key];
|
|
1212
|
+
}
|
|
1213
|
+
return n2;
|
|
1214
|
+
});
|
|
1156
1215
|
setNewKey("");
|
|
1157
1216
|
setNewValue("");
|
|
1158
1217
|
showFlash("success", `${key} added`);
|
|
@@ -1166,6 +1225,48 @@
|
|
|
1166
1225
|
if (e3.key === "Enter") saveEdit();
|
|
1167
1226
|
if (e3.key === "Escape") cancelEdit();
|
|
1168
1227
|
};
|
|
1228
|
+
const resetVar = async (key) => {
|
|
1229
|
+
setSaving(true);
|
|
1230
|
+
try {
|
|
1231
|
+
const data = await resetEnvVar(key);
|
|
1232
|
+
setOverrides((prev) => {
|
|
1233
|
+
const n2 = { ...prev };
|
|
1234
|
+
delete n2[key];
|
|
1235
|
+
return n2;
|
|
1236
|
+
});
|
|
1237
|
+
const updater = (prev) => {
|
|
1238
|
+
const n2 = { ...prev };
|
|
1239
|
+
if (data.value === null) {
|
|
1240
|
+
delete n2[key];
|
|
1241
|
+
} else {
|
|
1242
|
+
n2[key] = data.value;
|
|
1243
|
+
}
|
|
1244
|
+
return n2;
|
|
1245
|
+
};
|
|
1246
|
+
setVariables(updater);
|
|
1247
|
+
setInitial(updater);
|
|
1248
|
+
showFlash("success", `${key} reset to original`);
|
|
1249
|
+
} catch (e3) {
|
|
1250
|
+
showFlash("error", e3.message ?? "Failed to reset");
|
|
1251
|
+
} finally {
|
|
1252
|
+
setSaving(false);
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
const doResetAll = async () => {
|
|
1256
|
+
setSaving(true);
|
|
1257
|
+
try {
|
|
1258
|
+
await resetAllEnvVars();
|
|
1259
|
+
setOverrides({});
|
|
1260
|
+
const data = await refresh();
|
|
1261
|
+
if (data) setInitial(data.variables);
|
|
1262
|
+
showFlash("success", "All overrides reset");
|
|
1263
|
+
} catch (e3) {
|
|
1264
|
+
showFlash("error", e3.message ?? "Failed to reset all");
|
|
1265
|
+
} finally {
|
|
1266
|
+
setSaving(false);
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
const overrideCount = Object.keys(overrides).length;
|
|
1169
1270
|
const allEntries = Object.entries(variables);
|
|
1170
1271
|
const filteredEntries = T2(() => {
|
|
1171
1272
|
const q2 = search.toLowerCase().trim();
|
|
@@ -1205,11 +1306,27 @@
|
|
|
1205
1306
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-mb-4", style: "align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;", children: [
|
|
1206
1307
|
/* @__PURE__ */ u3("h2", { class: "profiler-section__header", style: "margin:0;", children: "Environment Variables" }),
|
|
1207
1308
|
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
|
|
1208
|
-
/* @__PURE__ */ u3(
|
|
1209
|
-
|
|
1309
|
+
!forceReadOnly && /* @__PURE__ */ u3(k, { children: [
|
|
1310
|
+
/* @__PURE__ */ u3("button", { onClick: refresh, disabled: refreshing, style: headerBtn, children: refreshing ? "\u2026" : "\u21BA Refresh" }),
|
|
1311
|
+
/* @__PURE__ */ u3("button", { onClick: () => setShowImport((v3) => !v3), style: `${headerBtn}${showImport ? "border-color:var(--profiler-accent);color:var(--profiler-accent);" : ""}`, children: "\u2B06 Import" })
|
|
1312
|
+
] }),
|
|
1210
1313
|
/* @__PURE__ */ u3("button", { onClick: exportEnv, style: headerBtn, children: "\u2B07 Export" }),
|
|
1211
1314
|
/* @__PURE__ */ u3("button", { onClick: toggleAllMasks, style: headerBtn, children: allRevealed ? "\u{1F648} Mask all" : "\u{1F441} Reveal all" }),
|
|
1212
|
-
/* @__PURE__ */ u3(
|
|
1315
|
+
!forceReadOnly && overrideCount > 0 && /* @__PURE__ */ u3(
|
|
1316
|
+
"button",
|
|
1317
|
+
{
|
|
1318
|
+
onClick: doResetAll,
|
|
1319
|
+
disabled: saving,
|
|
1320
|
+
title: "Remove all Sidekiq overrides and restore original values",
|
|
1321
|
+
style: `${headerBtn}border-color:var(--profiler-warning,#f59e0b);color:var(--profiler-warning,#f59e0b);`,
|
|
1322
|
+
children: [
|
|
1323
|
+
"\u21A9 Reset all (",
|
|
1324
|
+
overrideCount,
|
|
1325
|
+
")"
|
|
1326
|
+
]
|
|
1327
|
+
}
|
|
1328
|
+
),
|
|
1329
|
+
!forceReadOnly && /* @__PURE__ */ u3(
|
|
1213
1330
|
"button",
|
|
1214
1331
|
{
|
|
1215
1332
|
onClick: () => setReadOnly((v3) => !v3),
|
|
@@ -1220,7 +1337,11 @@
|
|
|
1220
1337
|
)
|
|
1221
1338
|
] })
|
|
1222
1339
|
] }),
|
|
1223
|
-
/* @__PURE__ */ u3("div", { style: "background:var(--profiler-
|
|
1340
|
+
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: [
|
|
1341
|
+
"Snapshot taken at request time \u2014 modify env vars in ",
|
|
1342
|
+
/* @__PURE__ */ u3("a", { href: "/_profiler?section=env", style: "color:var(--profiler-accent);", children: "/_profiler?section=env" }),
|
|
1343
|
+
"."
|
|
1344
|
+
] }) : /* @__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." }),
|
|
1224
1345
|
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: [
|
|
1225
1346
|
flash.type === "success" ? "\u2713" : "\u2717",
|
|
1226
1347
|
" ",
|
|
@@ -1315,7 +1436,8 @@
|
|
|
1315
1436
|
const isCopiedKey = copiedId === `__key__${key}`;
|
|
1316
1437
|
const wasModified = initial[key] !== void 0 && initial[key] !== value;
|
|
1317
1438
|
const wasAdded = initial[key] === void 0;
|
|
1318
|
-
const
|
|
1439
|
+
const override = overrides[key];
|
|
1440
|
+
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);" : "";
|
|
1319
1441
|
return /* @__PURE__ */ u3("tr", { style: diffStyle, children: [
|
|
1320
1442
|
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("div", { class: "profiler-flex", style: "align-items:center;gap:4px;", children: [
|
|
1321
1443
|
/* @__PURE__ */ u3("code", { class: "profiler-text--xs profiler-text--mono", children: key }),
|
|
@@ -1368,9 +1490,11 @@
|
|
|
1368
1490
|
}
|
|
1369
1491
|
)
|
|
1370
1492
|
] }),
|
|
1371
|
-
|
|
1372
|
-
"
|
|
1373
|
-
|
|
1493
|
+
override && /* @__PURE__ */ u3("div", { style: "font-size:10px;color:var(--profiler-accent);margin-top:2px;font-family:monospace;", children: [
|
|
1494
|
+
"workers \u2190 ",
|
|
1495
|
+
override.value === "__profiler_deleted__" ? "(deleted)" : override.value,
|
|
1496
|
+
" \xB7 original: ",
|
|
1497
|
+
override.original ?? "(unset)"
|
|
1374
1498
|
] })
|
|
1375
1499
|
] }) : /* @__PURE__ */ u3(k, { children: [
|
|
1376
1500
|
/* @__PURE__ */ u3("div", { class: "profiler-flex", style: "align-items:center;gap:4px;", children: [
|
|
@@ -1379,7 +1503,9 @@
|
|
|
1379
1503
|
"span",
|
|
1380
1504
|
{
|
|
1381
1505
|
class: "profiler-text--xs profiler-text--mono",
|
|
1382
|
-
|
|
1506
|
+
onClick: () => !readOnly && !isEditing && startEdit(key),
|
|
1507
|
+
title: !readOnly ? "Click to edit" : void 0,
|
|
1508
|
+
style: `word-break:break-all;flex:1;${!readOnly ? "cursor:pointer;" : ""}`,
|
|
1383
1509
|
children: unmasked ? isQuoted(value) ? /* @__PURE__ */ u3(k, { children: [
|
|
1384
1510
|
/* @__PURE__ */ u3("span", { style: "color:var(--profiler-text-muted);opacity:0.5;", children: '"' }),
|
|
1385
1511
|
value,
|
|
@@ -1406,20 +1532,29 @@
|
|
|
1406
1532
|
}
|
|
1407
1533
|
)
|
|
1408
1534
|
] }),
|
|
1409
|
-
|
|
1410
|
-
"
|
|
1411
|
-
|
|
1535
|
+
override && /* @__PURE__ */ u3("div", { style: "font-size:10px;color:var(--profiler-accent);margin-top:2px;font-family:monospace;", children: [
|
|
1536
|
+
"workers \u2190 ",
|
|
1537
|
+
override.value === "__profiler_deleted__" ? "(deleted)" : unmasked ? override.value : "\u2022".repeat(Math.min((override.value ?? "").length, 20)),
|
|
1538
|
+
" \xB7 original: ",
|
|
1539
|
+
override.original == null ? "(unset)" : unmasked ? override.original : "\u2022".repeat(Math.min(override.original.length, 20))
|
|
1412
1540
|
] })
|
|
1413
1541
|
] }) }),
|
|
1414
1542
|
!readOnly && /* @__PURE__ */ u3("td", { style: "text-align:right;white-space:nowrap;", children: isEditing ? /* @__PURE__ */ u3(k, { children: [
|
|
1415
1543
|
/* @__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" }),
|
|
1416
1544
|
/* @__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" })
|
|
1417
|
-
] }) : isBool(value) ? /* @__PURE__ */ u3(k, { children: [
|
|
1418
|
-
/* @__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" }),
|
|
1419
|
-
/* @__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" })
|
|
1420
1545
|
] }) : /* @__PURE__ */ u3(k, { children: [
|
|
1421
|
-
/* @__PURE__ */ u3("button", { onClick: () => startEdit(key), disabled: saving, style: "background:none;border:none;cursor:pointer;color:var(--profiler-accent);font-size:
|
|
1422
|
-
/* @__PURE__ */ u3(
|
|
1546
|
+
/* @__PURE__ */ u3("button", { onClick: () => startEdit(key), disabled: saving, title: "Edit", style: "background:none;border:none;cursor:pointer;color:var(--profiler-accent);font-size:14px;padding:0 4px;", children: "\u270E" }),
|
|
1547
|
+
override && /* @__PURE__ */ u3(
|
|
1548
|
+
"button",
|
|
1549
|
+
{
|
|
1550
|
+
onClick: () => resetVar(key),
|
|
1551
|
+
disabled: saving,
|
|
1552
|
+
title: `Reset to original: ${override.original ?? "(unset)"}`,
|
|
1553
|
+
style: "background:none;border:none;cursor:pointer;color:var(--profiler-warning,#f59e0b);font-size:14px;padding:0 4px;",
|
|
1554
|
+
children: "\u21A9"
|
|
1555
|
+
}
|
|
1556
|
+
),
|
|
1557
|
+
/* @__PURE__ */ u3("button", { onClick: () => deleteVar(key), disabled: saving, title: "Delete", style: "background:none;border:none;cursor:pointer;color:var(--profiler-error,#ef4444);font-size:14px;padding:0 4px;", children: "\u2715" })
|
|
1423
1558
|
] }) })
|
|
1424
1559
|
] }, key);
|
|
1425
1560
|
})
|
|
@@ -4385,7 +4520,7 @@
|
|
|
4385
4520
|
activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] }),
|
|
4386
4521
|
activeTab === "i18n" && /* @__PURE__ */ u3(I18nTab, { i18nData: cd["i18n"] }),
|
|
4387
4522
|
activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs }),
|
|
4388
|
-
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"] })
|
|
4523
|
+
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"], readOnly: true })
|
|
4389
4524
|
] })
|
|
4390
4525
|
] }),
|
|
4391
4526
|
!embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
|
|
@@ -4448,12 +4583,15 @@
|
|
|
4448
4583
|
|
|
4449
4584
|
// app/assets/typescript/profiler/components/dashboard/JobProfileDashboard.tsx
|
|
4450
4585
|
function JobProfileDashboard({ profile, initialTab, embedded }) {
|
|
4451
|
-
const validTabs = ["job", "database", "cache", "http", "jobs"];
|
|
4452
|
-
const defaultTab = validTabs.includes(initialTab) ? initialTab : "job";
|
|
4453
|
-
const [activeTab, setActiveTab] = d2(defaultTab);
|
|
4454
4586
|
const cd = profile.collectors_data || {};
|
|
4455
4587
|
const hasHttp = cd["http"]?.total_requests > 0;
|
|
4456
4588
|
const hasJobs = (profile.child_jobs?.length ?? 0) > 0;
|
|
4589
|
+
const hasDumps = (cd["dump"]?.count ?? 0) > 0;
|
|
4590
|
+
const hasLogs = (cd["logs"]?.total ?? 0) > 0;
|
|
4591
|
+
const hasException = !!cd["exception"]?.exception_class;
|
|
4592
|
+
const validTabs = ["job", "database", "cache", "http", "jobs", "dump", "logs", "exception", "env", "timeline"];
|
|
4593
|
+
const defaultTab = validTabs.includes(initialTab) ? initialTab : "job";
|
|
4594
|
+
const [activeTab, setActiveTab] = d2(hasException ? "exception" : defaultTab);
|
|
4457
4595
|
const jobData = cd["job"];
|
|
4458
4596
|
const isFailed = jobData?.status === "failed";
|
|
4459
4597
|
const parent = profile.parent_profile;
|
|
@@ -4515,22 +4653,36 @@
|
|
|
4515
4653
|
] }),
|
|
4516
4654
|
/* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
|
|
4517
4655
|
/* @__PURE__ */ u3("div", { class: "tabs", children: [
|
|
4656
|
+
hasException && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("exception"), onClick: handleTabClick("exception"), style: "color:var(--profiler-error,#ef4444);", children: "\u{1F4A5} Exception" }),
|
|
4518
4657
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("job"), onClick: handleTabClick("job"), children: "Job" }),
|
|
4519
4658
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("database"), onClick: handleTabClick("database"), children: "Database" }),
|
|
4520
4659
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
|
|
4521
4660
|
hasHttp && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("http"), onClick: handleTabClick("http"), children: "Outbound HTTP" }),
|
|
4661
|
+
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("timeline"), onClick: handleTabClick("timeline"), children: "Timeline" }),
|
|
4522
4662
|
hasJobs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("jobs"), onClick: handleTabClick("jobs"), children: [
|
|
4523
4663
|
"Jobs (",
|
|
4524
4664
|
profile.child_jobs.length,
|
|
4525
4665
|
")"
|
|
4526
|
-
] })
|
|
4666
|
+
] }),
|
|
4667
|
+
hasDumps && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("dump"), onClick: handleTabClick("dump"), children: [
|
|
4668
|
+
"Dumps (",
|
|
4669
|
+
cd["dump"].count,
|
|
4670
|
+
")"
|
|
4671
|
+
] }),
|
|
4672
|
+
hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" }),
|
|
4673
|
+
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("env"), onClick: handleTabClick("env"), children: "Env" })
|
|
4527
4674
|
] }),
|
|
4528
4675
|
/* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
|
|
4676
|
+
activeTab === "exception" && /* @__PURE__ */ u3(ExceptionTab, { exceptionData: cd["exception"] }),
|
|
4529
4677
|
activeTab === "job" && /* @__PURE__ */ u3(JobTab, { jobData: cd["job"] }),
|
|
4530
4678
|
activeTab === "database" && /* @__PURE__ */ u3(DatabaseTab, { dbData: cd["database"], token: profile.token }),
|
|
4531
4679
|
activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
|
|
4532
4680
|
activeTab === "http" && /* @__PURE__ */ u3(HttpTab, { httpData: cd["http"] }),
|
|
4533
|
-
activeTab === "
|
|
4681
|
+
activeTab === "timeline" && /* @__PURE__ */ u3(FlameGraphTab, { flamegraphData: cd["flamegraph"], perfData: cd["performance"], functionProfileData: cd["function_profile"] }),
|
|
4682
|
+
activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs }),
|
|
4683
|
+
activeTab === "dump" && /* @__PURE__ */ u3(DumpsTab, { dumpData: cd["dump"] }),
|
|
4684
|
+
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] }),
|
|
4685
|
+
activeTab === "env" && /* @__PURE__ */ u3(EnvTab, { envData: cd["env"], readOnly: true })
|
|
4534
4686
|
] })
|
|
4535
4687
|
] }),
|
|
4536
4688
|
!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
|
|
@@ -21,13 +22,37 @@ module Profiler
|
|
|
21
22
|
value = params[:value]
|
|
22
23
|
|
|
23
24
|
if value.nil? || value.to_s.empty?
|
|
25
|
+
Profiler.env_override_store.delete(key)
|
|
24
26
|
ENV.delete(key)
|
|
25
|
-
render json: { key: key, value: nil, deleted: true }
|
|
27
|
+
render json: { key: key, value: nil, deleted: true, override: Profiler.env_override_store.all_overrides[key] }
|
|
26
28
|
else
|
|
29
|
+
current_original = Profiler.env_override_store.all_overrides.dig(key, "original")
|
|
30
|
+
if current_original == value.to_s
|
|
31
|
+
Profiler.env_override_store.reset(key)
|
|
32
|
+
else
|
|
33
|
+
Profiler.env_override_store.set(key, value.to_s)
|
|
34
|
+
end
|
|
27
35
|
ENV[key] = value.to_s
|
|
28
|
-
render json: { key: key, value: ENV[key] }
|
|
36
|
+
render json: { key: key, value: ENV[key], override: Profiler.env_override_store.all_overrides[key] }
|
|
29
37
|
end
|
|
30
38
|
end
|
|
39
|
+
|
|
40
|
+
def reset_override
|
|
41
|
+
key = params[:key].to_s.strip
|
|
42
|
+
|
|
43
|
+
if key.blank?
|
|
44
|
+
render json: { error: "Key cannot be blank" }, status: :unprocessable_entity
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Profiler.env_override_store.reset(key)
|
|
49
|
+
render json: { key: key, value: ENV[key], reset: true }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def reset_all
|
|
53
|
+
Profiler.env_override_store.reset_all
|
|
54
|
+
render json: { reset: true }
|
|
55
|
+
end
|
|
31
56
|
end
|
|
32
57
|
end
|
|
33
58
|
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
|
|
@@ -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
|
|
@@ -14,6 +14,11 @@ module Profiler
|
|
|
14
14
|
def call(env)
|
|
15
15
|
return @app.call(env) unless should_profile?(env)
|
|
16
16
|
|
|
17
|
+
status = nil
|
|
18
|
+
headers = nil
|
|
19
|
+
body = nil
|
|
20
|
+
collectors = nil
|
|
21
|
+
|
|
17
22
|
profile = Models::Profile.new(build_request(env))
|
|
18
23
|
Profiler::CurrentContext.token = profile.token
|
|
19
24
|
|
|
@@ -23,14 +28,12 @@ module Profiler
|
|
|
23
28
|
# Store profile in env for collectors
|
|
24
29
|
env["profiler.profile"] = profile
|
|
25
30
|
|
|
26
|
-
# Create and subscribe collectors
|
|
27
31
|
collectors = create_collectors(profile)
|
|
28
32
|
env["profiler.collectors"] = collectors
|
|
29
33
|
|
|
30
34
|
# Measure memory before
|
|
31
35
|
memory_before = current_memory if Profiler.configuration.track_memory
|
|
32
36
|
|
|
33
|
-
# Process request
|
|
34
37
|
status, headers, body = @app.call(env)
|
|
35
38
|
|
|
36
39
|
# Measure memory after
|
|
@@ -39,14 +42,11 @@ module Profiler
|
|
|
39
42
|
profile.memory = memory_after - memory_before
|
|
40
43
|
end
|
|
41
44
|
|
|
42
|
-
# Collect and buffer response body (avoids double-reading by ToolbarInjector)
|
|
43
45
|
body_content = collect_body(body)
|
|
44
46
|
body = [body_content]
|
|
45
47
|
|
|
46
|
-
# Finish profile
|
|
47
48
|
profile.finish(status, headers)
|
|
48
49
|
|
|
49
|
-
# Store request and response bodies
|
|
50
50
|
profile.set_bodies(
|
|
51
51
|
request_body: req_body_raw,
|
|
52
52
|
response_body: body_content,
|
|
@@ -54,7 +54,6 @@ module Profiler
|
|
|
54
54
|
resp_content_type: (headers["content-type"] || headers["Content-Type"]).to_s
|
|
55
55
|
)
|
|
56
56
|
|
|
57
|
-
# Collect data from all collectors
|
|
58
57
|
collectors.each do |collector|
|
|
59
58
|
begin
|
|
60
59
|
collector.collect if collector.respond_to?(:collect)
|
|
@@ -64,14 +63,11 @@ module Profiler
|
|
|
64
63
|
end
|
|
65
64
|
end
|
|
66
65
|
|
|
67
|
-
# Store profile
|
|
68
66
|
Profiler.storage.save(profile.token, profile)
|
|
69
67
|
Profiler::CurrentContext.clear
|
|
70
68
|
|
|
71
|
-
# Add profiler token header
|
|
72
69
|
headers["X-Profiler-Token"] = profile.token
|
|
73
70
|
|
|
74
|
-
# Inject toolbar if HTML response
|
|
75
71
|
if html_response?(headers)
|
|
76
72
|
nonce = env['action_dispatch.content_security_policy_nonce']
|
|
77
73
|
body = ToolbarInjector.new(body, profile.token, nonce).inject
|
|
@@ -80,7 +76,9 @@ module Profiler
|
|
|
80
76
|
[status, headers, body]
|
|
81
77
|
rescue => e
|
|
82
78
|
warn "Profiler error: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
83
|
-
|
|
79
|
+
collectors&.each { |c| c.unsubscribe if c.respond_to?(:unsubscribe) }
|
|
80
|
+
Profiler::CurrentContext.clear
|
|
81
|
+
status ? [status, headers, body || []] : @app.call(env)
|
|
84
82
|
end
|
|
85
83
|
|
|
86
84
|
private
|
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|
|
|
@@ -19,6 +19,9 @@ module Profiler
|
|
|
19
19
|
|
|
20
20
|
@db = SQLite3::Database.new(db_path.to_s)
|
|
21
21
|
@db.results_as_hash = true
|
|
22
|
+
@db.busy_timeout = 5000
|
|
23
|
+
@db.execute("PRAGMA journal_mode=WAL")
|
|
24
|
+
@db.execute("PRAGMA synchronous=NORMAL")
|
|
22
25
|
|
|
23
26
|
@blob_store = BlobStore.new(blob_path.to_s)
|
|
24
27
|
|
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.1
|
|
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-17 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
|