Skip to content

Commit 6f5c9fd

Browse files
author
Cihad Tekin
committed
[system-utility] Profiler writes to file system instead of mongodb. new endpoint: take-heap-snapshot
1 parent 154a057 commit 6f5c9fd

File tree

3 files changed

+71
-95
lines changed

3 files changed

+71
-95
lines changed

CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
## Version xx.xx.xx
2+
Features:
3+
- [system-utility] New endpoint: /take-heap-snapshot.
4+
- [system-utility] Using nodejs fs to write profiler files instead of gridfs.
5+
16
## Version 24.05.22
27
Features:
38
- [core] Add self tracking capability
4-
9+
510
Fixes:
611
- [push] Using apns-id header as message result in debug mode
712
- [server-stats] Fix data point calculation in job

plugins/system-utility/api/api.js

+19-24
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@ function handleMessage(msg) {
3131
if (msg.cmd === "stopProfiler" || msg.cmd === "startProfiler") {
3232
args = [processName];
3333
}
34-
35-
systemUtility[msg.cmd](...args).catch(err => {
36-
log.e(err);
37-
console.error(err);
38-
});
34+
systemUtility[msg.cmd](...args).catch(err => log.e(err));
3935
}
4036
else if (typeof msg === "object" && msg.cmd === "setNumberOfWorkers") {
4137
numberOfWorkers = msg.params.numberOfWorkers;
@@ -178,29 +174,29 @@ function stopWithTimeout(type, fromTimeout = false) {
178174
});
179175
return true;
180176

