@@ -46,6 +46,22 @@ export enum Rule {
4646 ALL ,
4747}
4848
49+ type IpcMetricsSnapshot = {
50+ uptimeMs : number ;
51+ queueDepth : number ;
52+ maxQueueDepth : number ;
53+ commandsEnqueued : number ;
54+ commandsWritten : number ;
55+ stdinWriteErrors : number ;
56+ drainCount : number ;
57+ totalDrainWaitMs : number ;
58+ stdoutChunks : number ;
59+ stdoutBytes : number ;
60+ framesProcessed : number ;
61+ parseErrors : number ;
62+ deferredProcessTicks : number ;
63+ } ;
64+
4965export class DeckRules {
5066 public readonly mainMin : number ;
5167 public readonly mainMax : number ;
@@ -156,6 +172,22 @@ export class Room extends YgoRoom {
156172 private readonly notifier : RoomClientNotifier ;
157173 private readonly pendingCppMessages : string [ ] = [ ] ;
158174 private isWaitingCppDrain = false ;
175+ private ipcMetricsStartedAt = Date . now ( ) ;
176+ private ipcMetricsLogInterval : NodeJS . Timeout | null = null ;
177+ private lastDrainStartAt : bigint | null = null ;
178+ private ipcMetrics = {
179+ maxQueueDepth : 0 ,
180+ commandsEnqueued : 0 ,
181+ commandsWritten : 0 ,
182+ stdinWriteErrors : 0 ,
183+ drainCount : 0 ,
184+ totalDrainWaitNs : 0n ,
185+ stdoutChunks : 0 ,
186+ stdoutBytes : 0 ,
187+ framesProcessed : 0 ,
188+ parseErrors : 0 ,
189+ deferredProcessTicks : 0 ,
190+ } ;
159191
160192 private constructor ( attr : RoomAttr ) {
161193 super ( {
@@ -560,9 +592,12 @@ export class Room extends YgoRoom {
560592 this . isWaitingCppDrain = false ;
561593 this . pendingCppMessages . length = 0 ;
562594 this . _duel . stdin . on ( "error" , ( error ) => {
595+ this . ipcMetrics . stdinWriteErrors += 1 ;
563596 this . logger . error ( "Error writing to the child process" ) ;
564597 this . logger . error ( error ) ;
565598 } ) ;
599+
600+ this . startIpcMetricsReporting ( ) ;
566601 }
567602
568603 private get duel ( ) : ChildProcessWithoutNullStreams | null {
@@ -797,9 +832,30 @@ export class Room extends YgoRoom {
797832
798833 public sendMessageToCpp ( message : string ) : void {
799834 this . pendingCppMessages . push ( `${ message } \n` ) ;
835+ this . ipcMetrics . commandsEnqueued += 1 ;
836+ if ( this . pendingCppMessages . length > this . ipcMetrics . maxQueueDepth ) {
837+ this . ipcMetrics . maxQueueDepth = this . pendingCppMessages . length ;
838+ }
800839 this . flushCppMessageQueue ( ) ;
801840 }
802841
842+ public recordCppStdoutChunk ( byteLength : number ) : void {
843+ this . ipcMetrics . stdoutChunks += 1 ;
844+ this . ipcMetrics . stdoutBytes += byteLength ;
845+ }
846+
847+ public recordCppFrameProcessed ( ) : void {
848+ this . ipcMetrics . framesProcessed += 1 ;
849+ }
850+
851+ public recordCppParseError ( ) : void {
852+ this . ipcMetrics . parseErrors += 1 ;
853+ }
854+
855+ public recordCppDeferredProcessTick ( ) : void {
856+ this . ipcMetrics . deferredProcessTicks += 1 ;
857+ }
858+
803859 isFinished ( ) : boolean {
804860 return this . currentDuel ?. isFinished ?? false ;
805861 }
@@ -880,6 +936,7 @@ export class Room extends YgoRoom {
880936 this . _replay . destroy ( ) ;
881937 }
882938 this . roomTimer . stop ( ) ;
939+ this . stopIpcMetricsReporting ( ) ;
883940 } ) ;
884941 }
885942
@@ -983,18 +1040,69 @@ export class Room extends YgoRoom {
9831040
9841041 if ( ! writeSuccess ) {
9851042 this . isWaitingCppDrain = true ;
1043+ this . ipcMetrics . drainCount += 1 ;
1044+ this . lastDrainStartAt = process . hrtime . bigint ( ) ;
9861045 duel . stdin . once ( "drain" , ( ) => {
1046+ if ( this . lastDrainStartAt !== null ) {
1047+ this . ipcMetrics . totalDrainWaitNs += process . hrtime . bigint ( ) - this . lastDrainStartAt ;
1048+ this . lastDrainStartAt = null ;
1049+ }
9871050 this . isWaitingCppDrain = false ;
9881051 this . flushCppMessageQueue ( ) ;
9891052 } ) ;
9901053
9911054 return ;
9921055 }
9931056
1057+ this . ipcMetrics . commandsWritten += 1 ;
9941058 this . pendingCppMessages . shift ( ) ;
9951059 }
9961060 }
9971061
1062+
1063+ private startIpcMetricsReporting ( ) : void {
1064+ if ( process . env . IPC_METRICS_ENABLED !== "true" ) {
1065+ return ;
1066+ }
1067+
1068+ if ( this . ipcMetricsLogInterval ) {
1069+ return ;
1070+ }
1071+
1072+ this . ipcMetricsStartedAt = Date . now ( ) ;
1073+ this . ipcMetricsLogInterval = setInterval ( ( ) => {
1074+ const snapshot = this . getIpcMetricsSnapshot ( ) ;
1075+ this . logger . info ( "IPC_METRICS" , snapshot ) ;
1076+ } , 60_000 ) ;
1077+ }
1078+
1079+ private stopIpcMetricsReporting ( ) : void {
1080+ if ( ! this . ipcMetricsLogInterval ) {
1081+ return ;
1082+ }
1083+
1084+ clearInterval ( this . ipcMetricsLogInterval ) ;
1085+ this . ipcMetricsLogInterval = null ;
1086+ }
1087+
1088+ private getIpcMetricsSnapshot ( ) : IpcMetricsSnapshot {
1089+ return {
1090+ uptimeMs : Date . now ( ) - this . ipcMetricsStartedAt ,
1091+ queueDepth : this . pendingCppMessages . length ,
1092+ maxQueueDepth : this . ipcMetrics . maxQueueDepth ,
1093+ commandsEnqueued : this . ipcMetrics . commandsEnqueued ,
1094+ commandsWritten : this . ipcMetrics . commandsWritten ,
1095+ stdinWriteErrors : this . ipcMetrics . stdinWriteErrors ,
1096+ drainCount : this . ipcMetrics . drainCount ,
1097+ totalDrainWaitMs : Number ( this . ipcMetrics . totalDrainWaitNs ) / 1e6 ,
1098+ stdoutChunks : this . ipcMetrics . stdoutChunks ,
1099+ stdoutBytes : this . ipcMetrics . stdoutBytes ,
1100+ framesProcessed : this . ipcMetrics . framesProcessed ,
1101+ parseErrors : this . ipcMetrics . parseErrors ,
1102+ deferredProcessTicks : this . ipcMetrics . deferredProcessTicks ,
1103+ } ;
1104+ }
1105+
9981106 private extractDeckPoints ( notes ?: string ) : number | undefined {
9991107 if ( ! notes ) {
10001108 return undefined ;
0 commit comments