@gravitylabsllc/porthole 0.1.0 → 0.2.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.
Files changed (55) hide show
  1. package/README.md +123 -0
  2. package/dist/adb.js +430 -21
  3. package/dist/adb.js.map +1 -1
  4. package/dist/args.js +144 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/capture.js +139 -30
  7. package/dist/capture.js.map +1 -1
  8. package/dist/cli.js +221 -62
  9. package/dist/cli.js.map +1 -1
  10. package/dist/device.js +337 -4
  11. package/dist/device.js.map +1 -1
  12. package/dist/index.js +2030 -377
  13. package/dist/index.js.map +1 -1
  14. package/dist/moment.js +240 -0
  15. package/dist/moment.js.map +1 -0
  16. package/dist/perfetto.js +826 -0
  17. package/dist/perfetto.js.map +1 -0
  18. package/dist/report.js +68 -7
  19. package/dist/report.js.map +1 -1
  20. package/dist/save.js +252 -0
  21. package/dist/save.js.map +1 -0
  22. package/dist/sessions.js +704 -0
  23. package/dist/sessions.js.map +1 -0
  24. package/dist/system.js +169 -0
  25. package/dist/system.js.map +1 -0
  26. package/dist/systrace.js +198 -0
  27. package/dist/systrace.js.map +1 -0
  28. package/dist/timeline.js +731 -29
  29. package/dist/timeline.js.map +1 -1
  30. package/dist/trace.js +317 -27
  31. package/dist/trace.js.map +1 -1
  32. package/dist/watermark.js +220 -0
  33. package/dist/watermark.js.map +1 -0
  34. package/package.json +10 -4
  35. package/src/adb.ts +583 -0
  36. package/src/args.ts +177 -0
  37. package/src/capture.ts +292 -0
  38. package/src/cli.ts +367 -0
  39. package/src/device.ts +635 -0
  40. package/src/index.ts +2545 -0
  41. package/src/moment.ts +306 -0
  42. package/src/perfetto.ts +972 -0
  43. package/src/report.ts +285 -0
  44. package/src/save.ts +322 -0
  45. package/src/sessions.ts +894 -0
  46. package/src/system.ts +221 -0
  47. package/src/systrace.ts +258 -0
  48. package/src/timeline.ts +1036 -0
  49. package/src/trace.ts +769 -0
  50. package/src/watermark.ts +337 -0
  51. package/ui/dist/assets/index-DtnyBXCM.css +1 -0
  52. package/ui/dist/assets/index-h7VNB9Fl.js +70 -0
  53. package/ui/dist/index.html +2 -2
  54. package/ui/dist/assets/index--1mlZuNZ.css +0 -1
  55. package/ui/dist/assets/index-BeVGHRFm.js +0 -68
