Skip to content

Commit 920b233

Browse files
committed
refactor: wip
1 parent 2c97026 commit 920b233

3 files changed

Lines changed: 49 additions & 79 deletions

File tree

packages/utils/src/lib/performance-observer.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import {
33
PerformanceObserver,
44
performance,
55
} from 'node:perf_hooks';
6-
import { isEnvVarEnabled } from './env.js';
7-
import { PROFILER_DEBUG_ENV_VAR } from './profiler/constants.js';
86
import type { AppendableSink } from './wal.js';
97

108
/**
@@ -122,6 +120,12 @@ export type PerformanceObserverOptions<T> = {
122120
* @default DEFAULT_MAX_QUEUE_SIZE (10000)
123121
*/
124122
maxQueueSize?: number;
123+
/**
124+
* Whether debug mode is enabled for encode failures.
125+
* When true, encode failures create performance marks for debugging.
126+
*
127+
*/
128+
debug: boolean
125129
};
126130

127131
/**
@@ -242,14 +246,15 @@ export class PerformanceObserverSink<T> {
242246
captureBufferedEntries,
243247
flushThreshold = DEFAULT_FLUSH_THRESHOLD,
244248
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
249+
debug,
245250
} = options;
246251
this.#encodePerfEntry = encodePerfEntry;
247252
this.#sink = sink;
248253
this.#buffered = captureBufferedEntries ?? true;
249254
this.#maxQueueSize = maxQueueSize;
250255
validateFlushThreshold(flushThreshold, this.#maxQueueSize);
251256
this.#flushThreshold = flushThreshold;
252-
this.#debug = isEnvVarEnabled(PROFILER_DEBUG_ENV_VAR);
257+
this.#debug = debug;
253258
}
254259

255260
/**

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

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { performance } from 'node:perf_hooks';
21
import { isEnvVarEnabled } from '../env.js';
32
import { type FatalKind, subscribeProcessExit } from '../exit-process.js';
43
import {
@@ -13,7 +12,7 @@ import type {
1312
MarkerPayload,
1413
} from '../user-timing-extensibility-api.type.js';
1514
import { ShardedWal } from '../wal-sharded.js';
16-
import { type WalFormat, WriteAheadLogFile } from '../wal.js';
15+
import { type WalFormat, type WalRecord, WriteAheadLogFile } from '../wal.js';
1716
import {
1817
PROFILER_DEBUG_MEASURE_PREFIX,
1918
PROFILER_ENABLED_ENV_VAR,
@@ -24,15 +23,15 @@ import {
2423
} from './constants.js';
2524
import { Profiler, type ProfilerOptions } from './profiler.js';
2625

27-
export type ProfilerBufferOptions<DomainEvents extends object> = Omit<
26+
export type ProfilerBufferOptions<DomainEvents extends WalRecord> = Omit<
2827
PerformanceObserverOptions<DomainEvents>,
2928
'sink' | 'encodePerfEntry'
3029
>;
31-
export type ProfilerFormat<DomainEvents extends object> = Partial<
30+
export type ProfilerFormat<DomainEvents extends WalRecord> = Partial<
3231
WalFormat<DomainEvents>
3332
> &
3433
Pick<PerformanceObserverOptions<DomainEvents>, 'encodePerfEntry'>;
35-
export type PersistOptions<DomainEvents extends object> = {
34+
export type PersistOptions<DomainEvents extends WalRecord> = {
3635
/**
3736
* Output directory for WAL shards and final files.
3837
* @default 'tmp/profiles'
@@ -58,19 +57,13 @@ export type PersistOptions<DomainEvents extends object> = {
5857
* @template Tracks - Record type defining available track names and their configurations
5958
*/
6059
export type NodejsProfilerOptions<
61-
DomainEvents extends object,
60+
DomainEvents extends WalRecord,
6261
Tracks extends Record<string, Omit<ActionTrackEntryPayload, 'dataType'>>,
6362
> = ProfilerOptions<Tracks> &
6463
ProfilerBufferOptions<DomainEvents> &
6564
PersistOptions<DomainEvents>;
6665

