@@ -45,21 +45,6 @@ export class WebsocketCronService {
4545 private readonly apiConfigService : ApiConfigService ,
4646 ) { }
4747
48- @Cron ( '*/1 * * * * *' )
49- handleWebsocketMetrics ( ) {
50- const connectedClients = this . server . sockets . sockets . size ?? 0 ;
51- // TODO: add more metrics in the future
52- // const subscriptions: Record<string, number> = {};
53-
54- // this.server.sockets.adapter.rooms.forEach((socketsSet, roomName) => {
55- // subscriptions[roomName] = socketsSet.size;
56- // });
57-
58- this . eventEmitter . emit ( MetricsEvents . SetWebsocketMetrics , {
59- connectedClients,
60- } ) ;
61- }
62-
6348 @Cron ( '*/6 * * * * *' )
6449 @Lock ( { name : 'Push transactions to subscribers' , verbose : true } )
6550 async handleTransactionsUpdate ( ) {
@@ -128,6 +113,59 @@ export class WebsocketCronService {
128113 ) ;
129114 }
130115
116+ @Cron ( '*/10 * * * * *' )
117+ @Lock ( { name : 'Push websocket subscriptions metrics' , verbose : true } )
118+ handleWebsocketMetrics ( ) {
119+ const connectedClients = this . server . sockets . sockets . size ?? 0 ;
120+
121+ // Efficient unique-listener counts per topic
122+ const adapter = this . server . sockets . adapter as any ;
123+ const sids : Map < string , Set < string > > = adapter ?. sids ?? new Map ( ) ;
124+
125+ const topicPrefixes : Array < { key : string ; prefix ?: string ; room ?: string } > = [
126+ { key : 'tx' , prefix : TransactionsGateway . keyPrefix } ,
127+ { key : 'customTx' , prefix : TransactionsCustomGateway . keyPrefix } ,
128+ { key : 'events' , prefix : EventsGateway . keyPrefix } ,
129+ { key : 'customEvents' , prefix : EventsCustomGateway . keyPrefix } ,
130+ { key : 'blocks' , prefix : BlocksGateway . keyPrefix } ,
131+ { key : 'pool' , prefix : PoolGateway . keyPrefix } ,
132+ { key : 'stats' , room : 'statsRoom' } ,
133+ ] ;
134+
135+ const topics : Record < string , number > = { } ;
136+ for ( const { key } of topicPrefixes ) topics [ key ] = 0 ;
137+
138+ // Count unique sockets per prefix-based topic by scanning socket -> rooms map once
139+ if ( sids && sids . size > 0 ) {
140+ for ( const [ , rooms ] of sids ) {
141+ // Track whether this socket has been counted for a given topic key
142+ const matched : Record < string , boolean > = { } ;
143+
144+ for ( const roomName of rooms ) {
145+ for ( const { key, prefix } of topicPrefixes ) {
146+ if ( ! prefix || matched [ key ] ) continue ;
147+ if ( roomName . startsWith ( prefix ) ) {
148+ topics [ key ] += 1 ;
149+ matched [ key ] = true ;
150+ }
151+ }
152+ }
153+ }
154+ }
155+
156+ // Handle exact-room topics (like statsRoom) directly from rooms map
157+ const rooms : Map < string , Set < string > > = adapter ?. rooms ?? new Map ( ) ;
158+ const statsRoomSet = rooms . get ( 'statsRoom' ) ;
159+ if ( statsRoomSet ) {
160+ topics [ 'stats' ] = statsRoomSet . size ;
161+ }
162+
163+ this . eventEmitter . emit ( MetricsEvents . SetWebsocketMetrics , {
164+ connectedClients,
165+ topics,
166+ } ) ;
167+ }
168+
131169 private async getLatestRoundOnChainData ( ) {
132170 const rounds = await this . roundService . getRounds ( new RoundFilter ( { size : 1 } ) ) ;
133171 return rounds [ 0 ] ;
0 commit comments