Skip to content

Commit

Permalink
src: support dump node report (X-Profiler#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyj1991 authored Dec 2, 2019
1 parent 9c70196 commit 0c19e6d
Show file tree
Hide file tree
Showing 29 changed files with 1,182 additions and 39 deletions.
11 changes: 8 additions & 3 deletions bin/xprofctl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const args = yargs
.command('start_gc_profiling', '启动 gc 采样', profilingYargs)
.command('stop_gc_profiling', '生成 gcprofile', normalYargs)
.command('heapdump', '生成 heapsnapshot', normalYargs)
.command('diag_report', '生成诊断报告', normalYargs)
.command('check_version', '获取 xprofiler 版本号', normalYargs)
.command('get_config', '获取 xprofiler 配置', normalYargs)
.command('set_config', '设置 xprofiler 配置',
Expand Down Expand Up @@ -100,13 +101,13 @@ xctl(pid, action, options.data)
console.log(`CPU profiling 文件路径: ${data.filepath}`);
break;
case 'heapdump':
console.log(`Heapdump 文件路径${data.filepath}`);
console.log(`Heapdump 文件路径: ${data.filepath}`);
console.log(`生成 heapsnapshot 视堆序列化耗费需要数秒至数分钟.`);
break;
case 'start_heap_profiling': {
const profiling_time = options.data.profiling_time;
if (profiling_time) {
console.log(`Heap profiling 文件路径${data.filepath}`);
console.log(`Heap profiling 文件路径: ${data.filepath}`);
console.log(`请等待 ${profiling_time / 1000} 秒后读取此次 heap profiling 结果.`);
} else {
console.log(`进程 (pid ${pid}) 开始进行 Heap 采样.`);
Expand All @@ -119,7 +120,7 @@ xctl(pid, action, options.data)
case 'start_gc_profiling': {
const profiling_time = options.data.profiling_time;
if (profiling_time) {
console.log(`GC profiling 文件路径${data.filepath}`);
console.log(`GC profiling 文件路径: ${data.filepath}`);
console.log(`请等待 ${profiling_time / 1000} 秒后读取此次 gc profiling 结果.`);
} else {
console.log(`进程 (pid ${pid}) 开始进行 gc 采样.`);
Expand All @@ -129,6 +130,10 @@ xctl(pid, action, options.data)
case 'stop_gc_profiling':
console.log(`GC profiling 文件路径: ${data.filepath}`);
break;
case 'diag_report':
console.log(`诊断报告文件路径: ${data.filepath}`);
console.log(`生成诊断报告可能需要数秒至数十秒.`);
break;
default:
console.error(`未知操作 ${action}: ${JSON.stringify(data)}`);
}
Expand Down
9 changes: 9 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
"src/commands/heapdump/heap_snapshot.cc",
"src/commands/heapprofiler/sampling_heap_profiler.cc",
"src/commands/gcprofiler/gc_profiler.cc",
"src/commands/report/node_report.cc",
"src/commands/report/javascript_stack.cc",
"src/commands/report/native_stack.cc",
"src/commands/report/heap_statistics.cc",
"src/commands/report/uv_statistics.cc",
"src/commands/report/system_statistics.cc",
],
"include_dirs": [ '<!(node -e "require(\'nan\')")' ],
'cflags_cc!': [ '-fno-exceptions' ],
Expand All @@ -44,6 +50,7 @@
"src/platform/unix/cpu.cc",
"src/platform/unix/utils.cc",
"src/platform/unix/ipc.cc",
"src/platform/unix/report.cc",
]
}],
["OS == 'mac'", {
Expand All @@ -57,6 +64,7 @@
"src/platform/unix/cpu.cc",
"src/platform/unix/utils.cc",
"src/platform/unix/ipc.cc",
"src/platform/unix/report.cc",
]
}],
["OS == 'win'", {
Expand All @@ -75,6 +83,7 @@
"src/platform/win/cpu_win.cc",
"src/platform/win/utils_win.cc",
"src/platform/win/ipc_win.cc",
"src/platform/win/report_win.cc",
]
}],
],
Expand Down
78 changes: 52 additions & 26 deletions lib/xctl.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const utils = require('../lib/utils');

const TIMEOUT_ERROR = Symbol('XPROFILER_CTL_TIMEOUT');

const timer = {};
let server;
let client;

