Skip to content

Commit bf0ecb9

Browse files
committed
refactor: wip
1 parent 0b1fd3a commit bf0ecb9

File tree

6 files changed

+28
-138
lines changed

6 files changed

+28
-138
lines changed

packages/utils/eslint.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ export default tseslint.config(
1212
},
1313
},
1414
},
15+
{
16+
files: ['packages/utils/src/lib/**/file-sink*.ts'],
17+
rules: {
18+
'n/no-sync': 'off',
19+
eqeqeq: 'off',
20+
},
21+
},
1522
{
1623
files: ['**/*.json'],
1724
rules: {

packages/utils/src/lib/file-sink.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as fs from 'node:fs';
1+
import fs from 'node:fs';
22
import path from 'node:path';
33
import type { Decoder, Encoder } from './sink-source.type';
44

@@ -67,7 +67,11 @@ export class AppendFileSink {
6767
.map(line => (line.endsWith('\r') ? line.slice(0, -1) : line));
6868
}
6969

70-
recover() {
70+
recover(): {
71+
records: string[];
72+
errors: { lineNo: number; line: string; error: Error }[];
73+
partialTail: string | null;
74+
} {
7175
if (!fs.existsSync(this.filePath)) {
7276
return { records: [], errors: [], partialTail: null };
7377
}
@@ -131,7 +135,11 @@ export class JsonlFile<T> {
131135
yield* [...this.sink.readAll()].map(line => this.decode(line));
132136
}
133137

134-
recover() {
138+
recover(): {
139+
records: T[];
140+
errors: { lineNo: number; line: string; error: Error }[];
141+
partialTail: string | null;
142+
} {
135143
const r = this.sink.recover();
136144
return {
137145
records: r.records.map(l => this.decode(l)),

packages/utils/src/lib/profiler/file-sink-json-trace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export type FinalizeTraceFileOptions = {
2828
marginDurMs?: number;
2929
};
3030

31-
// eslint-disable-next-line max-lines-per-function
31+
// eslint-disable-next-line max-lines-per-function, complexity
3232
export function finalizeTraceFile(
3333
events: (SpanEvent | InstantEvent)[],
3434
outputPath: string,

packages/utils/src/lib/profiler/profiler.ts

Lines changed: 5 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1+
import process from 'node:process';
12
import { isEnvVarEnabled } from '../env.js';
2-
import { PerformanceObserverSink } from '../performance-observer.js';
3-
import type { Recoverable, Sink } from '../sink-source.type.js';
43
import {
54
type ActionTrackConfigs,
65
type MeasureCtxOptions,
@@ -96,22 +95,20 @@ export class Profiler<T extends ActionTrackConfigs> {
9695
/**
9796
* Sets enabled state for this profiler.
9897
*
99-
* Note: This only affects the current profiler instance and does not modify environment variables.
100-
* Environment variables are read-only configuration that should be set before application startup.
98+
* Also sets the `CP_PROFILING` environment variable.
99+
* This means any future {@link Profiler} instantiations (including child processes) will use the same enabled state.
101100
*
102101
* @param enabled - Whether profiling should be enabled
103102
*/
104103
setEnabled(enabled: boolean): void {
105-
if (this.#enabled === enabled) {
106-
return;
107-
}
104+
process.env[PROFILER_ENABLED_ENV_VAR] = `${enabled}`;
108105
this.#enabled = enabled;
109106
}
110107

111108
/**
112109
* Is profiling enabled?
113110
*
114-
* Profiling is enabled by {@link setEnabled} call or by the `CP_PROFILING` environment variable at instantiation.
111+
* Profiling is enabled by {@link setEnabled} call or `CP_PROFILING` environment variable.
115112
*
116113
* @returns Whether profiling is currently enabled
117114
*/
@@ -229,108 +226,3 @@ export class Profiler<T extends ActionTrackConfigs> {
229226
}
230227
}
231228
}
232-
233-
/**
234-
* Options for configuring a NodejsProfiler instance.
235-
*
236-
* Extends ProfilerOptions with a required sink parameter.
237-
*
238-
* @template Tracks - Record type defining available track names and their configurations
239-
*/
240-
export type NodejsProfilerOptions<
241-
DomainEvents,
242-
Tracks extends Record<string, ActionTrackEntryPayload>,
243-
> = ProfilerOptions<Tracks> & {
244-
/** Sink for buffering and flushing performance data */
245-
sink: Sink<DomainEvents, unknown> & Recoverable<unknown>;
246-
/** Encoder that converts PerformanceEntry to domain events */
247-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
248-
encode: (entry: PerformanceEntry) => DomainEvents[];
249-
};
250-
251-
/**
252-
* Performance profiler with automatic process exit handling for buffered performance data.
253-
*
254-
* This class extends the base {@link Profiler} with automatic flushing of performance data
255-
* when the process exits. It accepts a {@link PerformanceObserverSink} that buffers performance
256-
* entries and ensures they are written out during process termination, even for unexpected exits.
257-
*
258-
* The sink defines the output format for performance data, enabling flexible serialization
259-
* to various formats such as DevTools TraceEvent JSON, OpenTelemetry protocol buffers,
260-
* or custom domain-specific formats.
261-
*
262-
* The profiler automatically subscribes to the performance observer when enabled and installs
263-
* exit handlers that flush buffered data on process termination (signals, fatal errors, or normal exit).
264-
*
265-
*/
266-
export class NodejsProfiler<
267-
DomainEvents,
268-
Tracks extends Record<string, ActionTrackEntryPayload> = Record<
269-
string,
270-
ActionTrackEntryPayload
271-
>,
272-
> extends Profiler<Tracks> {
273-
#sink: Sink<DomainEvents, unknown> & Recoverable<unknown>;
274-
#performanceObserverSink: PerformanceObserverSink<DomainEvents>;
275-
276-
/**
277-
* Creates a new NodejsProfiler instance with automatic exit handling.
278-
*
279-
* @param options - Configuration options including the sink
280-
* @param options.sink - Sink for buffering and flushing performance data
281-
* @param options.tracks - Custom track configurations merged with defaults
282-
* @param options.prefix - Prefix for all measurement names
283-
* @param options.track - Default track name for measurements
284-
* @param options.trackGroup - Default track group for organization
285-
* @param options.color - Default color for track entries
286-
* @param options.enabled - Whether profiling is enabled (defaults to CP_PROFILING env var)
287-
*
288-
*/
289-
constructor(options: NodejsProfilerOptions<DomainEvents, Tracks>) {
290-
const { sink, encode, ...profilerOptions } = options;
291-
292-
super(profilerOptions);
293-
294-
this.#sink = sink;
295-
296-
this.#performanceObserverSink = new PerformanceObserverSink<DomainEvents>({
297-
sink,
298-
encode,
299-
});
300-
301-
this.#setObserving(this.isEnabled());
302-
}
303-
304-
#setObserving(observing: boolean): void {
305-
if (observing) {
306-
this.#sink.open();
307-
this.#performanceObserverSink.subscribe();
308-
} else {
309-
this.#performanceObserverSink.unsubscribe();
310-
this.#performanceObserverSink.flush();
311-
this.#sink.close();
312-
}
313-
}
314-
315-
/**
316-
* Sets enabled state for this profiler and manages sink/observer lifecycle.
317-
*
318-
* Design: Environment = default, Runtime = override
319-
* - Environment variables define defaults (read once at construction)
320-
* - This method provides runtime control without mutating globals
321-
* - Child processes are unaffected by runtime enablement changes
322-
*
323-
* Invariant: enabled ↔ sink + observer state
324-
* - enabled === true → sink open + observer subscribed
325-
* - enabled === false → sink closed + observer unsubscribed
326-
*
327-
* @param enabled - Whether profiling should be enabled
328-
*/
329-
setEnabled(enabled: boolean): void {
330-
if (this.isEnabled() === enabled) {
331-
return;
332-
}
333-
super.setEnabled(enabled);
334-
this.#setObserving(enabled);
335-
}
336-
}

packages/utils/src/lib/profiler/profiler.unit.test.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,12 @@ describe('Profiler', () => {
128128
expect(profiler.isEnabled()).toBe(false);
129129
});
130130

131-
it('setEnabled should update internal state without affecting env vars', () => {
131+
it('isEnabled should update environment variable', () => {
132132
profiler.setEnabled(true);
133-
expect(profiler.isEnabled()).toBe(true);
133+
expect(process.env.CP_PROFILING).toBe('true');
134134

135135
profiler.setEnabled(false);
136-
expect(profiler.isEnabled()).toBe(false);
136+
expect(process.env.CP_PROFILING).toBe('false');
137137
});
138138

139139
it('marker should execute without error when enabled', () => {
@@ -423,22 +423,4 @@ describe('Profiler', () => {
423423
).rejects.toThrow(error);
424424
expect(workFn).toHaveBeenCalled();
425425
});
426-
427-
it('setEnabled should be idempotent', () => {
428-
// Test enabling
429-
profiler.setEnabled(true);
430-
expect(profiler.isEnabled()).toBe(true);
431-
432-
// Setting to true again should not change anything
433-
profiler.setEnabled(true);
434-
expect(profiler.isEnabled()).toBe(true);
435-
436-
// Test disabling
437-
profiler.setEnabled(false);
438-
expect(profiler.isEnabled()).toBe(false);
439-
440-
// Setting to false again should not change anything
441-
profiler.setEnabled(false);
442-
expect(profiler.isEnabled()).toBe(false);
443-
});
444426
});

packages/utils/src/lib/sink-source.type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type EncoderInterface<I, O> = {
77
encode: (input: I) => O;
88
};
99

10+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1011
export type Sink<I = string | Buffer, O = unknown> = {
1112
setPath: (filePath: string) => void;
1213
getPath: () => string;

0 commit comments

Comments
 (0)