2g 0.3.1 → 0.4.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/CHANGELOG.md +12 -0
- package/dist/2g-api.js +5 -120
- package/dist/2g-api.js.map +1 -1
- package/dist/2g-api.mjs +1 -120
- package/dist/2g-api.mjs.map +1 -1
- package/dist/2g.js +42 -42
- package/dist/2g.js.map +1 -1
- package/dist/2g.mjs +115 -115
- package/dist/2g.mjs.map +1 -1
- package/dist/chunks/api-chunk.js +430 -0
- package/dist/chunks/api-chunk.js.map +1 -0
- package/dist/chunks/api-chunk.mjs +430 -0
- package/dist/chunks/api-chunk.mjs.map +1 -0
- package/dist/chunks/cli-chunk.js +160 -46
- package/dist/chunks/cli-chunk.js.map +1 -1
- package/dist/chunks/cli-chunk.mjs +214 -100
- package/dist/chunks/cli-chunk.mjs.map +1 -1
- package/dist/chunks/sessionSockets-chunk.js +4 -0
- package/dist/chunks/sessionSockets-chunk.js.map +1 -1
- package/dist/chunks/sessionSockets-chunk.mjs +9 -5
- package/dist/chunks/sessionSockets-chunk.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/tap-chunk.js +0 -287
- package/dist/chunks/tap-chunk.js.map +0 -1
- package/dist/chunks/tap-chunk.mjs +0 -287
- package/dist/chunks/tap-chunk.mjs.map +0 -1
|
@@ -2,17 +2,19 @@ import { fileURLToPath as e } from "node:url";
|
|
|
2
2
|
|
|
3
3
|
import { parseArgs as t } from "node:util";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { c as n, a as s, b as o, p as r, m as i, d as a, e as l } from "./sessionSockets-chunk.mjs";
|
|
6
6
|
|
|
7
|
-
import { r as
|
|
7
|
+
import { r as c, s as p, f as u } from "./pretty-chunk.mjs";
|
|
8
8
|
|
|
9
9
|
import d from "node:fs";
|
|
10
10
|
|
|
11
|
-
import { r as f, t as
|
|
11
|
+
import { d as m, r as f, t as h, l as g, c as v } from "./api-chunk.mjs";
|
|
12
12
|
|
|
13
|
-
import { createInterface as
|
|
13
|
+
import { createInterface as y } from "node:readline";
|
|
14
14
|
|
|
15
|
-
import
|
|
15
|
+
import w from "node:crypto";
|
|
16
|
+
|
|
17
|
+
import { spawn as b } from "node:child_process";
|
|
16
18
|
|
|
17
19
|
function runCleanCli(e = []) {
|
|
18
20
|
const o = function parseCleanOptions(e) {
|
|
@@ -38,15 +40,15 @@ function runCleanCli(e = []) {
|
|
|
38
40
|
};
|
|
39
41
|
}(e);
|
|
40
42
|
if (o.json) {
|
|
41
|
-
|
|
43
|
+
c();
|
|
42
44
|
}
|
|
43
|
-
const
|
|
45
|
+
const r = o.all ? n() : s();
|
|
44
46
|
process.stdout.write(o.json ? `${JSON.stringify({
|
|
45
|
-
removed:
|
|
46
|
-
})}\n` : `Removed ${
|
|
47
|
+
removed: r
|
|
48
|
+
})}\n` : `Removed ${r} ${o.all ? "exited" : "stale"} sessions.\n`);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
const
|
|
51
|
+
const x = {
|
|
50
52
|
since: {
|
|
51
53
|
type: "string"
|
|
52
54
|
},
|
|
@@ -65,8 +67,15 @@ const y = {
|
|
|
65
67
|
}
|
|
66
68
|
};
|
|
67
69
|
|
|
70
|
+
async function warnOnRotationLoss(e, t) {
|
|
71
|
+
if (!await m(e)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.warn(`⚠ Earlier events were rotated out of the retained window.\n For a complete trace, start \`${t}\` before the workload.`);
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
async function* readJsonlStream(e) {
|
|
69
|
-
const t =
|
|
78
|
+
const t = y({
|
|
70
79
|
input: e
|
|
71
80
|
});
|
|
72
81
|
for await (const e of t) {
|
|
@@ -82,6 +91,21 @@ async function* readJsonlStream(e) {
|
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
|
|
94
|
+
async function* filterEvents(e, t) {
|
|
95
|
+
const n = o(t.filter);
|
|
96
|
+
const s = r(t.since);
|
|
97
|
+
for await (const o of e) {
|
|
98
|
+
if (i(o, {
|
|
99
|
+
filter: t.filter,
|
|
100
|
+
since: s,
|
|
101
|
+
spans: t.spans,
|
|
102
|
+
debug: t.debug
|
|
103
|
+
}, n)) {
|
|
104
|
+
yield o;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
85
109
|
function parseSharedTapOptions(e) {
|
|
86
110
|
return {
|
|
87
111
|
since: e.since,
|
|
@@ -106,7 +130,15 @@ function parseHelp(e) {
|
|
|
106
130
|
}).values.help;
|
|
107
131
|
}
|
|
108
132
|
|
|
109
|
-
const
|
|
133
|
+
const S = [ ":started", ":done", ":failed" ];
|
|
134
|
+
|
|
135
|
+
async function convertToChromeTrace(e, t = {}) {
|
|
136
|
+
const n = new TraceConverter(t);
|
|
137
|
+
for await (const t of e) {
|
|
138
|
+
n.add(t);
|
|
139
|
+
}
|
|
140
|
+
return n.toTraceFile();
|
|
141
|
+
}
|
|
110
142
|
|
|
111
143
|
class TraceConverter {
|
|
112
144
|
#e=[];
|
|
@@ -141,7 +173,7 @@ class TraceConverter {
|
|
|
141
173
|
name: e.slice(t + 1)
|
|
142
174
|
};
|
|
143
175
|
}(e._e);
|
|
144
|
-
const n = this.#
|
|
176
|
+
const n = this.#r(t.category, "string" == typeof e._w ? e._w : void 0);
|
|
145
177
|
const s = function extractArgs(e) {
|
|
146
178
|
const t = {};
|
|
147
179
|
for (const n of Object.keys(e)) {
|
|
@@ -192,31 +224,31 @@ class TraceConverter {
|
|
|
192
224
|
const n = new Map;
|
|
193
225
|
let s = 1;
|
|
194
226
|
for (const [e, o] of this.#t) {
|
|
195
|
-
const
|
|
227
|
+
const r = [];
|
|
196
228
|
for (const t of this.#e) {
|
|
197
229
|
if ("span" === t.kind && t.track === e) {
|
|
198
|
-
|
|
230
|
+
r.push(t);
|
|
199
231
|
}
|
|
200
232
|
}
|
|
201
|
-
const
|
|
202
|
-
for (let
|
|
203
|
-
const
|
|
204
|
-
n.set(`${e}\0${
|
|
233
|
+
const i = Math.max(assignLanes(r), 1);
|
|
234
|
+
for (let r = 0; r < i; r++) {
|
|
235
|
+
const i = s++;
|
|
236
|
+
n.set(`${e}\0${r}`, i);
|
|
205
237
|
t.push({
|
|
206
238
|
ph: "M",
|
|
207
239
|
name: "thread_name",
|
|
208
240
|
pid: this.#n,
|
|
209
|
-
tid:
|
|
241
|
+
tid: i,
|
|
210
242
|
args: {
|
|
211
|
-
name: 0 ===
|
|
243
|
+
name: 0 === r ? o : `${o} #${r + 1}`
|
|
212
244
|
}
|
|
213
245
|
}, {
|
|
214
246
|
ph: "M",
|
|
215
247
|
name: "thread_sort_index",
|
|
216
248
|
pid: this.#n,
|
|
217
|
-
tid:
|
|
249
|
+
tid: i,
|
|
218
250
|
args: {
|
|
219
|
-
sort_index:
|
|
251
|
+
sort_index: i
|
|
220
252
|
}
|
|
221
253
|
});
|
|
222
254
|
}
|
|
@@ -258,7 +290,7 @@ class TraceConverter {
|
|
|
258
290
|
}
|
|
259
291
|
};
|
|
260
292
|
}
|
|
261
|
-
#
|
|
293
|
+
#r(e, t) {
|
|
262
294
|
const n = t ? `${e}:${t}` : e;
|
|
263
295
|
if (!this.#t.has(n)) {
|
|
264
296
|
this.#t.set(n, t ? `${e} ${t}` : e);
|
|
@@ -274,13 +306,13 @@ function assignLanes(e) {
|
|
|
274
306
|
const t = e.ts + e.dur;
|
|
275
307
|
let s = -1;
|
|
276
308
|
for (let o = 0; o < n.length; o++) {
|
|
277
|
-
const
|
|
278
|
-
while (
|
|
279
|
-
|
|
309
|
+
const r = n[o];
|
|
310
|
+
while (r.length && r[r.length - 1].ts + r[r.length - 1].dur <= e.ts) {
|
|
311
|
+
r.pop();
|
|
280
312
|
}
|
|
281
|
-
const
|
|
282
|
-
if (!
|
|
283
|
-
|
|
313
|
+
const i = r[r.length - 1];
|
|
314
|
+
if (!i || i.ts + i.dur >= t) {
|
|
315
|
+
r.push(e);
|
|
284
316
|
s = o;
|
|
285
317
|
break;
|
|
286
318
|
}
|
|
@@ -295,7 +327,7 @@ function assignLanes(e) {
|
|
|
295
327
|
}
|
|
296
328
|
|
|
297
329
|
function stripSuffix$1(e) {
|
|
298
|
-
for (const t of
|
|
330
|
+
for (const t of S) {
|
|
299
331
|
if (e.endsWith(t)) {
|
|
300
332
|
return e.slice(0, -t.length);
|
|
301
333
|
}
|
|
@@ -303,16 +335,24 @@ function stripSuffix$1(e) {
|
|
|
303
335
|
return e;
|
|
304
336
|
}
|
|
305
337
|
|
|
306
|
-
const
|
|
338
|
+
const T = [ ":started", ":done", ":failed" ];
|
|
339
|
+
|
|
340
|
+
async function convertToOpenTelemetry(e, t = {}) {
|
|
341
|
+
const n = new OpenTelemetryConverter(t);
|
|
342
|
+
for await (const t of e) {
|
|
343
|
+
n.add(t);
|
|
344
|
+
}
|
|
345
|
+
return n.toFile();
|
|
346
|
+
}
|
|
307
347
|
|
|
308
348
|
class OpenTelemetryConverter {
|
|
309
|
-
#
|
|
349
|
+
#i=w.randomBytes(16).toString("hex");
|
|
310
350
|
#a=createSpanId();
|
|
311
351
|
#l=[];
|
|
312
|
-
#
|
|
352
|
+
#c=[];
|
|
313
353
|
#s;
|
|
314
354
|
#o="0.1.0";
|
|
315
|
-
#
|
|
355
|
+
#p;
|
|
316
356
|
#u;
|
|
317
357
|
constructor(e) {
|
|
318
358
|
this.options = e;
|
|
@@ -320,7 +360,7 @@ class OpenTelemetryConverter {
|
|
|
320
360
|
}
|
|
321
361
|
add(e) {
|
|
322
362
|
const t = unixNano(e._t);
|
|
323
|
-
this.#
|
|
363
|
+
this.#p = null == this.#p || BigInt(t) < BigInt(this.#p) ? t : this.#p;
|
|
324
364
|
this.#u = null == this.#u || BigInt(t) > BigInt(this.#u) ? t : this.#u;
|
|
325
365
|
if ("root:init" === e._e) {
|
|
326
366
|
if ("string" == typeof e.version) {
|
|
@@ -334,11 +374,11 @@ class OpenTelemetryConverter {
|
|
|
334
374
|
if ("number" == typeof e._d) {
|
|
335
375
|
this.#l.push(this.#d(e));
|
|
336
376
|
} else {
|
|
337
|
-
this.#
|
|
377
|
+
this.#c.push(this.#m(e));
|
|
338
378
|
}
|
|
339
379
|
}
|
|
340
380
|
toFile() {
|
|
341
|
-
const e = this.#
|
|
381
|
+
const e = this.#p ?? unixNano(Date.now());
|
|
342
382
|
const t = this.#u ?? e;
|
|
343
383
|
return {
|
|
344
384
|
resourceSpans: [ {
|
|
@@ -356,7 +396,7 @@ class OpenTelemetryConverter {
|
|
|
356
396
|
name: "2g"
|
|
357
397
|
},
|
|
358
398
|
spans: [ {
|
|
359
|
-
traceId: this.#
|
|
399
|
+
traceId: this.#i,
|
|
360
400
|
spanId: this.#a,
|
|
361
401
|
name: this.#s,
|
|
362
402
|
startTimeUnixNano: e,
|
|
@@ -364,7 +404,7 @@ class OpenTelemetryConverter {
|
|
|
364
404
|
attributes: attributes({
|
|
365
405
|
"event_log.kind": "session"
|
|
366
406
|
}),
|
|
367
|
-
events: this.#
|
|
407
|
+
events: this.#c.length ? this.#c : void 0
|
|
368
408
|
}, ...this.#l ]
|
|
369
409
|
} ]
|
|
370
410
|
} ]
|
|
@@ -375,7 +415,7 @@ class OpenTelemetryConverter {
|
|
|
375
415
|
const n = unixNano(e._t - e._d);
|
|
376
416
|
const s = unixNano(e._t);
|
|
377
417
|
return {
|
|
378
|
-
traceId: this.#
|
|
418
|
+
traceId: this.#i,
|
|
379
419
|
spanId: createSpanId(),
|
|
380
420
|
parentSpanId: this.#a,
|
|
381
421
|
name: t ? stripSuffix(t.name) : e._e,
|
|
@@ -384,7 +424,7 @@ class OpenTelemetryConverter {
|
|
|
384
424
|
attributes: eventAttributes(e, t)
|
|
385
425
|
};
|
|
386
426
|
}
|
|
387
|
-
#
|
|
427
|
+
#m(e) {
|
|
388
428
|
const t = splitEventName(e._e);
|
|
389
429
|
return {
|
|
390
430
|
timeUnixNano: unixNano(e._t),
|
|
@@ -470,7 +510,7 @@ function anyValue(e) {
|
|
|
470
510
|
}
|
|
471
511
|
|
|
472
512
|
function createSpanId() {
|
|
473
|
-
return
|
|
513
|
+
return w.randomBytes(8).toString("hex");
|
|
474
514
|
}
|
|
475
515
|
|
|
476
516
|
function unixNano(e) {
|
|
@@ -489,7 +529,7 @@ function splitEventName(e) {
|
|
|
489
529
|
}
|
|
490
530
|
|
|
491
531
|
function stripSuffix(e) {
|
|
492
|
-
for (const t of
|
|
532
|
+
for (const t of T) {
|
|
493
533
|
if (e.endsWith(t)) {
|
|
494
534
|
return e.slice(0, -t.length);
|
|
495
535
|
}
|
|
@@ -500,7 +540,7 @@ function stripSuffix(e) {
|
|
|
500
540
|
async function runExportCli(e) {
|
|
501
541
|
if (parseHelp(e)) {
|
|
502
542
|
!function printExportHelp() {
|
|
503
|
-
process.stdout.write([ "Usage: 2g export [selector]", "", "Exports session events to Chrome Trace or OTLP JSON.", "", "Options:", " selector Substring of PID, CWD, or command; a running", " session wins ties", " --input <file> Read JSONL events from a file", " -o, --output <file> Write output to a file", " --format <format> chrome-trace or opentelemetry", " --json Print JSON output", " --since <time> Duration ago (5m, 90s), Unix time, or ISO date", " --filter <pattern> Event name prefix (whole segments), e.g. category,", " category:kind; * wildcards; comma-separated", " --spans Keep only span events (log.span() pairs with a _d", " duration); omit to include point events too. A", ' plain "ms" payload field is not a span', " --debug Include debug events (only recorded when the", " command ran with LOG_DEBUG set)", " --tail Follow live events after replaying history", " --timeout <duration>
|
|
543
|
+
process.stdout.write([ "Usage: 2g export [selector]", "", "Exports session events to Chrome Trace or OTLP JSON.", "(Without --tail it reads only the retained window. Rotated out logs may be lost)", "", "--tail attaches to an already-running session and follows live from that point.", " Run it after the session is up and leave it attached while the workload runs.", " It will automatically stop on 30s idle.", "To trace a full command instead, use `2g record`.", "", "Options:", " selector Substring of PID, CWD, or command; a running", " session wins ties", " --input <file> Read JSONL events from a file", " -o, --output <file> Write output to a file", " --format <format> chrome-trace or opentelemetry", " --json Print JSON output", " --since <time> Duration ago (5m, 90s), Unix time, or ISO date", " --filter <pattern> Event name prefix (whole segments), e.g. category,", " category:kind; * wildcards; comma-separated", " --spans Keep only span events (log.span() pairs with a _d", " duration); omit to include point events too. A", ' plain "ms" payload field is not a span', " --debug Include debug events (only recorded when the", " command ran with LOG_DEBUG set)", " --tail Follow live events after replaying history; stops", " automatically on 30s idle", " --timeout <duration> Also stop tailing after this absolute duration", "" ].join("\n"));
|
|
504
544
|
}();
|
|
505
545
|
return;
|
|
506
546
|
}
|
|
@@ -509,7 +549,7 @@ async function runExportCli(e) {
|
|
|
509
549
|
args: e,
|
|
510
550
|
allowPositionals: !0,
|
|
511
551
|
options: {
|
|
512
|
-
...
|
|
552
|
+
...x,
|
|
513
553
|
input: {
|
|
514
554
|
type: "string"
|
|
515
555
|
},
|
|
@@ -543,11 +583,11 @@ async function runExportCli(e) {
|
|
|
543
583
|
}
|
|
544
584
|
return t;
|
|
545
585
|
}(n.values.timeout);
|
|
546
|
-
const
|
|
547
|
-
if (!function isExportFormat(e) {
|
|
586
|
+
const r = n.values.format ?? "chrome-trace";
|
|
587
|
+
if (!function isExportFormat$1(e) {
|
|
548
588
|
return "chrome-trace" === e || "opentelemetry" === e;
|
|
549
|
-
}(
|
|
550
|
-
throw new Error(`Unsupported format: ${
|
|
589
|
+
}(r)) {
|
|
590
|
+
throw new Error(`Unsupported format: ${r}`);
|
|
551
591
|
}
|
|
552
592
|
if (n.positionals[0] && n.values.input) {
|
|
553
593
|
throw new Error("Use either a session selector or --input, not both");
|
|
@@ -559,7 +599,7 @@ async function runExportCli(e) {
|
|
|
559
599
|
selector: n.positionals[0],
|
|
560
600
|
inputFile: n.values.input,
|
|
561
601
|
output: n.values.output,
|
|
562
|
-
format:
|
|
602
|
+
format: r,
|
|
563
603
|
since: s.since,
|
|
564
604
|
filter: s.filter,
|
|
565
605
|
spans: s.spans,
|
|
@@ -571,18 +611,19 @@ async function runExportCli(e) {
|
|
|
571
611
|
};
|
|
572
612
|
}(e);
|
|
573
613
|
if (n.json) {
|
|
574
|
-
|
|
614
|
+
c();
|
|
575
615
|
}
|
|
576
616
|
let s;
|
|
577
|
-
let
|
|
578
|
-
let
|
|
617
|
+
let o;
|
|
618
|
+
let r;
|
|
579
619
|
if (n.selector) {
|
|
580
620
|
const e = await f(n.selector);
|
|
581
|
-
|
|
621
|
+
o = function formatSessionProcessName(e) {
|
|
582
622
|
return e.version ? `${e.command} (v${e.version}, PID ${e.pid})` : `${e.command} (PID ${e.pid})`;
|
|
583
623
|
}(e);
|
|
584
|
-
|
|
585
|
-
|
|
624
|
+
r = e.pid;
|
|
625
|
+
await warnOnRotationLoss(e.sessionDir, "2g export --tail");
|
|
626
|
+
s = h(e.sessionDir, {
|
|
586
627
|
follow: n.follow,
|
|
587
628
|
debug: n.debug,
|
|
588
629
|
timeout: n.timeout,
|
|
@@ -599,44 +640,19 @@ async function runExportCli(e) {
|
|
|
599
640
|
yield* readJsonlStream(process.stdin);
|
|
600
641
|
}();
|
|
601
642
|
}
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
spans: t.spans,
|
|
610
|
-
debug: t.debug
|
|
611
|
-
}, n)) {
|
|
612
|
-
yield o;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}(s, n);
|
|
616
|
-
const h = "opentelemetry" === n.format ? await async function convertToOpenTelemetry(e, t = {}) {
|
|
617
|
-
const n = new OpenTelemetryConverter(t);
|
|
618
|
-
for await (const t of e) {
|
|
619
|
-
n.add(t);
|
|
620
|
-
}
|
|
621
|
-
return n.toFile();
|
|
622
|
-
}(u, {
|
|
623
|
-
pid: c,
|
|
624
|
-
processName: l
|
|
625
|
-
}) : await async function convertToChromeTrace(e, t = {}) {
|
|
626
|
-
const n = new TraceConverter(t);
|
|
627
|
-
for await (const t of e) {
|
|
628
|
-
n.add(t);
|
|
629
|
-
}
|
|
630
|
-
return n.toTraceFile();
|
|
631
|
-
}(u, {
|
|
632
|
-
pid: c,
|
|
633
|
-
processName: l
|
|
643
|
+
const i = filterEvents(s, n);
|
|
644
|
+
const l = "opentelemetry" === n.format ? await convertToOpenTelemetry(i, {
|
|
645
|
+
pid: r,
|
|
646
|
+
processName: o
|
|
647
|
+
}) : await convertToChromeTrace(i, {
|
|
648
|
+
pid: r,
|
|
649
|
+
processName: o
|
|
634
650
|
});
|
|
635
|
-
const
|
|
651
|
+
const p = JSON.stringify(l, null, 2);
|
|
636
652
|
if (n.output) {
|
|
637
|
-
d.writeFileSync(n.output, `${
|
|
653
|
+
d.writeFileSync(n.output, `${p}\n`);
|
|
638
654
|
} else {
|
|
639
|
-
process.stdout.write(`${
|
|
655
|
+
process.stdout.write(`${p}\n`);
|
|
640
656
|
}
|
|
641
657
|
}
|
|
642
658
|
|
|
@@ -644,6 +660,99 @@ function escapeTsv(e) {
|
|
|
644
660
|
return String(e).replace(/\\/g, "\\\\").replace(/\t/g, "\\t").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
645
661
|
}
|
|
646
662
|
|
|
663
|
+
async function runRecordCli(e) {
|
|
664
|
+
if (parseHelp(e)) {
|
|
665
|
+
!function printRecordHelp() {
|
|
666
|
+
process.stdout.write([ "Usage: 2g record [options] -- <command> [args...]", "", "Runs <command>, traces the events it emits, and writes the trace on exit.", "", "This is the reliable way to capture a one-off run (a build, a script): 2g", "owns the process, so the trace is complete end-to-end with no window to", "attach within and nothing to rotate out. To trace an already-running server", "instead, use `2g export <selector> --tail`.", "", "Options:", " -o, --output <file> Write the trace to a file (default: stdout)", " --format <format> chrome-trace or opentelemetry", " --json Print JSON output", " --filter <pattern> Event name prefix (whole segments), e.g. category,", " category:kind; * wildcards; comma-separated", " --spans Keep only span events (log.span() pairs with a _d", " duration); omit to include point events too", " --debug Enable and export debug events; no LOG_DEBUG needed", "", "Example:", " 2g record -o trace.json -- expo export # run & trace a build end-to-end", "" ].join("\n"));
|
|
667
|
+
}();
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const n = e.indexOf("--");
|
|
671
|
+
const s = -1 === n ? e : e.slice(0, n);
|
|
672
|
+
const o = -1 === n ? [] : e.slice(n + 1);
|
|
673
|
+
const r = function parseRecordOptions(e) {
|
|
674
|
+
const n = t({
|
|
675
|
+
args: e,
|
|
676
|
+
allowPositionals: !0,
|
|
677
|
+
options: {
|
|
678
|
+
filter: {
|
|
679
|
+
type: "string",
|
|
680
|
+
multiple: !0
|
|
681
|
+
},
|
|
682
|
+
spans: {
|
|
683
|
+
type: "boolean"
|
|
684
|
+
},
|
|
685
|
+
debug: {
|
|
686
|
+
type: "boolean"
|
|
687
|
+
},
|
|
688
|
+
output: {
|
|
689
|
+
type: "string",
|
|
690
|
+
short: "o"
|
|
691
|
+
},
|
|
692
|
+
format: {
|
|
693
|
+
type: "string"
|
|
694
|
+
},
|
|
695
|
+
json: {
|
|
696
|
+
type: "boolean"
|
|
697
|
+
},
|
|
698
|
+
help: {
|
|
699
|
+
type: "boolean",
|
|
700
|
+
short: "h"
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
const s = n.values.format ?? "chrome-trace";
|
|
705
|
+
if (!function isExportFormat(e) {
|
|
706
|
+
return "chrome-trace" === e || "opentelemetry" === e;
|
|
707
|
+
}(s)) {
|
|
708
|
+
throw new Error(`Unsupported format: ${s}`);
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
output: n.values.output,
|
|
712
|
+
format: s,
|
|
713
|
+
filter: n.values.filter?.flatMap(e => e.split(",")).map(e => e.trim()).filter(Boolean),
|
|
714
|
+
spans: !0 === n.values.spans,
|
|
715
|
+
debug: !0 === n.values.debug,
|
|
716
|
+
json: n.values.json ?? !1
|
|
717
|
+
};
|
|
718
|
+
}(s);
|
|
719
|
+
if (!o.length) {
|
|
720
|
+
throw new Error("record needs a command to run: 2g record [options] -- <command> [args...]");
|
|
721
|
+
}
|
|
722
|
+
if (r.json) {
|
|
723
|
+
c();
|
|
724
|
+
}
|
|
725
|
+
const i = v({
|
|
726
|
+
debug: r.debug,
|
|
727
|
+
filter: r.filter
|
|
728
|
+
});
|
|
729
|
+
const a = b(o[0], o.slice(1), i.spawnOptions({
|
|
730
|
+
env: process.env,
|
|
731
|
+
stdio: [ "inherit", r.output ? "inherit" : 2, "inherit" ]
|
|
732
|
+
}));
|
|
733
|
+
i.attach(a);
|
|
734
|
+
const l = new Promise((e, t) => {
|
|
735
|
+
a.on("exit", t => e(t ?? 0));
|
|
736
|
+
a.on("error", e => t(new Error(`Failed to run ${o[0]}: ${e.message}`)));
|
|
737
|
+
});
|
|
738
|
+
const p = {
|
|
739
|
+
pid: a.pid,
|
|
740
|
+
processName: o.join(" ")
|
|
741
|
+
};
|
|
742
|
+
const u = filterEvents(i, r);
|
|
743
|
+
const m = "opentelemetry" === r.format ? await convertToOpenTelemetry(u, p) : await convertToChromeTrace(u, p);
|
|
744
|
+
const f = await l;
|
|
745
|
+
const h = JSON.stringify(m, null, 2);
|
|
746
|
+
if (r.output) {
|
|
747
|
+
d.writeFileSync(r.output, `${h}\n`);
|
|
748
|
+
} else {
|
|
749
|
+
process.stdout.write(`${h}\n`);
|
|
750
|
+
}
|
|
751
|
+
if (f) {
|
|
752
|
+
process.exitCode = f;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
647
756
|
async function runTapCli(e) {
|
|
648
757
|
if (parseHelp(e)) {
|
|
649
758
|
!function printTapHelp() {
|
|
@@ -656,7 +765,7 @@ async function runTapCli(e) {
|
|
|
656
765
|
args: e,
|
|
657
766
|
allowPositionals: !0,
|
|
658
767
|
options: {
|
|
659
|
-
...
|
|
768
|
+
...x,
|
|
660
769
|
format: {
|
|
661
770
|
type: "string"
|
|
662
771
|
},
|
|
@@ -685,11 +794,12 @@ async function runTapCli(e) {
|
|
|
685
794
|
};
|
|
686
795
|
}(e);
|
|
687
796
|
if (n.json) {
|
|
688
|
-
|
|
797
|
+
c();
|
|
689
798
|
}
|
|
690
799
|
const s = await f(n.selector);
|
|
691
|
-
|
|
692
|
-
|
|
800
|
+
await warnOnRotationLoss(s.sessionDir, "2g tap --tail");
|
|
801
|
+
const o = p(process.stdout);
|
|
802
|
+
for await (const e of h(s.sessionDir, n)) {
|
|
693
803
|
if (n.pretty) {
|
|
694
804
|
process.stdout.write(`${u(e, {
|
|
695
805
|
colorize: o,
|
|
@@ -703,9 +813,9 @@ async function runTapCli(e) {
|
|
|
703
813
|
|
|
704
814
|
async function main(e = process.argv.slice(2)) {
|
|
705
815
|
const [n, ...s] = e;
|
|
706
|
-
if (!n || "--help" === n || "-h" === n) {
|
|
816
|
+
if (!n || "--help" === n || "-h" === n || "help" === n) {
|
|
707
817
|
!function printHelp() {
|
|
708
|
-
process.stdout.write([ "Usage: 2g <command>", "", "Reads structured JSONL event logs that instrumented CLIs record during runs", "into the system temp dir, with no setup: run a command, then tap it here.", "", "Commands:", " ps [selector] List recorded sessions", " --active, -a (running only) --json", "", " tap [selector] Replay session events as JSONL, oldest first", " --since <time> --filter <pattern> --spans --debug --format pretty", " --tail (follow live events after replay; runs until killed)", "", " export [selector] Export
|
|
818
|
+
process.stdout.write([ "Usage: 2g <command>", "", "Reads structured JSONL event logs that instrumented CLIs record during runs", "into the system temp dir, with no setup: run a command, then tap it here.", "", "Commands:", " ps [selector] List recorded sessions", " --active, -a (running only) --json", "", " tap [selector] Replay session events as JSONL, oldest first", " --since <time> --filter <pattern> --spans --debug --format pretty", " --tail (follow live events after replay; runs until killed)", "", " record -- <command> Run a command, trace it end-to-end, write on exit", " -o, --output <file> --format chrome-trace|opentelemetry --json", " --filter <pattern> --spans --debug", "", " export [selector] Export a past or live (--tail) session", " --input <jsonl-file> -o, --output <file> --json", " --format chrome-trace|opentelemetry", " --since <time> --filter <pattern> --spans --debug", " --tail --timeout <duration> (absolute; tail also stops after 30s idle)", "", " typegen List every event name/payload a project can emit", " -p, --project <tsconfig> -o, --output <file> --format dts|json --json", "", " clean Remove stale sessions", " --all, -a (remove all exited sessions) --json", "", "Typical use:", " 2g ps -a # what is running now?", ' 2g tap "expo start" --tail # follow live events', ' 2g tap "expo start" --since 5m --format pretty # recent history, readable', ' 2g tap "expo start" --filter metro:bundling # narrow to one area', "", "Trace a run to a flame chart:", " 2g record -o trace.json -- expo export # run & trace a build", ' 2g export "expo start" --tail -o trace.json # attach to a running server', ' 2g export "expo start" -o trace.json # after the fact (partial)', "", "Selectors match sessions by substring of PID, CWD, or command. If several", "match, and exactly one of them is running, it is chosen; otherwise 2g errors", "and lists the candidates.", "", "--filter takes comma-separated event-name prefixes matched on whole", 'segments: "category" and "category:kind" both match "category:kind:test";', "* matches anything. Discover names by tapping unfiltered or via typegen.", "", "--since accepts a duration ago (5m, 90s, 1h30m), Unix time, or ISO date.", '--spans keeps only span events (log.span() pairs with a "_d" duration in', "ms); without it, point events are included alongside spans.", "", 'Events are single JSON lines: {"_e":"category:kind","_t":<epoch ms>,...}.', '"_d" is a span duration, "_w" a worker id; debug events need --debug.', "", "Set LOG_EVENTS=<file|fd> on an instrumented command to redirect its events.", "Debug events are only recorded when the command runs with LOG_DEBUG set.", "", "Pass LOG_DEBUG=<filter> when debugging; it also mirrors matching events to", "stderr. Reading them back needs --debug.", "", "Run 2g <command> --help for per-command help.", "" ].join("\n"));
|
|
709
819
|
}();
|
|
710
820
|
return 0;
|
|
711
821
|
}
|
|
@@ -743,9 +853,9 @@ async function main(e = process.argv.slice(2)) {
|
|
|
743
853
|
}
|
|
744
854
|
});
|
|
745
855
|
if (n.values.json) {
|
|
746
|
-
|
|
856
|
+
c();
|
|
747
857
|
}
|
|
748
|
-
const s = (await
|
|
858
|
+
const s = (await g({
|
|
749
859
|
selector: n.positionals[0]
|
|
750
860
|
})).filter(e => !n.values.active || e.alive);
|
|
751
861
|
if (n.values.json) {
|
|
@@ -773,6 +883,10 @@ async function main(e = process.argv.slice(2)) {
|
|
|
773
883
|
runCleanCli(s);
|
|
774
884
|
return 0;
|
|
775
885
|
|
|
886
|
+
case "record":
|
|
887
|
+
await runRecordCli(s);
|
|
888
|
+
return 0;
|
|
889
|
+
|
|
776
890
|
case "export":
|
|
777
891
|
await runExportCli(s);
|
|
778
892
|
return 0;
|