function getXprofilerLogDir(pid) {
let xprofilerPath;
if (process.env.XPROFILER_UNIT_TEST_TMP_HOMEDIR) {
Expand Down Expand Up @@ -106,40 +110,53 @@ async function createMessageConnection(pid, xprofilerPath, message) {
}

return new Promise((resolve, reject) => {
const client = net.createConnection(xprofilerPath, () => {
client.write(message, err => err ? /* istanbul ignore next */reject(err) : resolve());
client = net.createConnection(xprofilerPath, () => {
client.write(message, err => err ? /* istanbul ignore next */reject(err) : resolve('success'));
});
client.on('error', /* istanbul ignore next */error => {
reject(`发送命令 ${message} 失败: ${error}`);
});
client.on('end', () => {
client.destroy();
});
});
}


let server;
function createResultServer(serverCtlPath) {
return new Promise((resolve, reject) => {
server = net.createServer(conn => {
conn.on('data', data => resolve(data.toString()));
conn.on('error', /* istanbul ignore next */err => reject(`接收响应失败: ${err}`));
conn.on('close', () => server.close());
});
server.listen(serverCtlPath);
server.on('error', /* istanbul ignore next */err => {
reject(`创建接收响应服务失败: ${err}`);
server.close();
});
server.on('error', /* istanbul ignore next */err => reject(`创建接收响应服务失败: ${err}`));
});
}

// release resource
function teardown(key) {
server && server.close();
server = null;

client && client.destroy();
client = null;

/* istanbul ignore next */
if (timer[key]) {
timer[key] && clearTimeout(timer[key]);
timer[key] = null;
return;
}

for (const t of Object.entries(timer)) {
t[1] && clearTimeout(t[1]);
t[1] = null;
}
}

let timer;
/* istanbul ignore next */
function timeout(time) {
return new Promise(resolve => timer = setTimeout(() => resolve(TIMEOUT_ERROR), time));
function timeout(time, key) {
return new Promise(resolve => timer[key] = setTimeout(() => {
resolve(TIMEOUT_ERROR);
teardown(key);
}, time));
}

async function sendCommands(pid, command, /* istanbul ignore next */options = {}) {
Expand All @@ -148,28 +165,39 @@ async function sendCommands(pid, command, /* istanbul ignore next */options = {}
throw new Error('命令参数不能缺失!');
}

// expired
const expired = 1500;

const resultTasks = [];
// wait for result
const serverCtlPath = composeCtlServerPath(pid);
resultTasks.push(createResultServer(serverCtlPath));
// expired
const expired = 2000;
resultTasks.push(timeout(expired));
resultTasks.push(timeout(expired, 'result'));

// send message & check result
const sendTasks = [];
// send message
const ipcPath = composeXprofilerPath(pid);
sendTasks.push(createMessageConnection(pid, ipcPath, { traceid: uuid(), cmd: command, options }));
sendTasks.push(timeout(expired, 'send'));

// send message & check result
let result = await Promise.all([
createMessageConnection(pid, ipcPath, { traceid: uuid(), cmd: command, options }),
Promise.race(sendTasks),
Promise.race(resultTasks)
]);

result = result[1];
/* istanbul ignore if */
if (result[0] === TIMEOUT_ERROR) {
throw new Error(`命令 ${command} 请求超时(${expired / 1000}s)!`);
}

/* istanbul ignore if */
if (result === TIMEOUT_ERROR) {
throw new Error(`命令 ${command} 超时(${expired / 1000}s)!`);
if (result[1] === TIMEOUT_ERROR) {
throw new Error(`命令 ${command} 响应超时(${expired / 1000}s)!`);
}

result = result[1];

return JSON.parse(result);
}

Expand All @@ -183,9 +211,7 @@ module.exports = async function (...args) {
result = { ok: false, message: err.message, stack: err.stack };
}

// release resource
server && server.close();
timer && clearTimeout(timer);
teardown();

return result;
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"main": "xprofiler.js",
"scripts": {
"build": "npm run lint && node-gyp rebuild",
"format": "clang-format -i --glob=\"src/**/*[.h|.cc]\"",
"format": "clang-format -i --glob=\"src/**/!(report_win)[.h|.cc]\"",
"lint": "npm run format && eslint --fix xprofiler.js \"test/**/*.js\" lib/*.js bin/xprofctl",
"test": "mocha -t 10000 -R spec test/*.test.js",
"test-single": "mocha -t 10000 -R spec",
Expand Down
24 changes: 22 additions & 2 deletions src/commands/dump.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "gcprofiler/gc_profiler.h"
#include "heapdump/heap_profiler.h"
#include "heapprofiler/sampling_heap_profiler.h"
#include "report/node_report.h"
#include "uv.h"
#include "v8.h"

Expand Down Expand Up @@ -45,6 +46,7 @@ static string cpuprofile_filepath = "";
static string sampling_heapprofile_filepath = "";
static string heapsnapshot_filepath = "";
static string gcprofile_filepath = "";
static string node_report_filepath = "";

static string Action2String(DumpAction action) {
string name = "";
Expand All @@ -70,6 +72,9 @@ static string Action2String(DumpAction action) {
case STOP_GC_PROFILING:
name = "stop_gc_profiling";
break;
case NODE_REPORT:
name = "node_report";
break;
default:
name = "unknown";
break;
Expand Down Expand Up @@ -135,7 +140,7 @@ static T *GetDumpData(void *data) {

static void AfterDumpFile(string filepath, string notify_type,
string unique_key) {
Debug(module_type, "<%s> %s dump file: %s creating.", notify_type.c_str(),
Debug(module_type, "<%s> %s dump file: %s.", notify_type.c_str(),
unique_key.c_str(), filepath.c_str());
}

Expand Down Expand Up @@ -223,6 +228,12 @@ void HandleAction(void *data, string notify_type) {
action_map.erase(START_GC_PROFILING);
action_map.erase(STOP_GC_PROFILING);
}
case NODE_REPORT: {
NodeReport::GetNodeReport(node_report_filepath);
AfterDumpFile(node_report_filepath, notify_type, unique_key);
action_map.erase(NODE_REPORT);
break;
}
default:
Error(module_type, "not support dump action: %d", action);
break;
Expand Down Expand Up @@ -287,7 +298,7 @@ static void ProfilingWatchDog(void *data) {

static string CreateFilepath(string prefix, string ext) {
return GetLogDir() + GetSep() + "x-" + prefix + "-" + to_string(GetPid()) +
"-" + GetDate() + "-" + RandNum() + "." + ext;
"-" + ConvertTime("%Y%m%d") + "-" + RandNum() + "." + ext;
}

int InitDumpAction() {
Expand Down Expand Up @@ -354,6 +365,10 @@ static json DoDumpAction(json command, DumpAction action, string prefix,
case STOP_GC_PROFILING:
result["filepath"] = gcprofile_filepath;
break;
case NODE_REPORT:
node_report_filepath = CreateFilepath(prefix, ext);
result["filepath"] = node_report_filepath;
break;
default:
break;
}
Expand Down Expand Up @@ -432,6 +447,11 @@ COMMAND_CALLBACK(StopGcProfiling) {
ACTION_HANDLE(STOP_GC_PROFILING, gcprofiler_, false, gcprofile, gcprofile)
}

COMMAND_CALLBACK(GetNodeReport) {
node_report_dump_data_t *data = new node_report_dump_data_t;
ACTION_HANDLE(NODE_REPORT, node_report_, false, diagreport, diag)
}

#undef ACTION_HANDLE

#undef CHECK
Expand Down
7 changes: 6 additions & 1 deletion src/commands/dump.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ enum DumpAction {
START_SAMPLING_HEAP_PROFILING,
STOP_SAMPLING_HEAP_PROFILING,
START_GC_PROFILING,
STOP_GC_PROFILING
STOP_GC_PROFILING,
NODE_REPORT
};

typedef unordered_map<int, bool> ActionMap;
Expand Down Expand Up @@ -45,6 +46,9 @@ typedef struct SamplingHeapProfilerDumpData : BaseDumpData {
typedef struct GcProfilerDumpData : BaseDumpData {
} gcprofiler_dump_data_t;

typedef struct NodeReportDumpData : BaseDumpData {
} node_report_dump_data_t;

int InitDumpAction();

void UnrefDumpActionAsyncHandle();
Expand All @@ -56,6 +60,7 @@ COMMAND_CALLBACK(StartSamplingHeapProfiling);
COMMAND_CALLBACK(StopSamplingHeapProfiling);
COMMAND_CALLBACK(StartGcProfiling);
COMMAND_CALLBACK(StopGcProfiling);
COMMAND_CALLBACK(GetNodeReport);
} // namespace xprofiler

#endif
2 changes: 2 additions & 0 deletions src/commands/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ void ParseCmd(char *command) {
// gc profiling
V(start_gc_profiling, StartGcProfiling)
V(stop_gc_profiling, StopGcProfiling)
// node report
V(diag_report, GetNodeReport)
#undef V

// not match any commands
Expand Down
44 changes: 44 additions & 0 deletions src/commands/report/heap_statistics.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "heap_statistics.h"

#include "v8.h"

namespace xprofiler {
using v8::HeapSpaceStatistics;
using v8::HeapStatistics;
using v8::Isolate;

void SetHeapStatistics(JSONWriter* writer) {
Isolate* isolate = Isolate::GetCurrent();
HeapStatistics v8_heap_stats;
isolate->GetHeapStatistics(&v8_heap_stats);

writer->json_objectstart("heapStatistics");
writer->json_keyvalue("heapTotal", v8_heap_stats.total_heap_size());
writer->json_keyvalue("heapTotalCommitted",
v8_heap_stats.total_physical_size());
writer->json_keyvalue("heapTotalUsed", v8_heap_stats.used_heap_size());
writer->json_keyvalue("heapTotalAvailable",
v8_heap_stats.total_available_size());
writer->json_keyvalue("heapLimit", v8_heap_stats.heap_size_limit());
writer->json_objectend();

HeapSpaceStatistics v8_heap_space_stats;
writer->json_arraystart("heapSpaceStatistics");
for (size_t i = 0; i < isolate->NumberOfHeapSpaces(); i++) {
isolate->GetHeapSpaceStatistics(&v8_heap_space_stats, i);
writer->json_start();
writer->json_keyvalue("name", v8_heap_space_stats.space_name());
writer->json_keyvalue("size", v8_heap_space_stats.space_size());
writer->json_keyvalue("committed",
v8_heap_space_stats.physical_space_size());
writer->json_keyvalue("capacity",
v8_heap_space_stats.space_used_size() +
v8_heap_space_stats.space_available_size());
writer->json_keyvalue("used", v8_heap_space_stats.space_used_size());
writer->json_keyvalue("available",
v8_heap_space_stats.space_available_size());
writer->json_end();
}
writer->json_arrayend();
}
} // namespace xprofiler
Loading

0 comments on commit 0c19e6d

Please sign in to comment.