package/dist/args.js ADDED
@@ -0,0 +1,144 @@
1
+ // Copyright 2026 Gravity Labs
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { readFile } from "node:fs/promises";
4
+ import { TRACE_VERSION } from "./trace.js";
5
+ /**
6
+ * A port a device or a browser can plausibly reach. `--port` used to be
7
+ * `Number(argv[++i])` with no check at all: a missing value is `NaN`, and a
8
+ * `NaN` port is not a refusal, it is a listener that never connects to
9
+ * anything while printing nothing to say why. Below 1024 needs privileges
10
+ * this process does not have on most platforms; above 65535 does not exist;
11
+ * a fraction is a typo, not a port.
12
+ */
13
+ export function parsePort(raw, option) {
14
+ if (raw === undefined || raw === "") {
15
+ return { message: `${option} needs a port number` };
16
+ }
17
+ // A port is a run of decimal digits, nothing else: `Number()` also accepts
18
+ // " 8677 " (trims whitespace), "1e4" (scientific notation) and "0x2000"
19
+ // (hex) as finite integers, none of which anyone typed on purpose.
20
+ if (/^-?\d+$/.test(raw)) {
21
+ const value = Number(raw);
22
+ if (value < 1024 || value > 65535) {
23
+ return { message: `${option} ${raw} is out of range (must be 1024-65535)` };
24
+ }
25
+ return value;
26
+ }
27
+ if (/^-?\d+\.\d+$/.test(raw)) {
28
+ return { message: `${option} ${raw} must be a whole number` };
29
+ }
30
+ return { message: `${option} ${JSON.stringify(raw)} is not a number` };
31
+ }
32
+ /**
33
+ * A device-uptime millisecond value — `porthole save`'s `--from`/`--to`.
34
+ * Same shape discipline as `parsePort`: a run of decimal digits only, so
35
+ * `"1e4"` or `" 100 "` (both finite per `Number()`) are refused rather than
36
+ * silently accepted. Unlike a port, 0 is a legitimate value (the very start
37
+ * of the uptime clock) and there is no upper bound — a session can run for
38
+ * days.
39
+ */
40
+ export function parseMillis(raw, option) {
41
+ if (raw === undefined || raw === "") {
42
+ return { message: `${option} needs a millisecond value` };
43
+ }
44
+ if (/^\d+$/.test(raw))
45
+ return Number(raw);
46
+ if (/^\d+\.\d+$/.test(raw)) {
47
+ return { message: `${option} ${raw} must be a whole number` };
48
+ }
49
+ return { message: `${option} ${JSON.stringify(raw)} is not a number` };
50
+ }
51
+ /**
52
+ * A relative lookback for `porthole save --since`: `10m`, `90s`, `2h`, or a
53
+ * bare millisecond count — the ticket's own examples, all a single unit, so
54
+ * a compound duration like `1h30m` is refused rather than half-parsed.
55
+ * Returns milliseconds.
56
+ */
57
+ export function parseDuration(raw, option) {
58
+ if (raw === undefined || raw === "") {
59
+ return { message: `${option} needs a duration (e.g. 10m, 90s, 2h, or a millisecond count)` };
60
+ }
61
+ const MULTIPLIER_MS = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000 };
62
+ const bare = /^\d+$/.test(raw) ? { value: Number(raw), unitMs: 1 } : null;
63
+ const suffixed = /^(\d+)(ms|s|m|h)$/.exec(raw);
64
+ const parsed = bare ?? (suffixed ? { value: Number(suffixed[1]), unitMs: MULTIPLIER_MS[suffixed[2]] } : null);
65
+ if (!parsed) {
66
+ return {
67
+ message: `${option} ${JSON.stringify(raw)} is not a duration (use 10m, 90s, 2h, or a millisecond count)`,
68
+ };
69
+ }
70
+ if (parsed.value <= 0) {
71
+ return { message: `${option} ${raw} must be positive` };
72
+ }
73
+ return parsed.value * parsed.unitMs;
74
+ }
75
+ const FAIL_ON_VALUES = ["nothing", "error", "regression"];
76
+ /**
77
+ * `--fail-on` used to be `argv[++i] as CaptureOptions["failOn"]` — a cast, not
78
+ * a check. A typo like `regresion` compiled, ran, and turned the CI gate off
79
+ * without saying a word: the worst failure mode here is a green build. This
80
+ * validates against the real union and names the accepted values so the typo
81
+ * is caught at the command line instead of at the postmortem.
82
+ */
83
+ export function parseFailOn(raw) {
84
+ if (raw !== undefined && FAIL_ON_VALUES.includes(raw)) {
85
+ return raw;
86
+ }
87
+ return {
88
+ message: `--fail-on must be one of: ${FAIL_ON_VALUES.join(", ")} (got ${JSON.stringify(raw ?? null)})`,
89
+ };
90
+ }
91
+ /**
92
+ * A required string option's value must actually be there — and must not be
93
+ * the next flag left dangling because this one's value was omitted.
94
+ * `--scenario` at the end of the command line and `--scenario --port 8677`
95
+ * were both silently accepted before: the first became `undefined` with no
96
+ * complaint, the second swallowed `--port` as the scenario name and left
97
+ * `8677` to be rejected later as a nonsense option, blaming the wrong flag.
98
+ */
99
+ export function requiredValue(raw, option) {
100
+ if (raw === undefined || raw === "--" || raw.startsWith("--")) {
101
+ return { message: `${option} needs a value` };
102
+ }
103
+ return raw;
104
+ }
105
+ /** Thrown by readTrace; the message is written straight to stderr, so it earns its keep alone. */
106
+ export class TraceReadError extends Error {
107
+ }
108
+ /**
109
+ * Reads and validates a trace file, refusing anything that is not one, rather
110
+ * than letting a missing file, truncated JSON, or a trace from a version this
111
+ * build does not understand fall through as an unhandled rejection and a raw
112
+ * stack trace — which is what a CI operator would have gotten instead of the
113
+ * one sentence they need.
114
+ */
115
+ export async function readTrace(file) {
116
+ let content;
117
+ try {
118
+ content = await readFile(file, "utf8");
119
+ }
120
+ catch (error) {
121
+ const code = error.code;
122
+ if (code === "ENOENT")
123
+ throw new TraceReadError(`no such file: ${file}`);
124
+ throw new TraceReadError(`could not read ${file}: ${error.message}`);
125
+ }
126
+ let parsed;
127
+ try {
128
+ parsed = JSON.parse(content);
129
+ }
130
+ catch {
131
+ throw new TraceReadError(`${file} is not valid JSON`);
132
+ }
133
+ if (typeof parsed !== "object" ||
134
+ parsed === null ||
135
+ typeof parsed.porthole !== "number") {
136
+ throw new TraceReadError(`${file} is not a porthole trace (missing "porthole" version field)`);
137
+ }
138
+ const version = parsed.porthole;
139
+ if (version !== TRACE_VERSION) {
140
+ throw new TraceReadError(`${file} is trace version ${version}, which this build (version ${TRACE_VERSION}) does not understand`);
141
+ }
142
+ return parsed;
143
+ }
144
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
package/dist/capture.js CHANGED
@@ -1,10 +1,11 @@
1
1
  // Copyright 2026 Gravity Labs
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { spawn } from "node:child_process";
4
- import { readFile, writeFile } from "node:fs/promises";
5
- import { DeviceClient } from "./device.js";
6
- import { renderComparison, renderReport } from "./report.js";
7
- import { buildTrace } from "./trace.js";
4
+ import { writeFile } from "node:fs/promises";
5
+ import { DeviceClient, isConnected } from "./device.js";
6
+ import { renderComparison, renderReport, shouldColor } from "./report.js";
7
+ import { buildTrace, resolveProfile } from "./trace.js";
8
+ import { parseFailOn, parsePort, readTrace, requiredValue } from "./args.js";
8
9
  export const CAPTURE_USAGE = `
9
10
  porthole capture — record a run and write a trace
10
11
 
@@ -26,14 +27,33 @@ porthole capture — record a run and write a trace
26
27
  The command runs to completion with the porthole recording. Its exit code is
27
28
  passed through unless --fail-on fires first.
28
29
  `;
