@64mb/freelens-power 1.5.0 → 1.6.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.
- package/README.md +4 -1
- package/out/main/ai-ipc.js +34 -3
- package/out/main/ai-ipc.js.map +1 -1
- package/out/main/common/power-logs-limits.js +227 -0
- package/out/main/common/power-logs-limits.js.map +1 -0
- package/out/main/common/power-logs.js +32 -0
- package/out/main/common/power-logs.js.map +1 -0
- package/out/main/common/store/logs-preferences-store.js +45 -0
- package/out/main/common/store/logs-preferences-store.js.map +1 -0
- package/out/main/dashboard-icon-cache.js +1 -1
- package/out/main/index.js +4 -0
- package/out/main/index.js.map +1 -1
- package/out/main/power-logs-budget.js +126 -0
- package/out/main/power-logs-budget.js.map +1 -0
- package/out/main/power-logs-child.js +86 -0
- package/out/main/power-logs-child.js.map +1 -0
- package/out/main/power-logs-database.js +179 -0
- package/out/main/power-logs-database.js.map +1 -0
- package/out/main/power-logs-ipc.js +275 -0
- package/out/main/power-logs-ipc.js.map +1 -0
- package/out/main/power-logs-query-child.js +23 -0
- package/out/main/power-logs-query-child.js.map +1 -0
- package/out/main/power-logs-query.js +127 -0
- package/out/main/power-logs-query.js.map +1 -0
- package/out/main/power-logs-worker.js +215 -0
- package/out/main/power-logs-worker.js.map +1 -0
- package/out/main/resource-search.js +1 -1
- package/out/renderer/ai-logs-sql.js +27 -0
- package/out/renderer/ai-logs-sql.js.map +1 -0
- package/out/renderer/ai-message.js +48 -27
- package/out/renderer/ai-message.js.map +1 -1
- package/out/renderer/ai-terminal-context.js +50 -40
- package/out/renderer/ai-terminal-context.js.map +1 -1
- package/out/renderer/ai.js +15 -2
- package/out/renderer/ai.js.map +1 -1
- package/out/renderer/appearance.js +1 -0
- package/out/renderer/appearance.js.map +1 -1
- package/out/renderer/index.js +67 -29
- package/out/renderer/index.js.map +1 -1
- package/out/renderer/power-log-json.js +29 -0
- package/out/renderer/power-log-json.js.map +1 -0
- package/out/renderer/power-logs-client.js +35 -0
- package/out/renderer/power-logs-client.js.map +1 -0
- package/out/renderer/power-logs-collector.js +316 -0
- package/out/renderer/power-logs-collector.js.map +1 -0
- package/out/renderer/power-logs-entrypoints.js +201 -0
- package/out/renderer/power-logs-entrypoints.js.map +1 -0
- package/out/renderer/power-logs-sql-row.js +52 -0
- package/out/renderer/power-logs-sql-row.js.map +1 -0
- package/out/renderer/power-logs-transport.js +122 -0
- package/out/renderer/power-logs-transport.js.map +1 -0
- package/out/renderer/power-logs.css.js +116 -0
- package/out/renderer/power-logs.css.js.map +1 -0
- package/out/renderer/power-logs.js +1352 -0
- package/out/renderer/power-logs.js.map +1 -0
- package/out/renderer/preferences/logs-preferences.js +93 -0
- package/out/renderer/preferences/logs-preferences.js.map +1 -0
- package/out/renderer/preferences/preferences.js +23 -9
- package/out/renderer/preferences/preferences.js.map +1 -1
- package/out/renderer/resource-relationships.js +1 -1
- package/out/renderer/src/common/power-log-parser.js +114 -0
- package/out/renderer/src/common/power-log-parser.js.map +1 -0
- package/out/renderer/src/common/power-logs-ai.js +56 -0
- package/out/renderer/src/common/power-logs-ai.js.map +1 -0
- package/out/renderer/src/common/power-logs-limits.js +227 -0
- package/out/renderer/src/common/power-logs-limits.js.map +1 -0
- package/out/renderer/src/common/power-logs.js +32 -0
- package/out/renderer/src/common/power-logs.js.map +1 -0
- package/out/renderer/src/common/store/logs-preferences-store.js +45 -0
- package/out/renderer/src/common/store/logs-preferences-store.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const require_power_logs = require("./common/power-logs.js");
|
|
2
|
+
//#region src/main/power-logs-budget.ts
|
|
3
|
+
var budgets = /* @__PURE__ */ new Map();
|
|
4
|
+
var active = false;
|
|
5
|
+
var POWER_LOG_STREAM_LEASE_MS = 3e4;
|
|
6
|
+
var validId = (value) => /^[a-zA-Z0-9_.:-]{1,253}$/.test(value);
|
|
7
|
+
var pendingCount = (budget) => budget.pending.size + [...budget.streams.values()].filter((stream) => !stream.active).length;
|
|
8
|
+
function getBudget(clusterId) {
|
|
9
|
+
if (!active) throw new Error("Log request canceled");
|
|
10
|
+
if (!validId(clusterId)) throw new Error("Invalid log budget");
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
for (const [key, value] of budgets) if (!value.pending.size && !value.streams.size && Math.max(value.start, value.bytes) <= now) budgets.delete(key);
|
|
13
|
+
let budget = budgets.get(clusterId);
|
|
14
|
+
if (!budget) {
|
|
15
|
+
if (budgets.size >= 100) throw new Error("Log request queue is full");
|
|
16
|
+
budget = {
|
|
17
|
+
start: 0,
|
|
18
|
+
bytes: 0,
|
|
19
|
+
pending: /* @__PURE__ */ new Set(),
|
|
20
|
+
streams: /* @__PURE__ */ new Map()
|
|
21
|
+
};
|
|
22
|
+
budgets.set(clusterId, budget);
|
|
23
|
+
}
|
|
24
|
+
return budget;
|
|
25
|
+
}
|
|
26
|
+
function activateStreams(clusterId, budget) {
|
|
27
|
+
for (const [id, stream] of budget.streams) {
|
|
28
|
+
if (stream.active) continue;
|
|
29
|
+
if ([...budget.streams.values()].filter((other) => other.active && other.follow === stream.follow).length >= (stream.follow ? require_power_logs.POWER_LOGS_LIMITS.liveConcurrency : require_power_logs.POWER_LOGS_LIMITS.historyConcurrency)) continue;
|
|
30
|
+
clearTimeout(stream.timer);
|
|
31
|
+
stream.active = true;
|
|
32
|
+
stream.timer = setTimeout(() => releasePowerLogStream(clusterId, id), POWER_LOG_STREAM_LEASE_MS);
|
|
33
|
+
stream.resolve();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function startPowerLogBudget() {
|
|
37
|
+
active = true;
|
|
38
|
+
}
|
|
39
|
+
function cancelPowerLogPermits(stop = false) {
|
|
40
|
+
if (stop) active = false;
|
|
41
|
+
for (const budget of budgets.values()) {
|
|
42
|
+
for (const wait of budget.pending) {
|
|
43
|
+
clearTimeout(wait.timer);
|
|
44
|
+
wait.detach();
|
|
45
|
+
wait.reject(/* @__PURE__ */ new Error("Log request canceled"));
|
|
46
|
+
}
|
|
47
|
+
for (const stream of budget.streams.values()) {
|
|
48
|
+
clearTimeout(stream.timer);
|
|
49
|
+
stream.detach();
|
|
50
|
+
if (!stream.active) stream.reject(/* @__PURE__ */ new Error("Log request canceled"));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
budgets.clear();
|
|
54
|
+
}
|
|
55
|
+
async function acquirePowerLogStream(clusterId, streamId, follow, signal) {
|
|
56
|
+
if (signal?.aborted || !validId(streamId) || typeof follow !== "boolean") throw new Error("Log request canceled");
|
|
57
|
+
const budget = getBudget(clusterId);
|
|
58
|
+
if (budget.streams.has(streamId)) throw new Error("Log stream already exists");
|
|
59
|
+
if (pendingCount(budget) >= 16) throw new Error("Log request queue is full");
|
|
60
|
+
await new Promise((resolve, reject) => {
|
|
61
|
+
const abort = () => releasePowerLogStream(clusterId, streamId);
|
|
62
|
+
const stream = {
|
|
63
|
+
follow,
|
|
64
|
+
active: false,
|
|
65
|
+
timer: setTimeout(abort, POWER_LOG_STREAM_LEASE_MS),
|
|
66
|
+
resolve,
|
|
67
|
+
reject,
|
|
68
|
+
detach: () => signal?.removeEventListener("abort", abort)
|
|
69
|
+
};
|
|
70
|
+
budget.streams.set(streamId, stream);
|
|
71
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
72
|
+
activateStreams(clusterId, budget);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function releasePowerLogStream(clusterId, streamId) {
|
|
76
|
+
const budget = budgets.get(clusterId), stream = budget?.streams.get(streamId);
|
|
77
|
+
if (!budget || !stream) return;
|
|
78
|
+
clearTimeout(stream.timer);
|
|
79
|
+
stream.detach();
|
|
80
|
+
budget.streams.delete(streamId);
|
|
81
|
+
if (!stream.active) stream.reject(/* @__PURE__ */ new Error("Log request canceled"));
|
|
82
|
+
activateStreams(clusterId, budget);
|
|
83
|
+
}
|
|
84
|
+
function renewPowerLogStream(clusterId, streamId) {
|
|
85
|
+
const budget = budgets.get(clusterId), stream = budget?.streams.get(streamId);
|
|
86
|
+
if (!budget || !stream?.active) throw new Error("Log stream lease expired");
|
|
87
|
+
clearTimeout(stream.timer);
|
|
88
|
+
stream.timer = setTimeout(() => releasePowerLogStream(clusterId, streamId), POWER_LOG_STREAM_LEASE_MS);
|
|
89
|
+
}
|
|
90
|
+
async function requestPowerLogPermit(clusterId, bytes, signal) {
|
|
91
|
+
if (signal?.aborted) throw new Error("Log request canceled");
|
|
92
|
+
if (bytes !== void 0 && (!Number.isSafeInteger(bytes) || bytes < 1 || bytes > require_power_logs.POWER_LOGS_LIMITS.batchBytes)) throw new Error("Invalid log budget");
|
|
93
|
+
const budget = getBudget(clusterId), now = Date.now();
|
|
94
|
+
if (pendingCount(budget) >= 16) throw new Error("Log request queue is full");
|
|
95
|
+
const lane = bytes === void 0 ? "start" : "bytes";
|
|
96
|
+
const start = Math.max(now, budget[lane]);
|
|
97
|
+
budget[lane] = start + (bytes === void 0 ? 1e3 / require_power_logs.POWER_LOGS_LIMITS.requestsPerSecond : bytes / require_power_logs.POWER_LOGS_LIMITS.bytesPerSecond * 1e3);
|
|
98
|
+
if (start <= now) return;
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
const finish = (error) => {
|
|
101
|
+
clearTimeout(wait.timer);
|
|
102
|
+
wait.detach();
|
|
103
|
+
budget.pending.delete(wait);
|
|
104
|
+
if (error) reject(error);
|
|
105
|
+
else resolve();
|
|
106
|
+
};
|
|
107
|
+
const abort = () => finish(/* @__PURE__ */ new Error("Log request canceled"));
|
|
108
|
+
const wait = {
|
|
109
|
+
timer: setTimeout(() => finish(), Math.ceil(start - now)),
|
|
110
|
+
reject,
|
|
111
|
+
detach: () => signal?.removeEventListener("abort", abort)
|
|
112
|
+
};
|
|
113
|
+
budget.pending.add(wait);
|
|
114
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
exports.POWER_LOG_STREAM_LEASE_MS = POWER_LOG_STREAM_LEASE_MS;
|
|
119
|
+
exports.acquirePowerLogStream = acquirePowerLogStream;
|
|
120
|
+
exports.cancelPowerLogPermits = cancelPowerLogPermits;
|
|
121
|
+
exports.releasePowerLogStream = releasePowerLogStream;
|
|
122
|
+
exports.renewPowerLogStream = renewPowerLogStream;
|
|
123
|
+
exports.requestPowerLogPermit = requestPowerLogPermit;
|
|
124
|
+
exports.startPowerLogBudget = startPowerLogBudget;
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=power-logs-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"power-logs-budget.js","names":[],"sources":["../../src/main/power-logs-budget.ts"],"sourcesContent":["import { POWER_LOGS_LIMITS as L } from \"../common/power-logs\";\n\ntype Wait = { timer: ReturnType<typeof setTimeout>; reject: (error: Error) => void; detach: () => void };\ntype Stream = {\n follow: boolean;\n active: boolean;\n timer: ReturnType<typeof setTimeout>;\n resolve: () => void;\n reject: (error: Error) => void;\n detach: () => void;\n};\ntype Budget = { start: number; bytes: number; pending: Set<Wait>; streams: Map<string, Stream> };\nconst budgets = new Map<string, Budget>();\nlet active = false;\nexport const POWER_LOG_STREAM_LEASE_MS = 30000;\nconst validId = (value: string) => /^[a-zA-Z0-9_.:-]{1,253}$/.test(value);\nconst pendingCount = (budget: Budget) =>\n budget.pending.size + [...budget.streams.values()].filter((stream) => !stream.active).length;\nfunction getBudget(clusterId: string): Budget {\n if (!active) throw new Error(\"Log request canceled\");\n if (!validId(clusterId)) throw new Error(\"Invalid log budget\");\n const now = Date.now();\n for (const [key, value] of budgets)\n if (!value.pending.size && !value.streams.size && Math.max(value.start, value.bytes) <= now) budgets.delete(key);\n let budget = budgets.get(clusterId);\n if (!budget) {\n if (budgets.size >= 100) throw new Error(\"Log request queue is full\");\n budget = { start: 0, bytes: 0, pending: new Set(), streams: new Map() };\n budgets.set(clusterId, budget);\n }\n return budget;\n}\nfunction activateStreams(clusterId: string, budget: Budget): void {\n for (const [id, stream] of budget.streams) {\n if (stream.active) continue;\n const count = [...budget.streams.values()].filter((other) => other.active && other.follow === stream.follow).length;\n if (count >= (stream.follow ? L.liveConcurrency : L.historyConcurrency)) continue;\n clearTimeout(stream.timer);\n stream.active = true;\n stream.timer = setTimeout(() => releasePowerLogStream(clusterId, id), POWER_LOG_STREAM_LEASE_MS);\n stream.resolve();\n }\n}\nexport function startPowerLogBudget(): void {\n active = true;\n}\nexport function cancelPowerLogPermits(stop = false): void {\n if (stop) active = false;\n for (const budget of budgets.values()) {\n for (const wait of budget.pending) {\n clearTimeout(wait.timer);\n wait.detach();\n wait.reject(new Error(\"Log request canceled\"));\n }\n for (const stream of budget.streams.values()) {\n clearTimeout(stream.timer);\n stream.detach();\n if (!stream.active) stream.reject(new Error(\"Log request canceled\"));\n }\n }\n budgets.clear();\n}\nexport async function acquirePowerLogStream(\n clusterId: string,\n streamId: string,\n follow: boolean,\n signal?: AbortSignal,\n): Promise<void> {\n if (signal?.aborted || !validId(streamId) || typeof follow !== \"boolean\") throw new Error(\"Log request canceled\");\n const budget = getBudget(clusterId);\n if (budget.streams.has(streamId)) throw new Error(\"Log stream already exists\");\n if (pendingCount(budget) >= 16) throw new Error(\"Log request queue is full\");\n await new Promise<void>((resolve, reject) => {\n const abort = () => releasePowerLogStream(clusterId, streamId);\n const stream: Stream = {\n follow,\n active: false,\n timer: setTimeout(abort, POWER_LOG_STREAM_LEASE_MS),\n resolve,\n reject,\n detach: () => signal?.removeEventListener(\"abort\", abort),\n };\n budget.streams.set(streamId, stream);\n signal?.addEventListener(\"abort\", abort, { once: true });\n activateStreams(clusterId, budget);\n });\n}\nexport function releasePowerLogStream(clusterId: string, streamId: string): void {\n const budget = budgets.get(clusterId),\n stream = budget?.streams.get(streamId);\n if (!budget || !stream) return;\n clearTimeout(stream.timer);\n stream.detach();\n budget.streams.delete(streamId);\n if (!stream.active) stream.reject(new Error(\"Log request canceled\"));\n activateStreams(clusterId, budget);\n}\nexport function renewPowerLogStream(clusterId: string, streamId: string): void {\n const budget = budgets.get(clusterId),\n stream = budget?.streams.get(streamId);\n if (!budget || !stream?.active) throw new Error(\"Log stream lease expired\");\n clearTimeout(stream.timer);\n stream.timer = setTimeout(() => releasePowerLogStream(clusterId, streamId), POWER_LOG_STREAM_LEASE_MS);\n}\nexport async function requestPowerLogPermit(clusterId: string, bytes?: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) throw new Error(\"Log request canceled\");\n if (bytes !== undefined && (!Number.isSafeInteger(bytes) || bytes < 1 || bytes > L.batchBytes))\n throw new Error(\"Invalid log budget\");\n const budget = getBudget(clusterId),\n now = Date.now();\n if (pendingCount(budget) >= 16) throw new Error(\"Log request queue is full\");\n const lane = bytes === undefined ? \"start\" : \"bytes\";\n const start = Math.max(now, budget[lane]);\n budget[lane] = start + (bytes === undefined ? 1000 / L.requestsPerSecond : (bytes / L.bytesPerSecond) * 1000);\n if (start <= now) return;\n await new Promise<void>((resolve, reject) => {\n const finish = (error?: Error) => {\n clearTimeout(wait.timer);\n wait.detach();\n budget.pending.delete(wait);\n if (error) reject(error);\n else resolve();\n };\n const abort = () => finish(new Error(\"Log request canceled\"));\n const wait: Wait = {\n timer: setTimeout(() => finish(), Math.ceil(start - now)),\n reject,\n detach: () => signal?.removeEventListener(\"abort\", abort),\n };\n budget.pending.add(wait);\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_power_logs_limits = require("./common/power-logs-limits.js");
|
|
3
|
+
const require_power_logs_database = require("./power-logs-database.js");
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
//#region src/main/power-logs-child.ts
|
|
6
|
+
var port = process.parentPort;
|
|
7
|
+
if (!port) throw new Error("Log process requires parent");
|
|
8
|
+
var engine = new require_power_logs_database.PowerLogsDatabase(process.argv[2]);
|
|
9
|
+
var queries = /* @__PURE__ */ new Map();
|
|
10
|
+
var queryKey = (request) => JSON.stringify([
|
|
11
|
+
request.clusterId,
|
|
12
|
+
request.contextId,
|
|
13
|
+
request.queryId
|
|
14
|
+
]);
|
|
15
|
+
var queue = Promise.resolve(), queuedCount = 0;
|
|
16
|
+
port.on("message", ({ data }) => {
|
|
17
|
+
if (data.request.operation === "cancel") {
|
|
18
|
+
const pending = queries.get(queryKey(data.request));
|
|
19
|
+
if (pending && !pending.started) {
|
|
20
|
+
if (!pending.canceled) port.postMessage({
|
|
21
|
+
id: pending.id,
|
|
22
|
+
error: "Log query canceled"
|
|
23
|
+
});
|
|
24
|
+
pending.canceled = true;
|
|
25
|
+
port.postMessage({
|
|
26
|
+
id: data.id,
|
|
27
|
+
result: true
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
engine.run(data.request).then((result) => port.postMessage({
|
|
32
|
+
id: data.id,
|
|
33
|
+
result
|
|
34
|
+
}), () => port.postMessage({
|
|
35
|
+
id: data.id,
|
|
36
|
+
error: "Log operation failed or exceeded its limit"
|
|
37
|
+
}));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const key = data.request.operation === "query" ? queryKey(data.request) : void 0;
|
|
41
|
+
if (key && queries.size) {
|
|
42
|
+
port.postMessage({
|
|
43
|
+
id: data.id,
|
|
44
|
+
error: "Log query is busy"
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (queuedCount >= 16) {
|
|
49
|
+
port.postMessage({
|
|
50
|
+
id: data.id,
|
|
51
|
+
error: "Log operation queue is full"
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const pending = key ? {
|
|
56
|
+
id: data.id,
|
|
57
|
+
started: false,
|
|
58
|
+
canceled: false
|
|
59
|
+
} : void 0;
|
|
60
|
+
if (key && pending) queries.set(key, pending);
|
|
61
|
+
queuedCount++;
|
|
62
|
+
const run = async () => {
|
|
63
|
+
try {
|
|
64
|
+
if (pending?.canceled) return;
|
|
65
|
+
require_power_logs_limits.applyPowerLogsLimits(data.limits);
|
|
66
|
+
if (pending) pending.started = true;
|
|
67
|
+
const result = data.request.operation === "export" && data.destination ? await (0, node_fs_promises.copyFile)(engine.exportPath(data.request), data.destination).then(() => true) : await engine.run(data.request);
|
|
68
|
+
port.postMessage({
|
|
69
|
+
id: data.id,
|
|
70
|
+
result
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
port.postMessage({
|
|
74
|
+
id: data.id,
|
|
75
|
+
error: "Log operation failed or exceeded its limit"
|
|
76
|
+
});
|
|
77
|
+
} finally {
|
|
78
|
+
queuedCount--;
|
|
79
|
+
if (key) queries.delete(key);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
queue = queue.then(run);
|
|
83
|
+
});
|
|
84
|
+
//#endregion
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=power-logs-child.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"power-logs-child.js","names":[],"sources":["../../src/main/power-logs-child.ts"],"sourcesContent":["import { copyFile } from \"node:fs/promises\";\nimport { applyPowerLogsLimits } from \"../common/power-logs-limits\";\nimport { PowerLogsDatabase } from \"./power-logs-database\";\n\nimport type { LogRequest } from \"../common/power-logs\";\n\nconst port = process.parentPort;\nif (!port) throw new Error(\"Log process requires parent\");\nconst engine = new PowerLogsDatabase(process.argv[2]);\nconst queries = new Map<string, { id: number; started: boolean; canceled: boolean }>();\nconst queryKey = (request: LogRequest & { queryId: string }) =>\n JSON.stringify([request.clusterId, request.contextId, request.queryId]);\nlet queue = Promise.resolve(),\n queuedCount = 0;\nport.on(\n \"message\",\n ({ data }: { data: { id: number; request: LogRequest; destination?: string; limits?: unknown } }) => {\n if (data.request.operation === \"cancel\") {\n const pending = queries.get(queryKey(data.request));\n if (pending && !pending.started) {\n if (!pending.canceled) port.postMessage({ id: pending.id, error: \"Log query canceled\" });\n pending.canceled = true;\n port.postMessage({ id: data.id, result: true });\n return;\n }\n void engine.run(data.request).then(\n (result) => port.postMessage({ id: data.id, result }),\n () => port.postMessage({ id: data.id, error: \"Log operation failed or exceeded its limit\" }),\n );\n return;\n }\n const key = data.request.operation === \"query\" ? queryKey(data.request) : undefined;\n if (key && queries.size) {\n port.postMessage({ id: data.id, error: \"Log query is busy\" });\n return;\n }\n if (queuedCount >= 16) {\n port.postMessage({ id: data.id, error: \"Log operation queue is full\" });\n return;\n }\n const pending = key ? { id: data.id, started: false, canceled: false } : undefined;\n if (key && pending) queries.set(key, pending);\n queuedCount++;\n const run = async () => {\n try {\n if (pending?.canceled) return;\n applyPowerLogsLimits(data.limits);\n if (pending) pending.started = true;\n const result =\n data.request.operation === \"export\" && data.destination\n ? await copyFile(engine.exportPath(data.request), data.destination).then(() => true)\n : await engine.run(data.request);\n port.postMessage({ id: data.id, result });\n } catch {\n port.postMessage({ id: data.id, error: \"Log operation failed or exceeded its limit\" });\n } finally {\n queuedCount--;\n if (key) queries.delete(key);\n }\n };\n queue = queue.then(run);\n },\n);\n"],"mappings":";;;;;AAMA,IAAM,OAAO,QAAQ;AACrB,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,6BAA6B;AACxD,IAAM,SAAS,IAAI,4BAAA,kBAAkB,QAAQ,KAAK,EAAE;AACpD,IAAM,0BAAU,IAAI,IAAiE;AACrF,IAAM,YAAY,YAChB,KAAK,UAAU;CAAC,QAAQ;CAAW,QAAQ;CAAW,QAAQ;AAAO,CAAC;AACxE,IAAI,QAAQ,QAAQ,QAAQ,GAC1B,cAAc;AAChB,KAAK,GACH,YACC,EAAE,WAAkG;CACnG,IAAI,KAAK,QAAQ,cAAc,UAAU;EACvC,MAAM,UAAU,QAAQ,IAAI,SAAS,KAAK,OAAO,CAAC;EAClD,IAAI,WAAW,CAAC,QAAQ,SAAS;GAC/B,IAAI,CAAC,QAAQ,UAAU,KAAK,YAAY;IAAE,IAAI,QAAQ;IAAI,OAAO;GAAqB,CAAC;GACvF,QAAQ,WAAW;GACnB,KAAK,YAAY;IAAE,IAAI,KAAK;IAAI,QAAQ;GAAK,CAAC;GAC9C;EACF;EACA,OAAY,IAAI,KAAK,OAAO,CAAC,CAAC,MAC3B,WAAW,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI;EAAO,CAAC,SAC9C,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,OAAO;EAA6C,CAAC,CAC7F;EACA;CACF;CACA,MAAM,MAAM,KAAK,QAAQ,cAAc,UAAU,SAAS,KAAK,OAAO,IAAI,KAAA;CAC1E,IAAI,OAAO,QAAQ,MAAM;EACvB,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,OAAO;EAAoB,CAAC;EAC5D;CACF;CACA,IAAI,eAAe,IAAI;EACrB,KAAK,YAAY;GAAE,IAAI,KAAK;GAAI,OAAO;EAA8B,CAAC;EACtE;CACF;CACA,MAAM,UAAU,MAAM;EAAE,IAAI,KAAK;EAAI,SAAS;EAAO,UAAU;CAAM,IAAI,KAAA;CACzE,IAAI,OAAO,SAAS,QAAQ,IAAI,KAAK,OAAO;CAC5C;CACA,MAAM,MAAM,YAAY;EACtB,IAAI;GACF,IAAI,SAAS,UAAU;GACvB,0BAAA,qBAAqB,KAAK,MAAM;GAChC,IAAI,SAAS,QAAQ,UAAU;GAC/B,MAAM,SACJ,KAAK,QAAQ,cAAc,YAAY,KAAK,cACxC,OAAA,GAAA,iBAAA,SAAA,CAAe,OAAO,WAAW,KAAK,OAAO,GAAG,KAAK,WAAW,CAAC,CAAC,WAAW,IAAI,IACjF,MAAM,OAAO,IAAI,KAAK,OAAO;GACnC,KAAK,YAAY;IAAE,IAAI,KAAK;IAAI;GAAO,CAAC;EAC1C,QAAQ;GACN,KAAK,YAAY;IAAE,IAAI,KAAK;IAAI,OAAO;GAA6C,CAAC;EACvF,UAAU;GACR;GACA,IAAI,KAAK,QAAQ,OAAO,GAAG;EAC7B;CACF;CACA,QAAQ,MAAM,KAAK,GAAG;AACxB,CACF"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const require_power_logs = require("./common/power-logs.js");
|
|
2
|
+
let node_crypto = require("node:crypto");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let node_sqlite = require("node:sqlite");
|
|
6
|
+
let node_readline = require("node:readline");
|
|
7
|
+
//#region src/main/power-logs-database.ts
|
|
8
|
+
var PowerLogsDatabase = class {
|
|
9
|
+
directory;
|
|
10
|
+
captures = /* @__PURE__ */ new Map();
|
|
11
|
+
closed = false;
|
|
12
|
+
timer;
|
|
13
|
+
constructor(directory) {
|
|
14
|
+
this.directory = directory;
|
|
15
|
+
(0, node_fs.mkdirSync)(directory, {
|
|
16
|
+
recursive: true,
|
|
17
|
+
mode: 448
|
|
18
|
+
});
|
|
19
|
+
(0, node_fs.chmodSync)(directory, 448);
|
|
20
|
+
this.timer = setInterval(() => this.expire(), 3e4);
|
|
21
|
+
this.timer.unref();
|
|
22
|
+
}
|
|
23
|
+
partition(request) {
|
|
24
|
+
return JSON.stringify([request.clusterId, request.contextId]);
|
|
25
|
+
}
|
|
26
|
+
expire() {
|
|
27
|
+
for (const [id, capture] of this.captures) {
|
|
28
|
+
if (capture.meta.expiresAt > Date.now()) continue;
|
|
29
|
+
try {
|
|
30
|
+
this.remove(id);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
remove(id) {
|
|
35
|
+
const capture = this.captures.get(id);
|
|
36
|
+
if (!capture) return;
|
|
37
|
+
try {
|
|
38
|
+
(0, node_fs.rmSync)(capture.directory, {
|
|
39
|
+
recursive: true,
|
|
40
|
+
force: true
|
|
41
|
+
});
|
|
42
|
+
} catch {
|
|
43
|
+
throw new Error("Log capture storage is busy or unavailable");
|
|
44
|
+
}
|
|
45
|
+
this.captures.delete(id);
|
|
46
|
+
}
|
|
47
|
+
exportPath(request) {
|
|
48
|
+
return this.capture(request).spool;
|
|
49
|
+
}
|
|
50
|
+
capture(request) {
|
|
51
|
+
const capture = this.captures.get(request.captureId);
|
|
52
|
+
if (!capture || capture.partition !== this.partition(request) || capture.meta.expiresAt <= Date.now()) throw new Error("Capture unavailable");
|
|
53
|
+
return capture;
|
|
54
|
+
}
|
|
55
|
+
async run(request) {
|
|
56
|
+
if (this.closed) throw new Error("Log storage unavailable");
|
|
57
|
+
this.expire();
|
|
58
|
+
if (request.operation === "permit" || "streamId" in request) throw new Error("Log permits require main process");
|
|
59
|
+
const partition = this.partition(request);
|
|
60
|
+
if (request.operation === "cancel") return true;
|
|
61
|
+
if (request.operation === "list") return [...this.captures.values()].filter((c) => c.partition === partition && c.meta.expiresAt > Date.now()).map((c) => ({ ...c.meta }));
|
|
62
|
+
if (request.operation === "create") {
|
|
63
|
+
if (this.captures.size >= require_power_logs.POWER_LOGS_LIMITS.captures) throw new Error("Capture limit reached");
|
|
64
|
+
const id = (0, node_crypto.randomUUID)(), directory = (0, node_path.join)(this.directory, id), now = Date.now();
|
|
65
|
+
(0, node_fs.mkdirSync)(directory, { mode: 448 });
|
|
66
|
+
const spool = (0, node_path.join)(directory, "logs.ndjson");
|
|
67
|
+
(0, node_fs.writeFileSync)(spool, "", { mode: 384 });
|
|
68
|
+
const meta = {
|
|
69
|
+
id,
|
|
70
|
+
target: request.target,
|
|
71
|
+
createdAt: now,
|
|
72
|
+
expiresAt: now + require_power_logs.POWER_LOGS_LIMITS.ttlMs,
|
|
73
|
+
state: "collecting",
|
|
74
|
+
rows: 0,
|
|
75
|
+
bytes: 0,
|
|
76
|
+
notes: []
|
|
77
|
+
};
|
|
78
|
+
this.captures.set(id, {
|
|
79
|
+
meta,
|
|
80
|
+
partition,
|
|
81
|
+
directory,
|
|
82
|
+
spool,
|
|
83
|
+
database: (0, node_path.join)(directory, "logs.sqlite"),
|
|
84
|
+
limits: {
|
|
85
|
+
captureBytes: require_power_logs.POWER_LOGS_LIMITS.captureBytes,
|
|
86
|
+
captureRows: require_power_logs.POWER_LOGS_LIMITS.captureRows
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return { ...meta };
|
|
90
|
+
}
|
|
91
|
+
const capture = this.capture(request);
|
|
92
|
+
if (request.operation === "delete") {
|
|
93
|
+
this.remove(request.captureId);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (request.operation === "export") throw new Error("Export requires trusted destination");
|
|
97
|
+
if (request.operation === "append") {
|
|
98
|
+
if (capture.meta.state !== "collecting") throw new Error("Capture is complete");
|
|
99
|
+
if (request.records.length > require_power_logs.POWER_LOGS_LIMITS.batchRows) throw new Error("Log batch exceeds limit");
|
|
100
|
+
const data = request.records.map((record) => JSON.stringify({
|
|
101
|
+
...record,
|
|
102
|
+
previous: record.previous === true
|
|
103
|
+
}) + "\n").join("");
|
|
104
|
+
const bytes = Buffer.byteLength(data);
|
|
105
|
+
if (bytes > require_power_logs.POWER_LOGS_LIMITS.batchBytes || capture.meta.bytes + bytes > capture.limits.captureBytes || capture.meta.rows + request.records.length > capture.limits.captureRows) throw new Error("Capture limit reached");
|
|
106
|
+
try {
|
|
107
|
+
(0, node_fs.appendFileSync)(capture.spool, data);
|
|
108
|
+
} catch {
|
|
109
|
+
(0, node_fs.truncateSync)(capture.spool, capture.meta.bytes);
|
|
110
|
+
throw new Error("Log capture storage unavailable");
|
|
111
|
+
}
|
|
112
|
+
capture.meta.rows += request.records.length;
|
|
113
|
+
capture.meta.bytes += bytes;
|
|
114
|
+
return { ...capture.meta };
|
|
115
|
+
}
|
|
116
|
+
if (request.operation === "finish") {
|
|
117
|
+
if (capture.meta.state !== "collecting") return { ...capture.meta };
|
|
118
|
+
let database;
|
|
119
|
+
try {
|
|
120
|
+
(0, node_fs.writeFileSync)(capture.database, "", { mode: 384 });
|
|
121
|
+
database = new node_sqlite.DatabaseSync(capture.database, { allowExtension: false });
|
|
122
|
+
database.exec("PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-16384; PRAGMA temp_store=MEMORY");
|
|
123
|
+
database.exec("CREATE TABLE logs(timestamp INTEGER, pod TEXT, podUid TEXT, container TEXT, level TEXT, message TEXT, fieldsJson TEXT, previous INTEGER CHECK(previous IN (0,1)))");
|
|
124
|
+
const insert = database.prepare("INSERT INTO logs VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
|
125
|
+
const input = (0, node_fs.createReadStream)(capture.spool);
|
|
126
|
+
const lines = (0, node_readline.createInterface)({
|
|
127
|
+
input,
|
|
128
|
+
crlfDelay: Infinity
|
|
129
|
+
});
|
|
130
|
+
let count = 0;
|
|
131
|
+
database.exec("BEGIN");
|
|
132
|
+
try {
|
|
133
|
+
for await (const line of lines) {
|
|
134
|
+
const record = JSON.parse(line);
|
|
135
|
+
insert.run(record.timestamp, record.pod, record.podUid, record.container, record.level, record.message, record.fieldsJson, record.previous ? 1 : 0);
|
|
136
|
+
if (++count % 5e3 === 0) {
|
|
137
|
+
database.exec("COMMIT; BEGIN");
|
|
138
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
database.exec("COMMIT");
|
|
142
|
+
} finally {
|
|
143
|
+
lines.close();
|
|
144
|
+
input.destroy();
|
|
145
|
+
}
|
|
146
|
+
database.exec("CREATE INDEX logs_timestamp ON logs(timestamp)");
|
|
147
|
+
} catch {
|
|
148
|
+
database?.close();
|
|
149
|
+
database = void 0;
|
|
150
|
+
(0, node_fs.rmSync)(capture.database, { force: true });
|
|
151
|
+
throw new Error("Log capture storage unavailable");
|
|
152
|
+
} finally {
|
|
153
|
+
database?.close();
|
|
154
|
+
}
|
|
155
|
+
capture.meta.state = request.partial ? "partial" : "ready";
|
|
156
|
+
capture.meta.notes = request.notes;
|
|
157
|
+
return { ...capture.meta };
|
|
158
|
+
}
|
|
159
|
+
if (capture.meta.state === "collecting") throw new Error("Finish capture before querying");
|
|
160
|
+
return {
|
|
161
|
+
database: capture.database,
|
|
162
|
+
captureId: capture.meta.id,
|
|
163
|
+
expiresAt: capture.meta.expiresAt
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
close() {
|
|
167
|
+
this.closed = true;
|
|
168
|
+
clearInterval(this.timer);
|
|
169
|
+
this.captures.clear();
|
|
170
|
+
(0, node_fs.rmSync)(this.directory, {
|
|
171
|
+
recursive: true,
|
|
172
|
+
force: true
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
exports.PowerLogsDatabase = PowerLogsDatabase;
|
|
178
|
+
|
|
179
|
+
//# sourceMappingURL=power-logs-database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"power-logs-database.js","names":[],"sources":["../../src/main/power-logs-database.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { appendFileSync, chmodSync, createReadStream, mkdirSync, rmSync, truncateSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { POWER_LOGS_LIMITS as L, type LogCapture, type LogRequest } from \"../common/power-logs\";\n\nimport type { PowerLogsQuerySnapshot } from \"./power-logs-query\";\n\ntype Capture = {\n meta: LogCapture;\n partition: string;\n directory: string;\n spool: string;\n database: string;\n limits: { captureBytes: number; captureRows: number };\n};\nexport class PowerLogsDatabase {\n private captures = new Map<string, Capture>();\n private closed = false;\n private timer: ReturnType<typeof setInterval>;\n constructor(private directory: string) {\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n chmodSync(directory, 0o700);\n this.timer = setInterval(() => this.expire(), 30000);\n this.timer.unref();\n }\n private partition(request: LogRequest): string {\n return JSON.stringify([request.clusterId, request.contextId]);\n }\n private expire(): void {\n for (const [id, capture] of this.captures) {\n if (capture.meta.expiresAt > Date.now()) continue;\n try {\n this.remove(id);\n } catch {\n // retain expired entries within the capture limit until a later sweep can remove locked files\n }\n }\n }\n private remove(id: string): void {\n const capture = this.captures.get(id);\n if (!capture) return;\n try {\n rmSync(capture.directory, { recursive: true, force: true });\n } catch {\n throw new Error(\"Log capture storage is busy or unavailable\");\n }\n this.captures.delete(id);\n }\n exportPath(request: LogRequest & { captureId: string }): string {\n return this.capture(request).spool;\n }\n private capture(request: LogRequest & { captureId: string }): Capture {\n const capture = this.captures.get(request.captureId);\n if (!capture || capture.partition !== this.partition(request) || capture.meta.expiresAt <= Date.now())\n throw new Error(\"Capture unavailable\");\n return capture;\n }\n async run(request: LogRequest): Promise<unknown> {\n if (this.closed) throw new Error(\"Log storage unavailable\");\n this.expire();\n if (request.operation === \"permit\" || \"streamId\" in request) throw new Error(\"Log permits require main process\");\n const partition = this.partition(request);\n if (request.operation === \"cancel\") return true;\n if (request.operation === \"list\")\n return [...this.captures.values()]\n .filter((c) => c.partition === partition && c.meta.expiresAt > Date.now())\n .map((c) => ({ ...c.meta }));\n if (request.operation === \"create\") {\n if (this.captures.size >= L.captures) throw new Error(\"Capture limit reached\");\n const id = randomUUID(),\n directory = join(this.directory, id),\n now = Date.now();\n mkdirSync(directory, { mode: 0o700 });\n const spool = join(directory, \"logs.ndjson\");\n writeFileSync(spool, \"\", { mode: 0o600 });\n const meta: LogCapture = {\n id,\n target: request.target,\n createdAt: now,\n expiresAt: now + L.ttlMs,\n state: \"collecting\",\n rows: 0,\n bytes: 0,\n notes: [],\n };\n this.captures.set(id, {\n meta,\n partition,\n directory,\n spool,\n database: join(directory, \"logs.sqlite\"),\n limits: { captureBytes: L.captureBytes, captureRows: L.captureRows },\n });\n return { ...meta };\n }\n const capture = this.capture(request);\n if (request.operation === \"delete\") {\n this.remove(request.captureId);\n return true;\n }\n if (request.operation === \"export\") throw new Error(\"Export requires trusted destination\");\n if (request.operation === \"append\") {\n if (capture.meta.state !== \"collecting\") throw new Error(\"Capture is complete\");\n if (request.records.length > L.batchRows) throw new Error(\"Log batch exceeds limit\");\n const data = request.records\n .map((record) => JSON.stringify({ ...record, previous: record.previous === true }) + \"\\n\")\n .join(\"\");\n const bytes = Buffer.byteLength(data);\n if (\n bytes > L.batchBytes ||\n capture.meta.bytes + bytes > capture.limits.captureBytes ||\n capture.meta.rows + request.records.length > capture.limits.captureRows\n )\n throw new Error(\"Capture limit reached\");\n try {\n appendFileSync(capture.spool, data);\n } catch {\n truncateSync(capture.spool, capture.meta.bytes);\n throw new Error(\"Log capture storage unavailable\");\n }\n capture.meta.rows += request.records.length;\n capture.meta.bytes += bytes;\n return { ...capture.meta };\n }\n if (request.operation === \"finish\") {\n if (capture.meta.state !== \"collecting\") return { ...capture.meta };\n let database: DatabaseSync | undefined;\n try {\n writeFileSync(capture.database, \"\", { mode: 0o600 });\n database = new DatabaseSync(capture.database, { allowExtension: false });\n database.exec(\n \"PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-16384; PRAGMA temp_store=MEMORY\",\n );\n database.exec(\n \"CREATE TABLE logs(timestamp INTEGER, pod TEXT, podUid TEXT, container TEXT, level TEXT, message TEXT, fieldsJson TEXT, previous INTEGER CHECK(previous IN (0,1)))\",\n );\n const insert = database.prepare(\"INSERT INTO logs VALUES (?, ?, ?, ?, ?, ?, ?, ?)\");\n const input = createReadStream(capture.spool);\n const lines = createInterface({ input, crlfDelay: Infinity });\n let count = 0;\n database.exec(\"BEGIN\");\n try {\n for await (const line of lines) {\n const record = JSON.parse(line);\n insert.run(\n record.timestamp,\n record.pod,\n record.podUid,\n record.container,\n record.level,\n record.message,\n record.fieldsJson,\n record.previous ? 1 : 0,\n );\n if (++count % 5000 === 0) {\n database.exec(\"COMMIT; BEGIN\");\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n database.exec(\"COMMIT\");\n } finally {\n lines.close();\n input.destroy();\n }\n database.exec(\"CREATE INDEX logs_timestamp ON logs(timestamp)\");\n } catch {\n database?.close();\n database = undefined;\n rmSync(capture.database, { force: true });\n throw new Error(\"Log capture storage unavailable\");\n } finally {\n database?.close();\n }\n capture.meta.state = request.partial ? \"partial\" : \"ready\";\n capture.meta.notes = request.notes;\n return { ...capture.meta };\n }\n if (capture.meta.state === \"collecting\") throw new Error(\"Finish capture before querying\");\n return {\n database: capture.database,\n captureId: capture.meta.id,\n expiresAt: capture.meta.expiresAt,\n } satisfies PowerLogsQuerySnapshot;\n }\n close(): void {\n this.closed = true;\n clearInterval(this.timer);\n this.captures.clear();\n rmSync(this.directory, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
|