@@ -2,10 +2,22 @@ import type { GrouperDiagnosticsConfig } from './config';
22import type GrouperMetrics from './grouperMetrics' ;
33import type { GrouperStep } from './grouperMetrics' ;
44
5+ const NS_PER_MS = 1_000_000n ;
6+
57interface LoggerLike {
68 warn ( message : string ) : void ;
79}
810
11+ /**
12+ * Frame on the active measurement stack. childTimeNs accumulates the duration
13+ * of nested measureStep() calls so the parent step records exclusive time.
14+ */
15+ interface StepFrame {
16+ step : GrouperStep ;
17+ startedAt : bigint ;
18+ childTimeNs : bigint ;
19+ }
20+
921/**
1022 * Context describing the handled task for slow handle log line.
1123 */
@@ -22,8 +34,9 @@ export interface SlowHandleContext {
2234 * measures Grouper handle steps and emits a slow handle warning on demand.
2335 */
2436export class SlowHandleSession {
25- private readonly startedAt : number = Date . now ( ) ;
37+ private readonly startedAt : bigint = process . hrtime . bigint ( ) ;
2638 private readonly timings : Map < GrouperStep , number > = new Map ( ) ;
39+ private readonly stack : StepFrame [ ] = [ ] ;
2740 private readonly logger : LoggerLike ;
2841 private readonly metrics : GrouperMetrics ;
2942 private readonly config : GrouperDiagnosticsConfig ;
@@ -40,21 +53,38 @@ export class SlowHandleSession {
4053 }
4154
4255 /**
43- * Measure a Grouper handle step and accumulate its duration in the session.
56+ * Measure a Grouper handle step and accumulate its exclusive duration
57+ * (time not spent inside nested measureStep calls) in the session.
4458 *
4559 * @param step - step name.
4660 * @param callback - measured callback.
4761 */
4862 public async measureStep < T > ( step : GrouperStep , callback : ( ) => Promise < T > | T ) : Promise < T > {
49- const stepStartedAt = Date . now ( ) ;
63+ const frame : StepFrame = {
64+ step,
65+ startedAt : process . hrtime . bigint ( ) ,
66+ childTimeNs : 0n ,
67+ } ;
68+
69+ this . stack . push ( frame ) ;
5070
5171 try {
5272 return await this . metrics . observeStepDuration ( step , callback ) ;
5373 } finally {
54- const durationMs = Date . now ( ) - stepStartedAt ;
55- const previousDurationMs = this . timings . get ( step ) || 0 ;
74+ const elapsedNs = process . hrtime . bigint ( ) - frame . startedAt ;
5675
57- this . timings . set ( step , previousDurationMs + durationMs ) ;
76+ this . stack . pop ( ) ;
77+
78+ const parent = this . stack [ this . stack . length - 1 ] ;
79+
80+ if ( parent ) {
81+ parent . childTimeNs += elapsedNs ;
82+ }
83+
84+ const exclusiveMs = Number ( ( elapsedNs - frame . childTimeNs ) / NS_PER_MS ) ;
85+ const previousMs = this . timings . get ( step ) || 0 ;
86+
87+ this . timings . set ( step , previousMs + exclusiveMs ) ;
5888 }
5989 }
6090
@@ -64,7 +94,7 @@ export class SlowHandleSession {
6494 * @param context - handled task context.
6595 */
6696 public logIfSlow ( context : SlowHandleContext ) : void {
67- const durationMs = Date . now ( ) - this . startedAt ;
97+ const durationMs = Number ( ( process . hrtime . bigint ( ) - this . startedAt ) / NS_PER_MS ) ;
6898
6999 if ( durationMs < this . config . slowHandleWarnMs ) {
70100 return ;
@@ -75,9 +105,13 @@ export class SlowHandleSession {
75105 . map ( ( [ step , stepDurationMs ] ) => `${ step } =${ stepDurationMs } ms` )
76106 . join ( ' ' ) ;
77107
108+ const titleField = context . title !== undefined
109+ ? ` title=${ JSON . stringify ( context . title ) } `
110+ : '' ;
111+
78112 this . logger . warn (
79113 `[slowHandle] duration=${ durationMs } ms project=${ context . projectId } type=${ context . type } ` +
80- `payloadSize=${ context . payloadSize } b deltaSize=${ context . deltaSize } b title=" ${ context . title } " steps="${ steps } "`
114+ `payloadSize=${ context . payloadSize } b deltaSize=${ context . deltaSize } b${ titleField } steps="${ steps } "`
81115 ) ;
82116 }
83117}
0 commit comments