1
1
import * as fs from 'fs' ;
2
+ import { mkdir } from 'fs/promises' ;
2
3
import { jsonSizeOf } from 'json-sizeof' ;
3
4
import * as path from 'path' ;
4
5
import ShareDB , { Agent , PubSub } from 'sharedb' ;
@@ -126,25 +127,32 @@ export class ResourceMonitor {
126
127
private readonly connectionInfoPath : string ;
127
128
private readonly agentInfoPath : string ;
128
129
private readonly pubSubInfoPath : string ;
130
+ private readonly baseOutputPath : string ;
129
131
130
132
/** Singleton. */
131
133
public static get instance ( ) : ResourceMonitor {
132
134
return ( ResourceMonitor . _instance ??= new ResourceMonitor ( ) ) ;
133
135
}
134
136
135
137
private constructor ( ) {
136
- this . heapInfoPath = path . join ( process . cwd ( ) , 'heap-info.csv' ) ;
137
- this . connectionInfoPath = path . join ( process . cwd ( ) , 'connection-info.csv' ) ;
138
- this . agentInfoPath = path . join ( process . cwd ( ) , 'agent-info.csv' ) ;
139
- this . pubSubInfoPath = path . join ( process . cwd ( ) , 'pubsub-info.csv' ) ;
138
+ const homePath : string = process . env [ 'HOME' ] ?? process . cwd ( ) ;
139
+ const xdgDataHomeEnv : string | undefined = process . env [ 'XDG_DATA_HOME' ] ;
140
+ const xdgDataHomePath : string =
141
+ xdgDataHomeEnv != null && xdgDataHomeEnv !== '' ? xdgDataHomeEnv : path . join ( homePath , '.local' , 'share' ) ;
142
+ this . baseOutputPath = process . env [ 'SF_RESOURCE_REPORTS_PATH' ] ?? path . join ( xdgDataHomePath , 'sf-resource-reports' ) ;
143
+
144
+ this . heapInfoPath = path . join ( this . baseOutputPath , 'heap-info.csv' ) ;
145
+ this . connectionInfoPath = path . join ( this . baseOutputPath , 'connection-info.csv' ) ;
146
+ this . agentInfoPath = path . join ( this . baseOutputPath , 'agent-info.csv' ) ;
147
+ this . pubSubInfoPath = path . join ( this . baseOutputPath , 'pubsub-info.csv' ) ;
140
148
const minutes : number = 30 ;
141
149
this . intervalMs = minutes * 60 * 1000 ;
142
150
}
143
151
144
152
/** Begin periodic recording. */
145
153
public start ( ) : void {
146
- setInterval ( ( ) => this . record ( ) , this . intervalMs ) ;
147
- this . record ( ) ;
154
+ setInterval ( ( ) => void this . record ( ) , this . intervalMs ) ;
155
+ void this . record ( ) ;
148
156
}
149
157
150
158
public startMonitoringConnection ( connection : Connection ) : void {
@@ -176,28 +184,29 @@ export class ResourceMonitor {
176
184
}
177
185
178
186
/** Record current resource usage. */
179
- public record ( ) : void {
180
- this . recordHeapUsage ( ) ;
181
- this . recordConnectionDiagnostics ( ) ;
182
- this . recordAgentDiagnostics ( ) ;
183
- this . recordPubSubDiagnostics ( ) ;
187
+ public async record ( ) : Promise < void > {
188
+ await this . prepareOutputDirectory ( ) ;
189
+ await this . recordHeapUsage ( ) ;
190
+ await this . recordConnectionDiagnostics ( ) ;
191
+ await this . recordAgentDiagnostics ( ) ;
192
+ await this . recordPubSubDiagnostics ( ) ;
184
193
}
185
194
186
- private recordConnectionDiagnostics ( ) : void {
195
+ private async recordConnectionDiagnostics ( ) : Promise < void > {
187
196
const connections = Array . from ( this . connections . values ( ) ) ;
188
197
const report : ConnectionInfo [ ] = connections . map ( ( connection : Connection ) => this . reportOnConnection ( connection ) ) ;
189
- void this . saveToCsv ( this . connectionInfoPath , report ) ;
198
+ await this . saveToCsv ( this . connectionInfoPath , report ) ;
190
199
}
191
200
192
- private recordAgentDiagnostics ( ) : void {
201
+ private async recordAgentDiagnostics ( ) : Promise < void > {
193
202
const report : AgentInfo [ ] = Array . from ( this . agents . values ( ) ) . map ( agent => this . reportOnAgent ( agent ) ) ;
194
- void this . saveToCsv ( this . agentInfoPath , report ) ;
203
+ await this . saveToCsv ( this . agentInfoPath , report ) ;
195
204
}
196
205
197
- private recordPubSubDiagnostics ( ) : void {
206
+ private async recordPubSubDiagnostics ( ) : Promise < void > {
198
207
if ( this . pubSub === undefined ) return ;
199
208
const report = this . reportOnPubSub ( this . pubSub ) ;
200
- void this . saveToCsv ( this . pubSubInfoPath , [ report ] ) ;
209
+ await this . saveToCsv ( this . pubSubInfoPath , [ report ] ) ;
201
210
}
202
211
203
212
private reportOnConnection ( connection : Connection ) : ConnectionInfo {
@@ -261,7 +270,7 @@ export class ResourceMonitor {
261
270
return pubsubInfo ;
262
271
}
263
272
264
- private recordHeapUsage ( ) : void {
273
+ private async recordHeapUsage ( ) : Promise < void > {
265
274
// Measuring memory is more meaningful if garbage collection runs first. The NodeJS process must be started with
266
275
// --expose-gc for this to work. Or we can temporarily switch it on and run gc, but with a context
267
276
// [workaround](https://github.com/nodejs/node/issues/16595).
@@ -282,7 +291,7 @@ export class ResourceMonitor {
282
291
arrayBuffersBytes : memoryUsage . arrayBuffers ,
283
292
availableMemoryBytes : process . availableMemory ( )
284
293
} ;
285
- void this . saveToCsv ( this . heapInfoPath , [ data ] ) ;
294
+ await this . saveToCsv ( this . heapInfoPath , [ data ] ) ;
286
295
}
287
296
288
297
/** Write data to a CSV file. If needed, create header row from the data's objects' keys. */
@@ -308,4 +317,8 @@ export class ResourceMonitor {
308
317
console . error ( `Ignoring error writing to ${ filePath } :` , error ) ;
309
318
}
310
319
}
320
+
321
+ private async prepareOutputDirectory ( ) : Promise < void > {
322
+ await mkdir ( this . baseOutputPath , { recursive : true } ) ;
323
+ }
311
324
}
0 commit comments