29
- /** Waits for the device to answer, so a capture does not silently record nothing. */
30
+ /**
31
+ * Waits for the device to answer, so a capture does not silently record
32
+ * nothing.
33
+ *
34
+ * GRA-157 AC5: this used to resolve the instant the socket connected, before
35
+ * hello had a chance to land — DeviceClient set state = "connected" on
36
+ * socket connect and issued the hello request without awaiting it, so a
37
+ * capture that started recording right here could finish with `hello: null`
38
+ * for a run it had already reported as connected. That is now structurally
39
+ * impossible without any change to this function: DeviceClient's "state"
40
+ * event does not fire "connected" until hello has actually resolved (see
41
+ * device.ts's setState()/connect()), and this only resolves `true` on that
42
+ * exact event, so by the time it does, `device.hello` below is guaranteed
43
+ * non-null. Waiting through the new "handshaking" state in between is free —
44
+ * this function was never told which non-"connected" states exist, and does
45
+ * not need to be now either.
46
+ */
30
47
  async function awaitConnection(device, timeoutMs = 10_000) {
31
- if (device.state === "connected")
48
+ // GRA-162: routed through isConnected() rather than `=== "connected"` so
49
+ // that a fifth ConnectionState fails `tsc` here instead of this function
50
+ // just never resolving true for it.
51
+ if (isConnected(device.state))
32
52
  return true;
33
53
  return new Promise((resolve) => {
34
54
  const timer = setTimeout(() => resolve(false), timeoutMs);
35
55
  device.on("state", (state) => {
36
- if (state === "connected") {
56
+ if (isConnected(state)) {
37
57
  clearTimeout(timer);
38
58
  resolve(true);
39
59
  }
@@ -57,8 +77,18 @@ export async function capture(options) {
57
77
  const durationMs = Date.now() - startedAt;
58
78
  // The last events are still in flight when the child exits.
59
79
  await new Promise((resolve) => setTimeout(resolve, 750));
80
+ // Non-null here unless the app disconnected again during the run — a real,
81
+ // separate risk (the process under test crashed or was reinstalled mid-run)
82
+ // that this ticket does not attempt to paper over. It is no longer possible
83
+ // for this to be null merely because we asked too early: awaitConnection()
84
+ // above only returns once DeviceClient has actually set hello (GRA-157).
60
85
  const hello = device.hello;
61
86
  device.stop();
87
+ // GRA-185: `capture` has no window narrower than the whole run, so
88
+ // `windowTo: Infinity` — a profile emitted anywhere in `events` (in
89
+ // practice, `DeviceCollector`'s one startup event) counts, exactly as it
90
+ // always has here.
91
+ const profile = resolveProfile({ liveEvents: events, windowTo: Number.POSITIVE_INFINITY, sessionProfile: null, hello });
62
92
  const trace = buildTrace({
63
93
  scenario: options.scenario,
64
94
  driver: options.driver,
@@ -66,16 +96,30 @@ export async function capture(options) {
66
96
  hello,
67
97
  durationMs,
68
98
  withEvents: options.withEvents,
99
+ profile,
69
100
  });
70
101
  await writeFile(options.out, JSON.stringify(trace, null, 2));
71
- process.stderr.write(`\n${renderReport(trace)}`);
102
+ // stderr, not stdout — this is the summary `porthole capture` prints
103
+ // alongside the child command's own output, so it colours against stderr's
104
+ // own TTY-ness, which can differ from stdout's (e.g. `capture ... | tee log`).
105
+ process.stderr.write(`\n${renderReport(trace, { color: shouldColor(process.stderr) })}`);
72
106
  process.stderr.write(`\nwrote ${options.out} (${events.length} events)\n`);
73
107
  let regressed = false;
74
108
  if (options.baseline) {
75
- const before = JSON.parse(await readFile(options.baseline, "utf8"));
76
- const comparison = renderComparison(before, trace);
77
- process.stderr.write(`\n${comparison.text}`);
78
- regressed = comparison.regressed;
109
+ try {
110
+ const before = await readTrace(options.baseline);
111
+ const comparison = renderComparison(before, trace);
112
+ process.stderr.write(`\n${comparison.text}`);
113
+ regressed = comparison.regressed;
114
+ }
115
+ catch (error) {
116
+ // Same hazard as `porthole compare` reading its two files, just reached
117
+ // from a capture that asked to be checked against a baseline inline. A
118
+ // baseline we could not read is not evidence either way, so it is
119
+ // reported and the comparison is skipped rather than crashing the run
120
+ // that was otherwise recorded successfully.
121
+ process.stderr.write(`${error.message}\n`);
122
+ }
79
123
  }
80
124
  const hasError = trace.findings.some((finding) => finding.severity === "error");
81
125
  if (options.failOn === "error" && hasError)
@@ -102,13 +146,28 @@ function run(command) {
102
146
  });
103
147
  }
104
148
  export async function report(file) {
105
- const trace = JSON.parse(await readFile(file, "utf8"));
106
- process.stdout.write(renderReport(trace));
149
+ let trace;
150
+ try {
151
+ trace = await readTrace(file);
152
+ }
153
+ catch (error) {
154
+ process.stderr.write(`${error.message}\n`);
155
+ return 2;
156
+ }
157
+ process.stdout.write(renderReport(trace, { color: shouldColor(process.stdout) }));
107
158
  return 0;
108
159
  }
109
160
  export async function compare(baseline, file) {
110
- const before = JSON.parse(await readFile(baseline, "utf8"));
111
- const after = JSON.parse(await readFile(file, "utf8"));
161
+ let before;
162
+ let after;
163
+ try {
164
+ before = await readTrace(baseline);
165
+ after = await readTrace(file);
166
+ }
167
+ catch (error) {
168
+ process.stderr.write(`${error.message}\n`);
169
+ return 2;
170
+ }
112
171
  const comparison = renderComparison(before, after);
113
172
  process.stdout.write(comparison.text);
114
173
  // A refusal is not a pass. Exiting 0 would turn a gate that compared nothing
@@ -133,22 +192,72 @@ export function parseCapture(argv) {
133
192
  options.command = argv.slice(i + 1);
134
193
  break;
135
194
  }
136
- else if (arg === "--scenario")
137
- options.scenario = argv[++i];
138
- else if (arg === "--out")
139
- options.out = argv[++i];
140
- else if (arg === "--driver")
141
- options.driver = argv[++i];
142
- else if (arg === "--baseline")
143
- options.baseline = argv[++i];
195
+ else if (arg === "--scenario") {
196
+ const value = requiredValue(argv[++i], "--scenario");
197
+ if (typeof value !== "string") {
198
+ process.stderr.write(`${value.message}\n`);
199
+ process.exit(2);
200
+ }
201
+ options.scenario = value;
202
+ }
203
+ else if (arg === "--out") {
204
+ // Validated here, before `capture()` is ever called: a bad --out used
205
+ // to fail only after the recording had already happened, so the run was
206
+ // lost *and* the operator got a raw stack trace instead of a sentence.
207
+ const value = requiredValue(argv[++i], "--out");
208
+ if (typeof value !== "string") {
209
+ process.stderr.write(`${value.message}\n`);
210
+ process.exit(2);
211
+ }
212
+ options.out = value;
213
+ }
214
+ else if (arg === "--driver") {
215
+ // Previously `argv[++i]` raw: `--driver --serial abc` swallowed
216
+ // "--serial" as the driver name and left "abc" to be rejected next as
217
+ // a nonsense option — blaming the wrong token for the actual mistake.
218
+ const value = requiredValue(argv[++i], "--driver");
219
+ if (typeof value !== "string") {
220
+ process.stderr.write(`${value.message}\n`);
221
+ process.exit(2);
222
+ }
223
+ options.driver = value;
224
+ }
225
+ else if (arg === "--baseline") {
226
+ const value = requiredValue(argv[++i], "--baseline");
227
+ if (typeof value !== "string") {
228
+ process.stderr.write(`${value.message}\n`);
229
+ process.exit(2);
230
+ }
231
+ options.baseline = value;
232
+ }
144
233
  else if (arg === "--with-events")
145
234
  options.withEvents = true;
146
- else if (arg === "--fail-on")
147
- options.failOn = argv[++i];
148
- else if (arg === "--port")
149
- options.port = Number(argv[++i]);
150
- else if (arg === "--serial")
151
- options.serial = argv[++i];
235
+ else if (arg === "--fail-on") {
236
+ const value = parseFailOn(argv[++i]);
237
+ if (typeof value !== "string") {
238
+ process.stderr.write(`${value.message}\n`);
239
+ process.exit(2);
240
+ }
241
+ options.failOn = value;
242
+ }
243
+ else if (arg === "--port") {
244
+ const value = parsePort(argv[++i], "--port");
245
+ if (typeof value !== "number") {
246
+ process.stderr.write(`${value.message}\n`);
247
+ process.exit(2);
248
+ }
249
+ options.port = value;
250
+ }
251
+ else if (arg === "--serial") {
252
+ // Same hazard as --driver above: a raw argv[++i] blames the wrong
253
+ // token when the value is missing or is actually the next flag.
254
+ const value = requiredValue(argv[++i], "--serial");
255
+ if (typeof value !== "string") {
256
+ process.stderr.write(`${value.message}\n`);
257
+ process.exit(2);
258
+ }
259
+ options.serial = value;
260
+ }
152
261
  else if (arg === "--no-forward")
153
262
  options.forward = false;
154
263
  else if (arg === "--help" || arg === "-h") {
@@ -1 +1 @@
1
- {"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}