1
1
import _ from 'lodash' ;
2
2
import apmAgent from 'elastic-apm-node' ;
3
3
import type { Server as SocketServer } from 'socket.io' ;
4
+ import type { Knex } from 'knex' ;
4
5
5
6
import { scopedLogger } from './logger.js' ;
6
7
import { fetchProbes , getWsServer , PROBES_NAMESPACE } from './ws/server.js' ;
7
8
import { getMeasurementRedisClient , type RedisClient } from './redis/measurement-client.js' ;
9
+ import { USERS_TABLE } from './http/auth.js' ;
10
+ import { client } from './sql/client.js' ;
8
11
9
12
const logger = scopedLogger ( 'metrics' ) ;
10
13
@@ -19,6 +22,7 @@ export class MetricsAgent {
19
22
constructor (
20
23
private readonly io : SocketServer ,
21
24
private readonly redis : RedisClient ,
25
+ private readonly sql : Knex ,
22
26
) { }
23
27
24
28
run ( ) : void {
@@ -32,11 +36,11 @@ export class MetricsAgent {
32
36
// finished measurements use 2 keys
33
37
// 1 global key tracks the in-progress measurements
34
38
return Math . round ( ( dbSize - awaitingSize - 1 ) / 2 ) ;
35
- } ) ;
39
+ } , 60 * 1000 ) ;
36
40
37
41
this . registerAsyncCollector ( `gp.probe.count.local` , async ( ) => {
38
42
return this . io . of ( PROBES_NAMESPACE ) . local . fetchSockets ( ) . then ( sockets => sockets . length ) ;
39
- } ) ;
43
+ } , 10 * 1000 ) ;
40
44
41
45
this . registerAsyncGroupCollector ( 'global probe stats' , async ( ) => {
42
46
const probes = await fetchProbes ( ) ;
@@ -52,7 +56,24 @@ export class MetricsAgent {
52
56
'gp.probe.count.adopted' : probes . filter ( probe => probe . owner ) . length ,
53
57
'gp.probe.count.total' : probes . length ,
54
58
} ;
55
- } ) ;
59
+ } , 10 * 1000 ) ;
60
+
61
+ this . registerAsyncGroupCollector ( `user stats` , async ( ) => {
62
+ const result = await this . sql ( USERS_TABLE )
63
+ . count ( 'user_type as c' )
64
+ . groupBy ( 'user_type' )
65
+ . select < { user_type : string , c : number } [ ] > ( [ 'user_type' ] ) ;
66
+
67
+ const countByType = _ ( result )
68
+ . mapKeys ( record => `gp.user.count.${ record . user_type } ` )
69
+ . mapValues ( record => record . c )
70
+ . value ( ) ;
71
+
72
+ return {
73
+ ...countByType ,
74
+ 'gp.user.count.total' : _ . sum ( Object . values ( countByType ) ) ,
75
+ } ;
76
+ } , 60 * 1000 ) ;
56
77
}
57
78
58
79
recordMeasurementTime ( type : string , time : number ) : void {
@@ -138,17 +159,17 @@ export class MetricsAgent {
138
159
} ) ;
139
160
}
140
161
141
- private registerAsyncCollector ( name : string , callback : ( ) => Promise < number > ) : void {
162
+ private registerAsyncCollector ( name : string , callback : ( ) => Promise < number > , interval : number ) : void {
142
163
this . timers [ name ] = setInterval ( ( ) => {
143
164
callback ( ) . then ( ( value ) => {
144
165
this . recordAsyncDatapoint ( name , value ) ;
145
166
} ) . catch ( ( error ) => {
146
167
logger . error ( `Failed to collect an async metric "${ name } "` , error ) ;
147
168
} ) ;
148
- } , 10 * 1000 ) ;
169
+ } , interval ) ;
149
170
}
150
171
151
- private registerAsyncGroupCollector ( groupName : string , callback : ( ) => Promise < { [ k : string ] : number } > ) : void {
172
+ private registerAsyncGroupCollector ( groupName : string , callback : ( ) => Promise < { [ k : string ] : number } > , interval : number ) : void {
152
173
this . timers [ groupName ] = setInterval ( ( ) => {
153
174
callback ( ) . then ( ( group ) => {
154
175
Object . entries ( group ) . forEach ( ( [ key , value ] ) => {
@@ -157,15 +178,15 @@ export class MetricsAgent {
157
178
} ) . catch ( ( error ) => {
158
179
logger . error ( `Failed to collect an async metric group "${ groupName } "` , error ) ;
159
180
} ) ;
160
- } , 10 * 1000 ) ;
181
+ } , interval ) ;
161
182
}
162
183
}
163
184
164
185
let agent : MetricsAgent ;
165
186
166
187
export const getMetricsAgent = ( ) => {
167
188
if ( ! agent ) {
168
- agent = new MetricsAgent ( getWsServer ( ) , getMeasurementRedisClient ( ) ) ;
189
+ agent = new MetricsAgent ( getWsServer ( ) , getMeasurementRedisClient ( ) , client ) ;
169
190
}
170
191
171
192
return agent ;
0 commit comments