181-
case 'list-files':
182-
validate(params, () => {
183-
systemUtility.listProfilerFiles()
184-
.then(res => common.returnMessage(params, 200, res))
185-
.catch(err => {
186-
log.e(err);
187-
common.returnMessage(params, 404, "Profiler files not found");
177+
case "take-heap-snapshot":
178+
validate(params, async () => {
179+
try {
180+
params.res.writeHead(200, {
181+
"Content-Type": "plain/text; charset=utf-8",
182+
"Content-Disposition": "attachment; filename=heap.heapsnapshot"
188183
});
184+
systemUtility.takeHeapSnapshot(params.res);
185+
}
186+
catch (err) {
187+
log.e(err);
188+
common.returnMessage(params, 500, err.toString());
189+
}
189190
});
190191
return true;
191192

192-
case 'download':
193+
case 'list-files':
193194
validate(params, () => {
194-
systemUtility.downloadProfilerFile(params.qstring.filename)
195-
.then(({ data, filename }) => {
196-
common.returnRaw(params, 200, data, {
197-
'Content-Type': 'plain/text; charset=utf-8',
198-
'Content-disposition': 'attachment; filename=' + filename
199-
});
200-
})
195+
systemUtility.listProfilerFiles()
196+
.then(res => common.returnMessage(params, 200, res))
201197
.catch(err => {
202198
log.e(err);
203-
common.returnMessage(params, 404, "File not found");
199+
common.returnMessage(params, 404, "Profiler files couldn't be found");
204200
});
205201
});
206202
return true;
@@ -210,7 +206,7 @@ function stopWithTimeout(type, fromTimeout = false) {
210206
try {
211207
const tarStream = await systemUtility.profilerFilesTarStream();
212208
if (tarStream === null) {
213-
common.returnMessage(params, 404, "Profiler files not found");
209+
common.returnMessage(params, 404, "Profiler files couldn't be found");
214210
}
215211
else {
216212
params.res.writeHead(200, {
@@ -223,8 +219,7 @@ function stopWithTimeout(type, fromTimeout = false) {
223219
}
224220
catch (err) {
225221
log.e(err);
226-
console.error(err);
227-
common.returnMessage(params, 500, "Server error");
222+
common.returnMessage(params, 500, err.toString());
228223
}
229224
});
230225
return true;

plugins/system-utility/api/system.utility.js

+46-70
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
var common = require('../../../api/utils/common.js');
22
const inspector = require('inspector');
3-
const countlyFs = require('../../../api/utils/countlyFs.js');
43
var exec = require('child_process').exec;
54
const tar = require("tar-stream");
65
const session = new inspector.Session();
6+
const path = require("path");
7+
const fs = require("fs/promises");
8+
const { Writable } = require('stream');
79

8-
const PROFILER_DIR = "nodeprofile";
10+
const PROFILER_DIR = path.join(__dirname, "../../../log/nodeprofile");
911

1012
var _id = null;
1113

@@ -424,23 +426,20 @@ function sessionPost(cmd) {
424426
}
425427

426428
/**
427-
* Saves the result to gridfs
428-
* @param {string} filename file name with extension
429-
* @param {object} result result object returned by the profiler
430-
* @returns {Promise<string>} filename
429+
* Takes a snapshot and writes its content to the passed stream.
430+
* IMPORTANT: it will call the "end" for the passed stream object when it's done
431+
* @param {Writable} stream Writable stream to write snapshot content to
432+
* @returns {void}
431433
*/
432-
function saveProfilerResult(filename, result) {
433-
return new Promise((res, rej) => {
434-
countlyFs.gridfs.saveData(
435-
PROFILER_DIR, filename, JSON.stringify(result),
436-
{ writeMode: "overwrite" },
437-
function(err) {
438-
if (err) {
439-
return rej(err);
440-
}
441-
res(filename);
442-
}
443-
);
434+
function takeHeapSnapshot(stream) {
435+
session.connect();
436+
session.on("HeapProfiler.addHeapSnapshotChunk", m => stream.write(m.params.chunk));
437+
session.post("HeapProfiler.takeHeapSnapshot", null, (err) => {
438+
session.disconnect();
439+
stream.end();
440+
if (err) {
441+
console.error(err);
442+
}
444443
});
445444
}
446445

@@ -470,30 +469,22 @@ async function startProfiler() {
470469
async function stopProfiler(processName) {
471470
const errors = [];
472471

473-
// clear old files
474472
try {
475-
await new Promise(
476-
(res, rej) => countlyFs.gridfs.deleteAll(
477-
PROFILER_DIR,
478-
null,
479-
err => err ? rej(err) : res()
480-
)
481-
);
473+
await fs.rm(PROFILER_DIR, { force: true, recursive: true }); // clear old files
474+
await fs.mkdir(PROFILER_DIR, { recursive: true });
482475
}
483476
catch (err) {
484-
if (err.code === 26) { // NamespaceNotFound: thrown when there's no collection initially
485-
// do nothing...
486-
}
487-
else {
488-
throw err;
489-
}
477+
errors.push(err);
490478
}
491479

492480
// coverage
493481
try {
494482
const coverage = await sessionPost("Profiler.takePreciseCoverage");
495-
await saveProfilerResult(processName + ".coverage", coverage?.result);
496483
await sessionPost("Profiler.stopPreciseCoverage");
484+
await fs.writeFile(
485+
path.join(PROFILER_DIR, processName + ".coverage"),
486+
JSON.stringify(coverage?.result)
487+
);
497488
}
498489
catch (err) {
499490
errors.push(err);
@@ -502,8 +493,11 @@ async function stopProfiler(processName) {
502493
// cpu profiler
503494
try {
504495
const cpuProfile = await sessionPost("Profiler.stop");
505-
await saveProfilerResult(processName + ".cpuprofile", cpuProfile?.profile);
506496
await sessionPost("Profiler.disable");
497+
await fs.writeFile(
498+
path.join(PROFILER_DIR, processName + ".cpuprofile"),
499+
JSON.stringify(cpuProfile?.profile)
500+
);
507501
}
508502
catch (err) {
509503
errors.push(err);
@@ -512,8 +506,11 @@ async function stopProfiler(processName) {
512506
// heap profiler
513507
try {
514508
const heapProfile = await sessionPost("HeapProfiler.stopSampling");
515-
await saveProfilerResult(processName + ".heapprofile", heapProfile?.profile);
516509
await sessionPost("HeapProfiler.disable");
510+
await fs.writeFile(
511+
path.join(PROFILER_DIR, processName + ".heapprofile"),
512+
JSON.stringify(heapProfile?.profile)
513+
);
517514
}
518515
catch (err) {
519516
errors.push(err);
@@ -526,39 +523,24 @@ async function stopProfiler(processName) {
526523
}
527524
}
528525

529-
/**
530-
* Returns the data of a file in PROFILER_DIR collection
531-
* @param {string} filename file name with extension
532-
* @returns {Promise<{data:string, filename:string}>} file object with name and content
533-
*/
534-
function downloadProfilerFile(filename) {
535-
return new Promise((resolve, reject) => {
536-
countlyFs.gridfs.getData(PROFILER_DIR, filename, {}, (err, data) => {
537-
if (err) {
538-
return reject("File not found");
539-
}
540-
resolve({ data, filename });
541-
});
542-
});
543-
}
544526
/**
545527
* Returns the names, creation dates and size of all files in the PROFILER_DIR collection
546-
* @returns {Promise<Array<{createdOn: Date, filename: string, size: number }>>} file info
528+
* @returns {Promise<Array<{createdOn: Date, filename: string, size: number, path: string }>>} file info
547529
*/
548-
function listProfilerFiles() {
549-
return new Promise((resolve, reject) => {
550-
countlyFs.gridfs.listFiles(PROFILER_DIR, (err, files) => {
551-
if (err) {
552-
return reject(err);
553-
}
554-
resolve(files);
555-
});
556-
});
530+
async function listProfilerFiles() {
531+
const files = await fs.readdir(PROFILER_DIR);
532+
return Promise.all(
533+
files.map(async (filename) => {
534+
const fullPath = path.join(PROFILER_DIR, filename);
535+
const { birthtime, size } = await fs.stat(fullPath);
536+
return { createdOn: birthtime, size, filename, path: fullPath }
537+
})
538+
);
557539
}
558540

559541
/**
560542
* Returns the tarball read stream for all profiler files
561-
* @returns {tar.Pack} tar stream
543+
* @returns {Promise<tar.Pack>} tar stream
562544
*/
563545
async function profilerFilesTarStream() {
564546
const files = await listProfilerFiles();
@@ -569,14 +551,8 @@ async function profilerFilesTarStream() {
569551
const pack = tar.pack();
570552
for (let i = 0; i < files.length; i++) {
571553
const entry = pack.entry({ name: files[i].filename, size: files[i].size });
572-
const stream = await new Promise((res, rej) => {
573-
countlyFs.gridfs.getStream(
574-
PROFILER_DIR,
575-
files[i].filename,
576-
{},
577-
(err, fileStream) => err ? rej(err) : res(fileStream)
578-
);
579-
});
554+
const handle = await fs.open(files[i].path);
555+
const stream = handle.createReadStream();
580556
stream.pipe(entry);
581557
stream.on("end", () => {
582558
entry.end();
@@ -622,9 +598,9 @@ function stopInspector() {
622598
});
623599
}
624600

601+
exports.takeHeapSnapshot = takeHeapSnapshot;
625602
exports.startProfiler = startProfiler;
626603
exports.stopProfiler = stopProfiler;
627-
exports.downloadProfilerFile = downloadProfilerFile;
628604
exports.listProfilerFiles = listProfilerFiles;
629605
exports.startInspector = startInspector;
630606
exports.stopInspector = stopInspector;

0 commit comments

Comments
 (0)