@superblocksteam/sdk 2.0.153-next.2 → 2.0.153
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/.turbo/turbo-build.log +1 -1
- package/dist/cli-replacement/dev-s3-restore.test.mjs +0 -1
- package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -1
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs +7 -59
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs.map +1 -1
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.mjs +1 -35
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/dist/cli-replacement/install-packages.npm-registry.test.mjs +0 -30
- package/dist/cli-replacement/install-packages.npm-registry.test.mjs.map +1 -1
- package/package.json +6 -6
- package/src/cli-replacement/dev-s3-restore.test.mts +0 -1
- package/src/cli-replacement/dev-startup-git-before-dbfs-order.test.mts +58 -129
- package/src/cli-replacement/dev.mts +0 -42
- package/src/cli-replacement/install-packages.npm-registry.test.mts +0 -43
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/cli-replacement/npm-install-cpu.d.mts +0 -15
- package/dist/cli-replacement/npm-install-cpu.d.mts.map +0 -1
- package/dist/cli-replacement/npm-install-cpu.mjs +0 -79
- package/dist/cli-replacement/npm-install-cpu.mjs.map +0 -1
- package/dist/cli-replacement/npm-install-cpu.test.d.mts +0 -2
- package/dist/cli-replacement/npm-install-cpu.test.d.mts.map +0 -1
- package/dist/cli-replacement/npm-install-cpu.test.mjs +0 -84
- package/dist/cli-replacement/npm-install-cpu.test.mjs.map +0 -1
- package/src/cli-replacement/npm-install-cpu.mts +0 -86
- package/src/cli-replacement/npm-install-cpu.test.mts +0 -131
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sum the cpu time of children this process has already reaped, in seconds.
|
|
3
|
-
*
|
|
4
|
-
* Returns null when the line cannot be read as procfs output, so a platform
|
|
5
|
-
* without procfs omits the measurement rather than reporting zero cpu.
|
|
6
|
-
*/
|
|
7
|
-
export declare function parseReapedChildCpuSeconds(stat: string): number | null;
|
|
8
|
-
/**
|
|
9
|
-
* Read the reaped-child cpu total for this process.
|
|
10
|
-
*
|
|
11
|
-
* Never throws: this brackets a real install, and no telemetry read may turn a
|
|
12
|
-
* completed install into a failure.
|
|
13
|
-
*/
|
|
14
|
-
export declare function readReapedChildCpuSeconds(statPath?: string): Promise<number | null>;
|
|
15
|
-
//# sourceMappingURL=npm-install-cpu.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"npm-install-cpu.d.mts","sourceRoot":"","sources":["../../src/cli-replacement/npm-install-cpu.mts"],"names":[],"mappings":"AA4CA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBtE;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,GAAE,MAAuB,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB"}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* npm's `--timing` breakdown says which phase an install spent its time in,
|
|
3
|
-
* but not whether the pod was working or waiting. Both readings are consistent
|
|
4
|
-
* with the same 19 second `reify:unpack`: npm streaming tarballs over the
|
|
5
|
-
* network, or npm burning cpu it cannot get enough of. Live edit pods request
|
|
6
|
-
* 200m of cpu and set no ceiling, so under contention they get a small
|
|
7
|
-
* proportional share of the machine, and the usual throttling metric never
|
|
8
|
-
* fires because there is no quota to throttle against.
|
|
9
|
-
*
|
|
10
|
-
* Comparing cpu time against wall time separates the two. An install that
|
|
11
|
-
* burns roughly its wall time in cpu is compute-bound and the share it gets
|
|
12
|
-
* matters; one that burns a fraction of it is waiting on the network or the
|
|
13
|
-
* disk, and cpu is a dead end.
|
|
14
|
-
*
|
|
15
|
-
* Linux tracks this per process in `/proc/self/stat`: `cutime` and `cstime`
|
|
16
|
-
* are the user and system time of children this process has already waited
|
|
17
|
-
* for. Bracketing an awaited spawn with two reads gives that spawn's cpu time
|
|
18
|
-
* without instrumenting npm itself.
|
|
19
|
-
*
|
|
20
|
-
* The figure is a ceiling rather than an exact attribution: any other child
|
|
21
|
-
* the dev-server happens to reap inside the same window lands in the same
|
|
22
|
-
* counters. Nothing else it spawns is remotely as expensive as an install, so
|
|
23
|
-
* the reading is dominated by npm, but a few hundred milliseconds of git in
|
|
24
|
-
* there is expected and does not change what the number is used for.
|
|
25
|
-
*/
|
|
26
|
-
import nodeFs from "node:fs/promises";
|
|
27
|
-
/**
|
|
28
|
-
* `cutime`/`cstime` are in clock ticks. The kernel reports procfs times in
|
|
29
|
-
* USER_HZ, which is 100 on Linux regardless of the kernel's internal tick
|
|
30
|
-
* rate, and there is no way to read `sysconf(_SC_CLK_TCK)` from Node.
|
|
31
|
-
*/
|
|
32
|
-
const USER_HZ = 100;
|
|
33
|
-
const PROC_SELF_STAT = "/proc/self/stat";
|
|
34
|
-
/**
|
|
35
|
-
* Field offsets counted from `state`, which is the first field after the
|
|
36
|
-
* executable name. `/proc/[pid]/stat` numbers `state` as field 3, so a
|
|
37
|
-
* one-indexed field N from the man page sits at N - 3 here.
|
|
38
|
-
*/
|
|
39
|
-
const CUTIME_OFFSET = 13;
|
|
40
|
-
const CSTIME_OFFSET = 14;
|
|
41
|
-
/**
|
|
42
|
-
* Sum the cpu time of children this process has already reaped, in seconds.
|
|
43
|
-
*
|
|
44
|
-
* Returns null when the line cannot be read as procfs output, so a platform
|
|
45
|
-
* without procfs omits the measurement rather than reporting zero cpu.
|
|
46
|
-
*/
|
|
47
|
-
export function parseReapedChildCpuSeconds(stat) {
|
|
48
|
-
// The executable name is unescaped and may itself contain spaces and
|
|
49
|
-
// parentheses, so the positional fields start after its LAST closing paren.
|
|
50
|
-
const commEnd = stat.lastIndexOf(")");
|
|
51
|
-
if (commEnd === -1) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const fields = stat
|
|
55
|
-
.slice(commEnd + 1)
|
|
56
|
-
.trim()
|
|
57
|
-
.split(/\s+/);
|
|
58
|
-
const cutime = Number(fields[CUTIME_OFFSET]);
|
|
59
|
-
const cstime = Number(fields[CSTIME_OFFSET]);
|
|
60
|
-
if (!Number.isFinite(cutime) || !Number.isFinite(cstime)) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
return (cutime + cstime) / USER_HZ;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Read the reaped-child cpu total for this process.
|
|
67
|
-
*
|
|
68
|
-
* Never throws: this brackets a real install, and no telemetry read may turn a
|
|
69
|
-
* completed install into a failure.
|
|
70
|
-
*/
|
|
71
|
-
export async function readReapedChildCpuSeconds(statPath = PROC_SELF_STAT) {
|
|
72
|
-
try {
|
|
73
|
-
return parseReapedChildCpuSeconds(await nodeFs.readFile(statPath, "utf8"));
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
//# sourceMappingURL=npm-install-cpu.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"npm-install-cpu.mjs","sourceRoot":"","sources":["../../src/cli-replacement/npm-install-cpu.mts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,MAAM,MAAM,kBAAkB,CAAC;AAEtC;;;;GAIG;AACH,MAAM,OAAO,GAAG,GAAG,CAAC;AAEpB,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAEzC;;;;GAIG;AACH,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,qEAAqE;IACrE,4EAA4E;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,IAAI;SAChB,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;SAClB,IAAI,EAAE;SACN,KAAK,CAAC,KAAK,CAAC,CAAC;IAChB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,WAAmB,cAAc;IAEjC,IAAI,CAAC;QACH,OAAO,0BAA0B,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"npm-install-cpu.test.d.mts","sourceRoot":"","sources":["../../src/cli-replacement/npm-install-cpu.test.mts"],"names":[],"mappings":""}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import nodeFs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { parseReapedChildCpuSeconds, readReapedChildCpuSeconds, } from "./npm-install-cpu.mjs";
|
|
6
|
-
/**
|
|
7
|
-
* Build a `/proc/self/stat` line. Real ones are a single space-separated line
|
|
8
|
-
* whose second field is the executable name in parentheses; everything after
|
|
9
|
-
* it is positional. Only utime/stime (fields 14 and 15) and cutime/cstime
|
|
10
|
-
* (fields 16 and 17) matter here, so the rest is filler.
|
|
11
|
-
*/
|
|
12
|
-
function statLine({ comm = "node", utime = 0, stime = 0, cutime = 0, cstime = 0, trailing = 20, }) {
|
|
13
|
-
const beforeTimes = [
|
|
14
|
-
"R", // 3 state
|
|
15
|
-
"1", // 4 ppid
|
|
16
|
-
"1", // 5 pgrp
|
|
17
|
-
"1", // 6 session
|
|
18
|
-
"0", // 7 tty_nr
|
|
19
|
-
"-1", // 8 tpgid
|
|
20
|
-
"4194304", // 9 flags
|
|
21
|
-
"0", // 10 minflt
|
|
22
|
-
"0", // 11 cminflt
|
|
23
|
-
"0", // 12 majflt
|
|
24
|
-
"0", // 13 cmajflt
|
|
25
|
-
];
|
|
26
|
-
const fields = [
|
|
27
|
-
...beforeTimes,
|
|
28
|
-
String(utime),
|
|
29
|
-
String(stime),
|
|
30
|
-
String(cutime),
|
|
31
|
-
String(cstime),
|
|
32
|
-
...Array.from({ length: trailing }, () => "0"),
|
|
33
|
-
];
|
|
34
|
-
return `42 (${comm}) ${fields.join(" ")}\n`;
|
|
35
|
-
}
|
|
36
|
-
let tmpDir;
|
|
37
|
-
beforeEach(async () => {
|
|
38
|
-
tmpDir = await nodeFs.mkdtemp(path.join(os.tmpdir(), "npm-install-cpu-"));
|
|
39
|
-
});
|
|
40
|
-
afterEach(async () => {
|
|
41
|
-
await nodeFs.rm(tmpDir, { force: true, recursive: true });
|
|
42
|
-
});
|
|
43
|
-
describe("parseReapedChildCpuSeconds", () => {
|
|
44
|
-
it("sums the child user and system columns and converts clock ticks to seconds", () => {
|
|
45
|
-
// 250 + 150 ticks at 100 ticks per second.
|
|
46
|
-
expect(parseReapedChildCpuSeconds(statLine({ cutime: 250, cstime: 150 }))).toBe(4);
|
|
47
|
-
});
|
|
48
|
-
it("ignores the process's own cpu columns", () => {
|
|
49
|
-
// utime/stime are this process's; only cutime/cstime cover reaped children.
|
|
50
|
-
expect(parseReapedChildCpuSeconds(statLine({ utime: 9999, stime: 9999, cutime: 100, cstime: 0 }))).toBe(1);
|
|
51
|
-
});
|
|
52
|
-
it("reads a zero as a real measurement rather than as missing", () => {
|
|
53
|
-
// An install that spawned nothing genuinely burned no child cpu, which is
|
|
54
|
-
// exactly the case worth telling apart from a slow one.
|
|
55
|
-
expect(parseReapedChildCpuSeconds(statLine({}))).toBe(0);
|
|
56
|
-
});
|
|
57
|
-
it("handles an executable name containing spaces and parentheses", () => {
|
|
58
|
-
// The comm field is not escaped, so splitting on spaces from the left
|
|
59
|
-
// shifts every positional field after it.
|
|
60
|
-
expect(parseReapedChildCpuSeconds(statLine({ comm: "my (weird) proc", cutime: 100, cstime: 100 }))).toBe(2);
|
|
61
|
-
});
|
|
62
|
-
it("returns null when the line has no closing parenthesis", () => {
|
|
63
|
-
expect(parseReapedChildCpuSeconds("42 node R 1 1 1")).toBeNull();
|
|
64
|
-
});
|
|
65
|
-
it("returns null when the line stops before the child cpu columns", () => {
|
|
66
|
-
expect(parseReapedChildCpuSeconds("42 (node) R 1 1 1")).toBeNull();
|
|
67
|
-
});
|
|
68
|
-
it("returns null when a child cpu column is not a number", () => {
|
|
69
|
-
expect(parseReapedChildCpuSeconds(statLine({ cutime: 100 }).replace(" 100 0", " x 0"))).toBeNull();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe("readReapedChildCpuSeconds", () => {
|
|
73
|
-
it("reads the measurement from the given path", async () => {
|
|
74
|
-
const file = path.join(tmpDir, "stat");
|
|
75
|
-
await nodeFs.writeFile(file, statLine({ cutime: 500, cstime: 500 }));
|
|
76
|
-
await expect(readReapedChildCpuSeconds(file)).resolves.toBe(10);
|
|
77
|
-
});
|
|
78
|
-
it("returns null when the file does not exist", async () => {
|
|
79
|
-
// macOS and any other non-procfs platform: the attribute is omitted
|
|
80
|
-
// rather than reported as zero cpu.
|
|
81
|
-
await expect(readReapedChildCpuSeconds(path.join(tmpDir, "missing"))).resolves.toBeNull();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
//# sourceMappingURL=npm-install-cpu.test.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"npm-install-cpu.test.mjs","sourceRoot":"","sources":["../../src/cli-replacement/npm-install-cpu.test.mts"],"names":[],"mappings":"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"}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* npm's `--timing` breakdown says which phase an install spent its time in,
|
|
3
|
-
* but not whether the pod was working or waiting. Both readings are consistent
|
|
4
|
-
* with the same 19 second `reify:unpack`: npm streaming tarballs over the
|
|
5
|
-
* network, or npm burning cpu it cannot get enough of. Live edit pods request
|
|
6
|
-
* 200m of cpu and set no ceiling, so under contention they get a small
|
|
7
|
-
* proportional share of the machine, and the usual throttling metric never
|
|
8
|
-
* fires because there is no quota to throttle against.
|
|
9
|
-
*
|
|
10
|
-
* Comparing cpu time against wall time separates the two. An install that
|
|
11
|
-
* burns roughly its wall time in cpu is compute-bound and the share it gets
|
|
12
|
-
* matters; one that burns a fraction of it is waiting on the network or the
|
|
13
|
-
* disk, and cpu is a dead end.
|
|
14
|
-
*
|
|
15
|
-
* Linux tracks this per process in `/proc/self/stat`: `cutime` and `cstime`
|
|
16
|
-
* are the user and system time of children this process has already waited
|
|
17
|
-
* for. Bracketing an awaited spawn with two reads gives that spawn's cpu time
|
|
18
|
-
* without instrumenting npm itself.
|
|
19
|
-
*
|
|
20
|
-
* The figure is a ceiling rather than an exact attribution: any other child
|
|
21
|
-
* the dev-server happens to reap inside the same window lands in the same
|
|
22
|
-
* counters. Nothing else it spawns is remotely as expensive as an install, so
|
|
23
|
-
* the reading is dominated by npm, but a few hundred milliseconds of git in
|
|
24
|
-
* there is expected and does not change what the number is used for.
|
|
25
|
-
*/
|
|
26
|
-
import nodeFs from "node:fs/promises";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* `cutime`/`cstime` are in clock ticks. The kernel reports procfs times in
|
|
30
|
-
* USER_HZ, which is 100 on Linux regardless of the kernel's internal tick
|
|
31
|
-
* rate, and there is no way to read `sysconf(_SC_CLK_TCK)` from Node.
|
|
32
|
-
*/
|
|
33
|
-
const USER_HZ = 100;
|
|
34
|
-
|
|
35
|
-
const PROC_SELF_STAT = "/proc/self/stat";
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Field offsets counted from `state`, which is the first field after the
|
|
39
|
-
* executable name. `/proc/[pid]/stat` numbers `state` as field 3, so a
|
|
40
|
-
* one-indexed field N from the man page sits at N - 3 here.
|
|
41
|
-
*/
|
|
42
|
-
const CUTIME_OFFSET = 13;
|
|
43
|
-
const CSTIME_OFFSET = 14;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Sum the cpu time of children this process has already reaped, in seconds.
|
|
47
|
-
*
|
|
48
|
-
* Returns null when the line cannot be read as procfs output, so a platform
|
|
49
|
-
* without procfs omits the measurement rather than reporting zero cpu.
|
|
50
|
-
*/
|
|
51
|
-
export function parseReapedChildCpuSeconds(stat: string): number | null {
|
|
52
|
-
// The executable name is unescaped and may itself contain spaces and
|
|
53
|
-
// parentheses, so the positional fields start after its LAST closing paren.
|
|
54
|
-
const commEnd = stat.lastIndexOf(")");
|
|
55
|
-
if (commEnd === -1) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const fields = stat
|
|
60
|
-
.slice(commEnd + 1)
|
|
61
|
-
.trim()
|
|
62
|
-
.split(/\s+/);
|
|
63
|
-
const cutime = Number(fields[CUTIME_OFFSET]);
|
|
64
|
-
const cstime = Number(fields[CSTIME_OFFSET]);
|
|
65
|
-
if (!Number.isFinite(cutime) || !Number.isFinite(cstime)) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return (cutime + cstime) / USER_HZ;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Read the reaped-child cpu total for this process.
|
|
74
|
-
*
|
|
75
|
-
* Never throws: this brackets a real install, and no telemetry read may turn a
|
|
76
|
-
* completed install into a failure.
|
|
77
|
-
*/
|
|
78
|
-
export async function readReapedChildCpuSeconds(
|
|
79
|
-
statPath: string = PROC_SELF_STAT,
|
|
80
|
-
): Promise<number | null> {
|
|
81
|
-
try {
|
|
82
|
-
return parseReapedChildCpuSeconds(await nodeFs.readFile(statPath, "utf8"));
|
|
83
|
-
} catch {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import nodeFs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
parseReapedChildCpuSeconds,
|
|
9
|
-
readReapedChildCpuSeconds,
|
|
10
|
-
} from "./npm-install-cpu.mjs";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Build a `/proc/self/stat` line. Real ones are a single space-separated line
|
|
14
|
-
* whose second field is the executable name in parentheses; everything after
|
|
15
|
-
* it is positional. Only utime/stime (fields 14 and 15) and cutime/cstime
|
|
16
|
-
* (fields 16 and 17) matter here, so the rest is filler.
|
|
17
|
-
*/
|
|
18
|
-
function statLine({
|
|
19
|
-
comm = "node",
|
|
20
|
-
utime = 0,
|
|
21
|
-
stime = 0,
|
|
22
|
-
cutime = 0,
|
|
23
|
-
cstime = 0,
|
|
24
|
-
trailing = 20,
|
|
25
|
-
}: {
|
|
26
|
-
comm?: string;
|
|
27
|
-
utime?: number;
|
|
28
|
-
stime?: number;
|
|
29
|
-
cutime?: number;
|
|
30
|
-
cstime?: number;
|
|
31
|
-
trailing?: number;
|
|
32
|
-
}): string {
|
|
33
|
-
const beforeTimes = [
|
|
34
|
-
"R", // 3 state
|
|
35
|
-
"1", // 4 ppid
|
|
36
|
-
"1", // 5 pgrp
|
|
37
|
-
"1", // 6 session
|
|
38
|
-
"0", // 7 tty_nr
|
|
39
|
-
"-1", // 8 tpgid
|
|
40
|
-
"4194304", // 9 flags
|
|
41
|
-
"0", // 10 minflt
|
|
42
|
-
"0", // 11 cminflt
|
|
43
|
-
"0", // 12 majflt
|
|
44
|
-
"0", // 13 cmajflt
|
|
45
|
-
];
|
|
46
|
-
const fields = [
|
|
47
|
-
...beforeTimes,
|
|
48
|
-
String(utime),
|
|
49
|
-
String(stime),
|
|
50
|
-
String(cutime),
|
|
51
|
-
String(cstime),
|
|
52
|
-
...Array.from({ length: trailing }, () => "0"),
|
|
53
|
-
];
|
|
54
|
-
return `42 (${comm}) ${fields.join(" ")}\n`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let tmpDir: string;
|
|
58
|
-
|
|
59
|
-
beforeEach(async () => {
|
|
60
|
-
tmpDir = await nodeFs.mkdtemp(path.join(os.tmpdir(), "npm-install-cpu-"));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
afterEach(async () => {
|
|
64
|
-
await nodeFs.rm(tmpDir, { force: true, recursive: true });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe("parseReapedChildCpuSeconds", () => {
|
|
68
|
-
it("sums the child user and system columns and converts clock ticks to seconds", () => {
|
|
69
|
-
// 250 + 150 ticks at 100 ticks per second.
|
|
70
|
-
expect(
|
|
71
|
-
parseReapedChildCpuSeconds(statLine({ cutime: 250, cstime: 150 })),
|
|
72
|
-
).toBe(4);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("ignores the process's own cpu columns", () => {
|
|
76
|
-
// utime/stime are this process's; only cutime/cstime cover reaped children.
|
|
77
|
-
expect(
|
|
78
|
-
parseReapedChildCpuSeconds(
|
|
79
|
-
statLine({ utime: 9999, stime: 9999, cutime: 100, cstime: 0 }),
|
|
80
|
-
),
|
|
81
|
-
).toBe(1);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("reads a zero as a real measurement rather than as missing", () => {
|
|
85
|
-
// An install that spawned nothing genuinely burned no child cpu, which is
|
|
86
|
-
// exactly the case worth telling apart from a slow one.
|
|
87
|
-
expect(parseReapedChildCpuSeconds(statLine({}))).toBe(0);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("handles an executable name containing spaces and parentheses", () => {
|
|
91
|
-
// The comm field is not escaped, so splitting on spaces from the left
|
|
92
|
-
// shifts every positional field after it.
|
|
93
|
-
expect(
|
|
94
|
-
parseReapedChildCpuSeconds(
|
|
95
|
-
statLine({ comm: "my (weird) proc", cutime: 100, cstime: 100 }),
|
|
96
|
-
),
|
|
97
|
-
).toBe(2);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("returns null when the line has no closing parenthesis", () => {
|
|
101
|
-
expect(parseReapedChildCpuSeconds("42 node R 1 1 1")).toBeNull();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("returns null when the line stops before the child cpu columns", () => {
|
|
105
|
-
expect(parseReapedChildCpuSeconds("42 (node) R 1 1 1")).toBeNull();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("returns null when a child cpu column is not a number", () => {
|
|
109
|
-
expect(
|
|
110
|
-
parseReapedChildCpuSeconds(
|
|
111
|
-
statLine({ cutime: 100 }).replace(" 100 0", " x 0"),
|
|
112
|
-
),
|
|
113
|
-
).toBeNull();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe("readReapedChildCpuSeconds", () => {
|
|
118
|
-
it("reads the measurement from the given path", async () => {
|
|
119
|
-
const file = path.join(tmpDir, "stat");
|
|
120
|
-
await nodeFs.writeFile(file, statLine({ cutime: 500, cstime: 500 }));
|
|
121
|
-
await expect(readReapedChildCpuSeconds(file)).resolves.toBe(10);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("returns null when the file does not exist", async () => {
|
|
125
|
-
// macOS and any other non-procfs platform: the attribute is omitted
|
|
126
|
-
// rather than reported as zero cpu.
|
|
127
|
-
await expect(
|
|
128
|
-
readReapedChildCpuSeconds(path.join(tmpDir, "missing")),
|
|
129
|
-
).resolves.toBeNull();
|
|
130
|
-
});
|
|
131
|
-
});
|