6766
export type NodeJsProfilerState = 'idle' | 'running' | 'closed';
68-
type NodeJsProfilerTransitionReason =
69-
| 'enable'
70-
| 'disable'
71-
| 'process-exit'
72-
| 'fatal-error'
73-
| 'api-call';
7467

7568
/**
7669
* Performance profiler with automatic process exit handling for buffered performance data.
@@ -89,7 +82,7 @@ type NodeJsProfilerTransitionReason =
8982
* @template Tracks - Record type defining available track names and their configurations
9083
*/
9184
export class NodejsProfiler<
92-
DomainEvents extends object,
85+
DomainEvents extends WalRecord,
9386
Tracks extends Record<string, ActionTrackEntryPayload> = Record<
9487
string,
9588
ActionTrackEntryPayload
@@ -145,58 +138,53 @@ export class NodejsProfiler<
145138
captureBufferedEntries,
146139
flushThreshold,
147140
maxQueueSize,
141+
debug: this.isDebugMode()
148142
});
149143

150144
this.#unsubscribeExitHandlers = subscribeProcessExit({
151-
onError: (error: unknown, kind: FatalKind) => {
152-
this.#fatalErrorMarker(error, kind);
153-
if (this.#state !== 'closed') {
154-
this.#transition('closed', 'fatal-error');
155-
}
145+
onError: (
146+
error: unknown,
147+
kind: 'uncaughtException' | 'unhandledRejection',
148+
) => {
149+
this.#handleFatalError(error, kind);
156150
},
157151
onExit: (_code: number) => {
158-
if (this.#state !== 'closed') {
159-
this.#transition('closed', 'process-exit');
160-
}
152+
this.close();
161153
},
162154
});
163155

164156
const initialEnabled =
165157
options.enabled ?? isEnvVarEnabled(PROFILER_ENABLED_ENV_VAR);
166158
if (initialEnabled) {
167-
this.#transition('running', 'enable');
159+
this.#transition('running');
168160
}
169161
}
170162

171163
/**
172164
* Creates a performance marker for a profiler state transition.
173165
*/
174-
#logTransition(
175-
from: NodeJsProfilerState,
176-
to: NodeJsProfilerState,
177-
reason: NodeJsProfilerTransitionReason,
178-
): void {
179-
if (!this.isDebugMode()) {
180-
return;
181-
}
182-
183-
performance.mark(
184-
`${PROFILER_DEBUG_MEASURE_PREFIX}:transition`,
185-
asOptions(
186-
markerPayload({
187-
color: 'warning',
188-
tooltipText: `Profiler transition: ${from} -> ${to}`,
189-
properties: [
190-
['From', from],
191-
['To', to],
192-
['Reason', reason],
193-
...objectToEntries(this.stats),
194-
],
195-
}),
196-
),
197-
);
166+
#transitionMarker(transition: string): void {
167+
const transitionMarkerPayload: MarkerPayload = {
168+
dataType: 'marker',
169+
color: 'primary',
170+
tooltipText: `Profiler state transition: ${transition}`,
171+
properties: [['Transition', transition], ...objectToEntries(this.stats)],
172+
};
173+
this.marker(transition, transitionMarkerPayload);
198174
}
199175

176+
/**
177+
* Handles fatal errors by marking them and shutting down the profiler.
178+
* @param error - The error that occurred
179+
* @param kind - The kind of fatal error (uncaughtException or unhandledRejection)
180+
*/
181+
#handleFatalError(
182+
error: unknown,
183+
kind: 'uncaughtException' | 'unhandledRejection',
184+
): void {
185+
this.#fatalErrorMarker(error, kind);
186+
this.close(); // Ensures buffers flush and sink finalizes
187+
}
200188
/**
201189
* Creates a fatal errors by marking them and shutting down the profiler.
202190
* @param error - The error that occurred
@@ -221,20 +209,17 @@ export class NodejsProfiler<
221209
* - `idle -> closed`: Closes sink if it was opened and finalizes shards (irreversible)
222210
*
223211
* @param next - The target state to transition to
224-
* @param reason - The caller intent for this transition
225212
* @throws {Error} If attempting to transition from 'closed' state or invalid transition
226213
*/
227-
#transition(next: NodeJsProfilerState, reason: NodeJsProfilerTransitionReason): void {
214+
#transition(next: NodeJsProfilerState): void {
228215
if (this.#state === next) {
229216
return;
230217
}
231218
if (this.#state === 'closed') {
232219
throw new Error('Profiler already closed');
233220
}
234221

