Skip to content

Commit 51b3472

Browse files
committed
chore: Add CLI args for config & increase e2e test timeout
1 parent e5ab23f commit 51b3472

8 files changed

Lines changed: 216 additions & 50 deletions

File tree

src/cli.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,21 @@ export const cliOptions = {
211211
hidden: true,
212212
describe: 'Set to false to opt-out of usage statistics collection.',
213213
},
214+
clearcutEndpoint: {
215+
type: 'string',
216+
hidden: true,
217+
describe: 'Endpoint for Clearcut telemetry.',
218+
},
219+
clearcutForceFlushIntervalMs: {
220+
type: 'number',
221+
hidden: true,
222+
describe: 'Force flush interval in milliseconds (for testing).',
223+
},
224+
clearcutIncludePidHeader: {
225+
type: 'boolean',
226+
hidden: true,
227+
describe: 'Include watchdog PID in Clearcut request headers (for testing).',
228+
},
214229
} satisfies Record<string, YargsOptions>;
215230

216231
export function parseArguments(version: string, argv = process.argv) {

src/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ if (args.usageStatistics) {
4141
clearcutLogger = new ClearcutLogger({
4242
logFile: args.logFile,
4343
appVersion: VERSION,
44+
clearcutEndpoint: args.clearcutEndpoint,
45+
clearcutForceFlushIntervalMs: args.clearcutForceFlushIntervalMs,
46+
clearcutIncludePidHeader: args.clearcutIncludePidHeader,
4447
});
4548
}
4649

src/telemetry/clearcut-logger.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export class ClearcutLogger {
3737
logFile?: string;
3838
persistence?: Persistence;
3939
watchdogClient?: WatchdogClient;
40+
clearcutEndpoint?: string;
41+
clearcutForceFlushIntervalMs?: number;
42+
clearcutIncludePidHeader?: boolean;
4043
}) {
4144
this.#persistence = options.persistence ?? new FilePersistence();
4245
this.#watchdog =
@@ -46,6 +49,9 @@ export class ClearcutLogger {
4649
appVersion: options.appVersion,
4750
osType: detectOsType(),
4851
logFile: options.logFile,
52+
clearcutEndpoint: options.clearcutEndpoint,
53+
clearcutForceFlushIntervalMs: options.clearcutForceFlushIntervalMs,
54+
clearcutIncludePidHeader: options.clearcutIncludePidHeader,
4955
});
5056
}
5157

