Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bin/xprofctl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ const normalYargs = yargs => yargs
.hide('v')
.hide('h');

const killProcessYargs = yargs => yargs
.group(['target_pid'], 'kill_process 配置:')
.describe('target_pid', '目标进程 pid')
.number('target_pid')
.demandOption(['target_pid'])
.hide('v')
.hide('h');

const args = yargs
.usage('$0 <action> -p <pid> [-t profiling_time] [-w worker_thread_id]')
// commands
Expand All @@ -33,6 +41,7 @@ const args = yargs
.command('heapdump', '生成 heapsnapshot', normalYargs)
.command('diag_report', '生成诊断报告', normalYargs)
.command('generate_cored', '生成 coredump', normalYargs)
.command('kill_process', '强制终止目标进程 (SIGKILL)', killProcessYargs)
.command('check_version', '获取 xprofiler 版本号', normalYargs)
.command('get_config', '获取 xprofiler 配置', normalYargs)
.command('set_config', '设置 xprofiler 配置',
Expand Down Expand Up @@ -169,6 +178,9 @@ xctl(pid, thread_id, action, args)
console.log(`Coredump 文件路径: ${data.filepath}`);
console.log(`生成 Coredump 可能需要数秒至数十秒.`);
break;
case 'kill_process':
console.log(`进程 ${data.target_pid} 已被强制终止.`);
break;
default:
console.error(`未知操作 ${action}: ${JSON.stringify(data)}`);
}
Expand Down
1 change: 1 addition & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"src/commands/simple/version.cc",
"src/commands/simple/registry.cc",
"src/commands/simple/config.cc",
"src/commands/simple/kill.cc",
"src/commands/cpuprofiler/cpu_profiler.cc",
"src/commands/cpuprofiler/cpu_profile.cc",
"src/commands/cpuprofiler/cpu_profile_node.cc",
Expand Down
4 changes: 4 additions & 0 deletions src/commands/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "commands/dump.h"
#include "commands/send.h"
#include "commands/simple/config.h"
#include "commands/simple/kill.h"
#include "commands/simple/registry.h"
#include "commands/simple/version.h"
#include "library/error.h"
Expand Down Expand Up @@ -79,6 +80,9 @@ void ParseCmd(char* command) {
// generator
HANDLE_COMMANDS(generate_coredump, GenerateCoredump)

// kill process
HANDLE_COMMANDS(kill_process, KillProcess)

// not match any commands
/* else */ {
ErrorValue(traceid, FmtMessage("not support command: %s", cmd.c_str()));
Expand Down
95 changes: 95 additions & 0 deletions src/commands/simple/kill.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "commands/simple/kill.h"

#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <unistd.h>

#include <cerrno>
#include <csignal>
#include <cstring>
#endif

#include "library/utils.h"

namespace xprofiler {
using nlohmann::json;

COMMAND_CALLBACK(KillProcess) {
XpfError err;
int target_pid = GetJsonValue<int>(command["options"], "target_pid", err);
if (err.Fail()) {
error(format("%s", err.GetErrMessage()));
return;
}

if (target_pid <= 0) {
error(format("target_pid must be a positive integer"));
return;
}

if (target_pid == 1) {
error(format("cannot kill init process (pid 1)"));
return;
}

#ifdef _WIN32
if (static_cast<DWORD>(target_pid) == GetCurrentProcessId()) {
error(format("cannot kill self process"));
return;
}

// Check permission and kill on Windows
HANDLE hProcess =
OpenProcess(PROCESS_TERMINATE, FALSE, static_cast<DWORD>(target_pid));
if (hProcess == NULL) {
DWORD win_err = GetLastError();
if (win_err == ERROR_ACCESS_DENIED) {
error(
format("permission denied: cannot terminate process %d", target_pid));
} else {
error(
format("process %d does not exist or cannot be opened", target_pid));
}
return;
}
BOOL result = TerminateProcess(hProcess, 1);
CloseHandle(hProcess);
if (!result) {
error(format("failed to terminate process %d", target_pid));
return;
}
#else
if (static_cast<pid_t>(target_pid) == getpid()) {
error(format("cannot kill self process"));
return;
}

// Check permission using kill(pid, 0)
if (kill(static_cast<pid_t>(target_pid), 0) == -1) {
if (errno == EPERM) {
error(format("permission denied: cannot send signal to process %d",
target_pid));
} else if (errno == ESRCH) {
error(format("process %d does not exist", target_pid));
} else {
error(format("failed to check process %d: %s", target_pid,
strerror(errno)));
}
return;
}

// Send SIGKILL
if (kill(static_cast<pid_t>(target_pid), SIGKILL) == -1) {
error(format("failed to kill process %d: %s", target_pid, strerror(errno)));
return;
}
#endif

json data;
data["target_pid"] = target_pid;
data["message"] = format("process %d killed successfully", target_pid);
success(data);
}
} // namespace xprofiler
10 changes: 10 additions & 0 deletions src/commands/simple/kill.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef XPROFILER_SRC_COMMANDS_SIMPLE_KILL_H
#define XPROFILER_SRC_COMMANDS_SIMPLE_KILL_H

#include "commands/parser.h"

namespace xprofiler {
COMMAND_CALLBACK(KillProcess);
} // namespace xprofiler

#endif /* XPROFILER_SRC_COMMANDS_SIMPLE_KILL_H */
8 changes: 6 additions & 2 deletions test/commands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ function convertOptions(options) {

describe('commands', () => {
for (let i = 0; i < testConfig.length; i++) {
const { cmd, options = {}, profileRules, profileCheck,
const { cmd, options: staticOptions = {}, optionsFn, profileRules, profileCheck,
errored = false, xctlRules, xprofctlRules, platform, env = {} } = testConfig[i];
for (let j = 0; j < testFiles.length; j++) {
const { jspath, desc, threadId = 0 } = testFiles[j];
const ospt = platform || currentPlatform;
const title =
`[${ospt}] execute [${cmd}] on thread(${threadId}) with `
+ `options: ${JSON.stringify(options)}, `
+ `options: ${JSON.stringify(staticOptions)}, `
+ `env: ${JSON.stringify(env)} `
+ desc;
describe(title, function () {
Expand All @@ -67,7 +67,11 @@ describe('commands', () => {
let resByXprofctl = '';
let pid = 0;
let exitInfo = { code: null, signal: null };
let options = staticOptions;
before(async function () {
if (typeof optionsFn === 'function') {
options = optionsFn();
}
mm(os, 'homedir', () => tmphome);
mm(process.env, 'UNIT_TEST_COMMAND_EXPIRED_TIME', commandExpiredTime);
console.log(`[${moment().format('YYYY-MM-DD HH:mm:ss')}]`, 'start fork.');
Expand Down
16 changes: 16 additions & 0 deletions test/fixtures/cases/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,22 @@ exports = module.exports = function (logdir) {
xctlRules: [],
xprofctlRules() { return [/执行命令失败: generate_coredump only support linux now./]; }
},
{
cmd: 'kill_process',
optionsFn() {
const child = cp.spawn(process.execPath, ['-e', 'setTimeout(() => {}, 99999)']);
child.unref();
return { target_pid: child.pid };
},
xctlRules: [
{ key: 'data.message', rule: /killed successfully/ },
],
xprofctlRules() {
// xctl kills the target process first; by the time xprofctl runs the
// process is already gone, so we only validate the xctl response.
return [];
}
},
];

return filterTestCaseByPlatform(list);
Expand Down