Skip to content

Commit 1fd31c0

Browse files
committed
Store resource usage report in more persistent location
1 parent de20eeb commit 1fd31c0

File tree

2 files changed

+33
-20
lines changed

2 files changed

+33
-20
lines changed

src/RealtimeServer/common/diagnostics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ function createHeapSnapshot() {
5353

5454
function recordResourceUsage() {
5555
console.log('Recording resource usage');
56-
ResourceMonitor.instance.record();
56+
void ResourceMonitor.instance.record();
5757
}

src/RealtimeServer/common/resource-monitor.ts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as fs from 'fs';
2+
import { mkdir } from 'fs/promises';
23
import { jsonSizeOf } from 'json-sizeof';
34
import * as path from 'path';
45
import ShareDB, { Agent, PubSub } from 'sharedb';
@@ -126,25 +127,32 @@ export class ResourceMonitor {
126127
private readonly connectionInfoPath: string;
127128
private readonly agentInfoPath: string;
128129
private readonly pubSubInfoPath: string;
130+
private readonly baseOutputPath: string;
129131

130132
/** Singleton. */
131133
public static get instance(): ResourceMonitor {
132134
return (ResourceMonitor._instance ??= new ResourceMonitor());
133135
}
134136

135137
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');
140148
const minutes: number = 30;
141149
this.intervalMs = minutes * 60 * 1000;
142150
}
143151

144152
/** Begin periodic recording. */
145153
public start(): void {
146-
setInterval(() => this.record(), this.intervalMs);
147-
this.record();
154+
setInterval(() => void this.record(), this.intervalMs);
155+
void this.record();
148156
}
149157

150158
public startMonitoringConnection(connection: Connection): void {
@@ -176,28 +184,29 @@ export class ResourceMonitor {
176184
}
177185

178186
/** 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();
184193
}
185194

186-
private recordConnectionDiagnostics(): void {
195+
private async recordConnectionDiagnostics(): Promise<void> {
187196
const connections = Array.from(this.connections.values());
188197
const report: ConnectionInfo[] = connections.map((connection: Connection) => this.reportOnConnection(connection));
189-
void this.saveToCsv(this.connectionInfoPath, report);
198+
await this.saveToCsv(this.connectionInfoPath, report);
190199
}
191200

192-
private recordAgentDiagnostics(): void {
201+
private async recordAgentDiagnostics(): Promise<void> {
193202
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);
195204
}
196205

197-
private recordPubSubDiagnostics(): void {
206+
private async recordPubSubDiagnostics(): Promise<void> {
198207
if (this.pubSub === undefined) return;
199208
const report = this.reportOnPubSub(this.pubSub);
200-
void this.saveToCsv(this.pubSubInfoPath, [report]);
209+
await this.saveToCsv(this.pubSubInfoPath, [report]);
201210
}
202211

203212
private reportOnConnection(connection: Connection): ConnectionInfo {
@@ -261,7 +270,7 @@ export class ResourceMonitor {
261270
return pubsubInfo;
262271
}
263272

264-
private recordHeapUsage(): void {
273+
private async recordHeapUsage(): Promise<void> {
265274
// Measuring memory is more meaningful if garbage collection runs first. The NodeJS process must be started with
266275
// --expose-gc for this to work. Or we can temporarily switch it on and run gc, but with a context
267276
// [workaround](https://github.com/nodejs/node/issues/16595).
@@ -282,7 +291,7 @@ export class ResourceMonitor {
282291
arrayBuffersBytes: memoryUsage.arrayBuffers,
283292
availableMemoryBytes: process.availableMemory()
284293
};
285-
void this.saveToCsv(this.heapInfoPath, [data]);
294+
await this.saveToCsv(this.heapInfoPath, [data]);
286295
}
287296

288297
/** Write data to a CSV file. If needed, create header row from the data's objects' keys. */
@@ -308,4 +317,8 @@ export class ResourceMonitor {
308317
console.error(`Ignoring error writing to ${filePath}:`, error);
309318
}
310319
}
320+
321+
private async prepareOutputDirectory(): Promise<void> {
322+
await mkdir(this.baseOutputPath, { recursive: true });
323+
}
311324
}

0 commit comments

Comments
 (0)