hillclimb 0.9.13 → 0.9.14
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/dist/git-diagnostics-worker.js +205 -0
- package/dist/main.js +421 -239
- package/package.json +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// src/git-traces/git-diagnostics-worker.ts
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import net from "net";
|
|
4
|
+
import path2 from "path";
|
|
5
|
+
import { parentPort, workerData } from "worker_threads";
|
|
6
|
+
|
|
7
|
+
// src/git-traces/git-diagnostics.ts
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { Worker } from "worker_threads";
|
|
12
|
+
var SAMPLE = {
|
|
13
|
+
stop: 0,
|
|
14
|
+
count: 1,
|
|
15
|
+
cpuMs: 2,
|
|
16
|
+
elapsedMs: 3,
|
|
17
|
+
bytes: 4,
|
|
18
|
+
growthMs: 5
|
|
19
|
+
};
|
|
20
|
+
var TRACE = { ready: 0, length: 1, partial: 2, done: 3 };
|
|
21
|
+
function bundleBytes(file) {
|
|
22
|
+
let bytes = 0;
|
|
23
|
+
if (!file) return bytes;
|
|
24
|
+
for (const candidate of [file, `${file}.lock`]) {
|
|
25
|
+
try {
|
|
26
|
+
bytes = Math.max(bytes, fs.statSync(candidate).size);
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return bytes;
|
|
31
|
+
}
|
|
32
|
+
function parseCpuTime(value) {
|
|
33
|
+
const m = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+(?:\.\d+)?)$/.exec(value);
|
|
34
|
+
return m ? Math.round(
|
|
35
|
+
(Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4])) * 1e3
|
|
36
|
+
) : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/git-traces/git-diagnostics-worker.ts
|
|
40
|
+
var { parentPid, bundleFile, started } = workerData;
|
|
41
|
+
var samples = new BigInt64Array(workerData.samples);
|
|
42
|
+
var control = new Int32Array(workerData.control);
|
|
43
|
+
var trace = Buffer.from(workerData.trace);
|
|
44
|
+
var sockets = /* @__PURE__ */ new Set();
|
|
45
|
+
var server;
|
|
46
|
+
var full = false;
|
|
47
|
+
var probe;
|
|
48
|
+
function ready(value) {
|
|
49
|
+
Atomics.store(control, TRACE.ready, value);
|
|
50
|
+
Atomics.notify(control, TRACE.ready);
|
|
51
|
+
}
|
|
52
|
+
if (workerData.socketPath) {
|
|
53
|
+
server = net.createServer((socket) => {
|
|
54
|
+
socket.on("error", () => {
|
|
55
|
+
});
|
|
56
|
+
if (sockets.size >= 16) {
|
|
57
|
+
Atomics.store(control, TRACE.partial, 1);
|
|
58
|
+
socket.destroy();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
sockets.add(socket);
|
|
62
|
+
let pending = Buffer.alloc(0);
|
|
63
|
+
let dropping = false;
|
|
64
|
+
socket.on("data", (chunk) => {
|
|
65
|
+
let offset = 0;
|
|
66
|
+
while (offset < chunk.length) {
|
|
67
|
+
const length = Atomics.load(control, TRACE.length);
|
|
68
|
+
if (full || length === trace.length) {
|
|
69
|
+
Atomics.store(control, TRACE.partial, 1);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const newline = chunk.indexOf(10, offset);
|
|
73
|
+
const end = newline < 0 ? chunk.length : newline + 1;
|
|
74
|
+
const piece = chunk.subarray(offset, end);
|
|
75
|
+
if (!dropping && pending.length + piece.length > 16384) {
|
|
76
|
+
dropping = true;
|
|
77
|
+
pending = Buffer.alloc(0);
|
|
78
|
+
Atomics.store(control, TRACE.partial, 1);
|
|
79
|
+
}
|
|
80
|
+
if (!dropping) {
|
|
81
|
+
if (newline < 0) pending = Buffer.concat([pending, piece]);
|
|
82
|
+
else {
|
|
83
|
+
const record = pending.length ? Buffer.concat([pending, piece]) : piece;
|
|
84
|
+
if (length + record.length <= trace.length) {
|
|
85
|
+
record.copy(trace, length);
|
|
86
|
+
Atomics.store(control, TRACE.length, length + record.length);
|
|
87
|
+
} else {
|
|
88
|
+
Atomics.store(control, TRACE.partial, 1);
|
|
89
|
+
full = true;
|
|
90
|
+
}
|
|
91
|
+
pending = Buffer.alloc(0);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (newline >= 0) dropping = false;
|
|
95
|
+
offset = end;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
socket.on("close", () => {
|
|
99
|
+
if (pending.length || dropping) Atomics.store(control, TRACE.partial, 1);
|
|
100
|
+
sockets.delete(socket);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
server.on("error", () => ready(-1));
|
|
104
|
+
server.listen(workerData.socketPath, () => ready(1));
|
|
105
|
+
} else ready(-1);
|
|
106
|
+
parentPort?.once("message", async () => {
|
|
107
|
+
const stopped = new Promise((resolve) => {
|
|
108
|
+
if (!probe) resolve();
|
|
109
|
+
else {
|
|
110
|
+
probe.once("close", () => resolve());
|
|
111
|
+
probe.kill("SIGKILL");
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const drained = new Promise((resolve) => {
|
|
115
|
+
if (!server?.listening) resolve();
|
|
116
|
+
else {
|
|
117
|
+
server.close(() => resolve());
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
if (sockets.size) Atomics.store(control, TRACE.partial, 1);
|
|
120
|
+
for (const socket of sockets) socket.destroy();
|
|
121
|
+
resolve();
|
|
122
|
+
}, 50).unref();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
await Promise.all([stopped, drained]);
|
|
126
|
+
Atomics.store(control, TRACE.done, 1);
|
|
127
|
+
Atomics.notify(control, TRACE.done);
|
|
128
|
+
});
|
|
129
|
+
var cpuByPid = /* @__PURE__ */ new Map();
|
|
130
|
+
var gitPid;
|
|
131
|
+
var ambiguous = false;
|
|
132
|
+
async function sample() {
|
|
133
|
+
if (Atomics.load(samples, SAMPLE.stop)) return;
|
|
134
|
+
const elapsed = Date.now() - started;
|
|
135
|
+
const bytes = bundleBytes(bundleFile);
|
|
136
|
+
if (bytes > Number(Atomics.load(samples, SAMPLE.bytes))) {
|
|
137
|
+
Atomics.store(samples, SAMPLE.bytes, BigInt(bytes));
|
|
138
|
+
Atomics.store(samples, SAMPLE.growthMs, BigInt(elapsed));
|
|
139
|
+
}
|
|
140
|
+
if (process.platform !== "win32") {
|
|
141
|
+
try {
|
|
142
|
+
const output = await new Promise((resolve, reject) => {
|
|
143
|
+
probe = execFile(
|
|
144
|
+
"ps",
|
|
145
|
+
["-axo", "pid=,ppid=,time=,comm="],
|
|
146
|
+
{
|
|
147
|
+
encoding: "utf8",
|
|
148
|
+
timeout: 1e3,
|
|
149
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
150
|
+
env: { ...process.env, LC_ALL: "C" }
|
|
151
|
+
},
|
|
152
|
+
(error, stdout) => {
|
|
153
|
+
probe = void 0;
|
|
154
|
+
if (error) reject(error);
|
|
155
|
+
else resolve(stdout);
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
if (Atomics.load(samples, SAMPLE.stop)) return;
|
|
160
|
+
const rows = output.split("\n").flatMap((line) => {
|
|
161
|
+
const m = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/.exec(line);
|
|
162
|
+
return m ? [
|
|
163
|
+
{
|
|
164
|
+
pid: Number(m[1]),
|
|
165
|
+
parent: Number(m[2]),
|
|
166
|
+
cpu: parseCpuTime(m[3]),
|
|
167
|
+
command: m[4]
|
|
168
|
+
}
|
|
169
|
+
] : [];
|
|
170
|
+
});
|
|
171
|
+
const roots = rows.filter(
|
|
172
|
+
(r) => r.parent === parentPid && path2.basename(r.command) === "git"
|
|
173
|
+
).map((r) => r.pid);
|
|
174
|
+
if (roots.length > 1 || roots.length === 1 && gitPid !== void 0 && roots[0] !== gitPid)
|
|
175
|
+
ambiguous = true;
|
|
176
|
+
if (roots.length === 1) gitPid ??= roots[0];
|
|
177
|
+
if (ambiguous) {
|
|
178
|
+
Atomics.store(samples, SAMPLE.cpuMs, -1n);
|
|
179
|
+
Atomics.store(samples, SAMPLE.count, 0n);
|
|
180
|
+
Atomics.store(samples, SAMPLE.elapsedMs, 0n);
|
|
181
|
+
}
|
|
182
|
+
const pids = new Set(ambiguous ? [] : roots);
|
|
183
|
+
for (let before = -1; before !== pids.size; ) {
|
|
184
|
+
before = pids.size;
|
|
185
|
+
for (const row of rows) if (pids.has(row.parent)) pids.add(row.pid);
|
|
186
|
+
}
|
|
187
|
+
for (const row of rows) {
|
|
188
|
+
if (pids.has(row.pid) && row.cpu !== null && (cpuByPid.has(row.pid) || cpuByPid.size < 4096))
|
|
189
|
+
cpuByPid.set(row.pid, Math.max(cpuByPid.get(row.pid) ?? 0, row.cpu));
|
|
190
|
+
}
|
|
191
|
+
if (pids.size && cpuByPid.size < 4096) {
|
|
192
|
+
Atomics.store(
|
|
193
|
+
samples,
|
|
194
|
+
SAMPLE.cpuMs,
|
|
195
|
+
BigInt([...cpuByPid.values()].reduce((a, b) => a + b, 0))
|
|
196
|
+
);
|
|
197
|
+
Atomics.add(samples, SAMPLE.count, 1n);
|
|
198
|
+
Atomics.store(samples, SAMPLE.elapsedMs, BigInt(elapsed));
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!Atomics.load(samples, SAMPLE.stop)) setTimeout(sample, 1e3);
|
|
204
|
+
}
|
|
205
|
+
setTimeout(sample, Math.max(0, 1e3 - (Date.now() - started)));
|