235-
const prev = this.#state;
236-
const transition = `${prev}->${next}`;
237-
this.#logTransition(prev, next, reason);
222+
const transition = `${this.#state}->${next}`;
238223

239224
switch (transition) {
240225
case 'idle->running':
@@ -259,7 +244,7 @@ export class NodejsProfiler<
259244
break;
260245

261246
default:
262-
throw new Error(`Invalid transition: ${prev} -> ${next}`);
247+
throw new Error(`Invalid transition: ${this.#state} -> ${next}`);
263248
}
264249

265250
this.#state = next;
@@ -273,7 +258,7 @@ export class NodejsProfiler<
273258
if (this.#state === 'closed') {
274259
return;
275260
}
276-
this.#transition('closed', 'api-call');
261+
this.#transition('closed');
277262
}
278263

279264
/** @returns Whether profiler is in 'running' state */
@@ -284,9 +269,9 @@ export class NodejsProfiler<
284269
/** Enables profiling (start/stop) */
285270
override setEnabled(enabled: boolean): void {
286271
if (enabled) {
287-
this.#transition('running', 'api-call');
272+
this.#transition('running');
288273
} else {
289-
this.#transition('idle', 'api-call');
274+
this.#transition('idle');
290275
}
291276
}
292277

@@ -295,21 +280,15 @@ export class NodejsProfiler<
295280
return this.#state;
296281
}
297282

298-
/** @returns Whether debug mode is enabled */
299-
get debug(): boolean {
300-
return this.isDebugMode();
301-
}
302-
303283
/** @returns Queue statistics and profiling state for monitoring */
304-
override get stats() {
284+
get stats() {
305285
const {
306286
state: sharderState,
307287
isCoordinator,
308288
...sharderStats
309289
} = this.#sharder.getStats();
310290

311291
return {
312-
...super.stats,
313292
profilerState: this.#state,
314293
debug: this.isDebugMode(),
315294
sharderState,

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ export class Profiler<T extends ActionTrackConfigs> {
8888
*/
8989
#debug: boolean = false;
9090
#enabled: boolean = false;
91-
readonly #prefix: string = '';
9291
readonly #defaults: ActionTrackEntryPayload;
9392
readonly tracks: Record<keyof T, ActionTrackEntryPayload> | undefined;
9493
readonly #ctxOf: ReturnType<typeof measureCtx>;
@@ -119,9 +118,6 @@ export class Profiler<T extends ActionTrackConfigs> {
119118

120119
this.#enabled = enabled ?? isEnvVarEnabled(PROFILER_ENABLED_ENV_VAR);
121120
this.#debug = debug ?? isEnvVarEnabled(PROFILER_DEBUG_ENV_VAR);
122-
if(prefix) {
123-
this.#prefix = prefix;
124-
}
125121
this.#defaults = { ...defaults, dataType };
126122
this.tracks = tracks
127123
? setupTracks({ ...defaults, dataType }, tracks)
@@ -133,16 +129,6 @@ export class Profiler<T extends ActionTrackConfigs> {
133129
});
134130
}
135131

136-
get stats() {
137-
return {
138-
prefix: this.#prefix,
139-
enabled: this.#enabled,
140-
debug: this.#debug,
141-
tracks: this.tracks,
142-
trackDefaults: this.#defaults,
143-
}
144-
}
145-
146132
/**
147133
* Sets enabled state for this profiler.
148134
*

0 commit comments

Comments
 (0)