src/telemetry/watchdog-client.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export class WatchdogClient {
2020
appVersion: string;
2121
osType: OsType;
2222
logFile?: string;
23+
clearcutEndpoint?: string;
24+
clearcutForceFlushIntervalMs?: number;
25+
clearcutIncludePidHeader?: boolean;
2326
},
2427
options?: {spawn?: typeof spawn},
2528
) {
@@ -37,6 +40,17 @@ export class WatchdogClient {
3740
if (config.logFile) {
3841
args.push(`--log-file=${config.logFile}`);
3942
}
43+
if (config.clearcutEndpoint) {
44+
args.push(`--clearcut-endpoint=${config.clearcutEndpoint}`);
45+
}
46+
if (config.clearcutForceFlushIntervalMs) {
47+
args.push(
48+
`--clearcut-force-flush-interval-ms=${config.clearcutForceFlushIntervalMs}`,
49+
);
50+
}
51+
if (config.clearcutIncludePidHeader) {
52+
args.push('--clearcut-include-pid-header');
53+
}
4054

4155
const spawner = options?.spawn ?? spawn;
4256
this.#childProcess = spawner(process.execPath, args, {

src/telemetry/watchdog/clearcut-sender.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ import type {
1414
OsType,
1515
} from '../types.js';
1616

17-
const INCLUDE_PID_HEADER = process.env.INCLUDE_PID_HEADER === 'true';
18-
const MAX_BUFFER_SIZE = parseInt(process.env.MAX_BUFFER_SIZE ?? '', 10) || 1000;
19-
const FLUSH_INTERVAL_MS =
20-
parseInt(process.env.FORCE_FLUSH_INTERVAL ?? '', 10) || 15 * 60 * 1000;
21-
const CLEARCUT_ENDPOINT =
22-
process.env.CLEARCUT_ENDPOINT ??
17+
export interface ClearcutSenderConfig {
18+
appVersion: string;
19+
osType: OsType;
20+
clearcutEndpoint?: string;
21+
forceFlushIntervalMs?: number;
22+
includePidHeader?: boolean;
23+
}
24+
25+
const MAX_BUFFER_SIZE = 1000;
26+
const DEFAULT_CLEARCUT_ENDPOINT =
2327
'https://play.googleapis.com/log?format=json_proto';
28+
const DEFAULT_FLUSH_INTERVAL_MS = 15 * 60 * 1000;
2429

2530
const LOG_SOURCE = 2839;
2631
const CLIENT_TYPE = 47;
@@ -37,16 +42,24 @@ interface BufferedEvent {
3742
export class ClearcutSender {
3843
#appVersion: string;
3944
#osType: OsType;
45+
#clearcutEndpoint: string;
46+
#flushIntervalMs: number;
47+
#includePidHeader: boolean;
4048
#sessionId: string;
4149
#sessionCreated: number;
4250
#buffer: BufferedEvent[] = [];
4351
#flushTimer: ReturnType<typeof setTimeout> | null = null;
4452
#isFlushing = false;
4553
#timerStarted = false;
4654

47-
constructor(appVersion: string, osType: OsType) {
48-
this.#appVersion = appVersion;
49-
this.#osType = osType;
55+
constructor(config: ClearcutSenderConfig) {
56+
this.#appVersion = config.appVersion;
57+
this.#osType = config.osType;
58+
this.#clearcutEndpoint =
59+
config.clearcutEndpoint ?? DEFAULT_CLEARCUT_ENDPOINT;
60+
this.#flushIntervalMs =
61+
config.forceFlushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
62+
this.#includePidHeader = config.includePidHeader ?? false;
5063
this.#sessionId = crypto.randomUUID();
5164
this.#sessionCreated = Date.now();
5265
}
@@ -66,7 +79,7 @@ export class ClearcutSender {
6679

6780
if (!this.#timerStarted) {
6881
this.#timerStarted = true;
69-
this.#scheduleFlush(FLUSH_INTERVAL_MS);
82+
this.#scheduleFlush(this.#flushIntervalMs);
7083
}
7184
}
7285

@@ -97,12 +110,12 @@ export class ClearcutSender {
97110
}
98111

99112
if (this.#buffer.length === 0) {
100-
this.#scheduleFlush(FLUSH_INTERVAL_MS);
113+
this.#scheduleFlush(this.#flushIntervalMs);
101114
return;
102115
}
103116

104117
this.#isFlushing = true;
105-
let nextDelayMs = FLUSH_INTERVAL_MS;
118+
let nextDelayMs = this.#flushIntervalMs;
106119

107120
// Optimistically remove events from buffer before sending.
108121
// This prevents race conditions where a simultaneous #finalFlush would include these same events.
@@ -182,12 +195,12 @@ export class ClearcutSender {
182195
const controller = new AbortController();
183196
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
184197
try {
185-
const response = await fetch(CLEARCUT_ENDPOINT, {
198+
const response = await fetch(this.#clearcutEndpoint, {
186199
method: 'POST',
187200
headers: {
188201
'Content-Type': 'application/json',
189202
// Used in E2E tests to confirm that the watchdog process is killed
190-
...(INCLUDE_PID_HEADER ? {'X-Watchdog-Pid': process.pid.toString()} : {}),
203+
...(this.#includePidHeader ? {'X-Watchdog-Pid': process.pid.toString()} : {}),
191204
},
192205
body: JSON.stringify(requestBody),
193206
signal: controller.signal,

src/telemetry/watchdog/main.ts

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,76 @@ import {WatchdogMessageType} from '../types.js';
1515

1616
import {ClearcutSender} from './clearcut-sender.js';
1717

18-
function main() {
19-
const {values} = parseArgs({
18+
interface WatchdogArgs {
19+
// Required arguments
20+
parentPid: number;
21+
appVersion: string;
22+
osType: OsType;
23+
// Optional arguments
24+
logFile?: string;
25+
clearcutEndpoint?: string;
26+
clearcutForceFlushIntervalMs?: number;
27+
clearcutIncludePidHeader?: boolean;
28+
}
29+
30+
function parseWatchdogArgs(): WatchdogArgs {
31+
const {values} = parseArgs({
2032
options: {
2133
'parent-pid': {type: 'string'},
2234
'app-version': {type: 'string'},
2335
'os-type': {type: 'string'},
2436
'log-file': {type: 'string'},
37+
'clearcut-endpoint': {type: 'string'},
38+
'clearcut-force-flush-interval-ms': {type: 'string'},
39+
'clearcut-include-pid-header': {type: 'boolean'},
2540
},
2641
strict: true,
2742
});
28-
43+
// Verify required arguments
2944
const parentPid = parseInt(values['parent-pid'] ?? '', 10);
3045
const appVersion = values['app-version'];
3146
const osType = parseInt(values['os-type'] ?? '', 10);
47+
if (isNaN(parentPid) || !appVersion || isNaN(osType)) {
48+
console.error(
49+
'Invalid arguments provided for watchdog process: ',
50+
JSON.stringify({parentPid, appVersion, osType})
51+
);
52+
process.exit(1);
53+
}
54+
55+
// Parse Optional Arguments
3256
const logFile = values['log-file'];
57+
const clearcutEndpoint = values['clearcut-endpoint'];
58+
const clearcutIncludePidHeader = values['clearcut-include-pid-header'];
59+
let clearcutForceFlushIntervalMs: number|undefined;
60+
if (values['clearcut-force-flush-interval-ms']) {
61+
const parsed = parseInt(values['clearcut-force-flush-interval-ms'], 10);
62+
if (!isNaN(parsed)) {
63+
clearcutForceFlushIntervalMs = parsed;
64+
}
65+
}
66+
67+
return {
68+
parentPid,
69+
appVersion,
70+
osType,
71+
logFile,
72+
clearcutEndpoint,
73+
clearcutForceFlushIntervalMs,
74+
clearcutIncludePidHeader,
75+
};
76+
}
77+
78+
function main() {
79+
const {
80+
parentPid,
81+
appVersion,
82+
osType,
83+
logFile,
84+
clearcutEndpoint,
85+
clearcutForceFlushIntervalMs,
86+
clearcutIncludePidHeader,
87+
} = parseWatchdogArgs();
3388
let logStream: WriteStream | undefined;
3489
if (logFile) {
3590
logStream = saveLogsToFile(logFile);
@@ -45,15 +100,6 @@ function main() {
45100
});
46101
};
47102

48-
if (isNaN(parentPid) || !appVersion || isNaN(osType)) {
49-
logger(
50-
'Invalid arguments provided for watchdog process: ',
51-
JSON.stringify({parentPid, appVersion, osType}),
52-
);
53-
exit(1);
54-
return;
55-
}
56-
57103
logger(
58104
'Watchdog started',
59105
JSON.stringify(
@@ -68,7 +114,13 @@ function main() {
68114
),
69115
);
70116

71-
const sender = new ClearcutSender(appVersion, osType as OsType);
117+
const sender = new ClearcutSender({
118+
appVersion,
119+
osType: osType,
120+
clearcutEndpoint,
121+
forceFlushIntervalMs: clearcutForceFlushIntervalMs,
122+
includePidHeader: clearcutIncludePidHeader,
123+
});
72124

73125
let isShuttingDown = false;
74126
function onParentDeath(reason: string) {

tests/e2e/telemetry.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,23 +166,22 @@ describe('Telemetry E2E', () => {
166166
SERVER_PATH,
167167
'--usage-statistics',
168168
'--headless',
169+
`--clearcutEndpoint=http://127.0.0.1:${mockContext.port}`,
170+
'--clearcutForceFlushIntervalMs=10',
171+
'--clearcutIncludePidHeader',
169172
],
170173
{
171174
stdio: ['pipe', 'pipe', 'pipe'],
172175
env: {
173176
...process.env,
174-
CLEARCUT_ENDPOINT: `http://127.0.0.1:${mockContext.port}`,
175-
FORCE_FLUSH_INTERVAL: '10',
176-
MAX_BUFFER_SIZE: '100',
177-
INCLUDE_PID_HEADER: 'true',
178177
},
179178
...spawnOptions,
180179
},
181180
);
182181

183182
const startEvent = await Promise.race([
184183
mockContext.waitForEvent(e => e.server_start !== undefined),
185-
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for server_start')), 5000))
184+
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for server_start')), 10000))
186185
]);
187186
assert.ok(startEvent, 'server_start event not received');
188187

@@ -199,7 +198,7 @@ describe('Telemetry E2E', () => {
199198
// Verify shutdown event
200199
const shutdownEvent = await Promise.race([
201200
mockContext.waitForEvent(e => e.server_shutdown !== undefined),
202-
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for server_shutdown')), 5000))
201+
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for server_shutdown')), 10000))
203202
]);
204203
assert.ok(shutdownEvent, 'server_shutdown event not received');
205204

0 commit comments

Comments
 (0)