Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoyee Cheung <joyeec9h3@gmail.com>2019-03-23 02:39:52 +0300
committerJoyee Cheung <joyeec9h3@gmail.com>2019-04-19 10:38:23 +0300
commite0e308448240260c207958dfc3dd9245d903af85 (patch)
tree611f54d2aac113f735f7a3047f2857561192e969 /src/inspector_profiler.cc
parent57ab3b56fcba725e1af4ed7f8a05bbdbd0b8a4d9 (diff)
inspector: implement --cpu-prof[-path]
This patch introduces a CLI flag --cpu-prof that starts the V8 CPU profiler on start up, and ends the profiler then writes the CPU profile before the Node.js instance (on the main thread or the worker thread) exits. By default the profile is written to `${cwd}/CPU.${yyyymmdd}.${hhmmss}.${pid}.${tid}.${seq}.cpuprofile`. The patch also introduces a --cpu-prof-path flag for the user to specify the path the profile will be written to. Refs: https://github.com/nodejs/node/issues/26878 PR-URL: https://github.com/nodejs/node/pull/27147 Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'src/inspector_profiler.cc')
-rw-r--r--src/inspector_profiler.cc146
1 files changed, 143 insertions, 3 deletions
diff --git a/src/inspector_profiler.cc b/src/inspector_profiler.cc
index 27b1db14c92..46d4c4fec8d 100644
--- a/src/inspector_profiler.cc
+++ b/src/inspector_profiler.cc
@@ -23,10 +23,14 @@ using v8::Value;
using v8_inspector::StringBuffer;
using v8_inspector::StringView;
-#ifdef __POSIX__
-const char* const kPathSeparator = "/";
-#else
+#ifdef _WIN32
const char* const kPathSeparator = "\\/";
+/* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */
+#define CWD_BUFSIZE (MAX_PATH * 4)
+#else
+#include <climits> // PATH_MAX on Solaris.
+const char* const kPathSeparator = "/";
+#define CWD_BUFSIZE (PATH_MAX)
#endif
std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
@@ -180,6 +184,116 @@ void V8CoverageConnection::End() {
DispatchMessage(end);
}
+void V8CpuProfilerConnection::OnMessage(
+ const v8_inspector::StringView& message) {
+ Debug(env(),
+ DebugCategory::INSPECTOR_PROFILER,
+ "Receive cpu profiling message, ending = %s\n",
+ ending_ ? "true" : "false");
+ if (!ending_) {
+ return;
+ }
+ Isolate* isolate = env()->isolate();
+ HandleScope handle_scope(isolate);
+ Local<Context> context = env()->context();
+ Context::Scope context_scope(context);
+ Local<String> result;
+ if (!String::NewFromTwoByte(isolate,
+ message.characters16(),
+ NewStringType::kNormal,
+ message.length())
+ .ToLocal(&result)) {
+ fprintf(stderr, "Failed to convert profiling message\n");
+ }
+ WriteCpuProfile(result);
+}
+
+void V8CpuProfilerConnection::WriteCpuProfile(Local<String> message) {
+ const std::string& path = env()->cpu_profile_path();
+ CHECK(!path.empty());
+ std::string directory = path.substr(0, path.find_last_of(kPathSeparator));
+ if (directory != path) {
+ uv_fs_t req;
+ int ret = fs::MKDirpSync(nullptr, &req, directory, 0777, nullptr);
+ uv_fs_req_cleanup(&req);
+ if (ret < 0 && ret != UV_EEXIST) {
+ char err_buf[128];
+ uv_err_name_r(ret, err_buf, sizeof(err_buf));
+ fprintf(stderr,
+ "%s: Failed to create cpu profile directory %s\n",
+ err_buf,
+ directory.c_str());
+ return;
+ }
+ }
+ MaybeLocal<String> result = GetResult(message);
+ if (!result.IsEmpty()) {
+ WriteResult(path.c_str(), result.ToLocalChecked());
+ }
+}
+
+MaybeLocal<String> V8CpuProfilerConnection::GetResult(Local<String> message) {
+ Local<Context> context = env()->context();
+ Isolate* isolate = env()->isolate();
+ Local<Value> parsed;
+ if (!v8::JSON::Parse(context, message).ToLocal(&parsed) ||
+ !parsed->IsObject()) {
+ fprintf(stderr, "Failed to parse CPU profile result as JSON object\n");
+ return MaybeLocal<String>();
+ }
+
+ Local<Value> result_v;
+ if (!parsed.As<Object>()
+ ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
+ .ToLocal(&result_v)) {
+ fprintf(stderr, "Failed to get result from CPU profile message\n");
+ return MaybeLocal<String>();
+ }
+
+ if (!result_v->IsObject()) {
+ fprintf(stderr, "'result' from CPU profile message is not an object\n");
+ return MaybeLocal<String>();
+ }
+
+ Local<Value> profile_v;
+ if (!result_v.As<Object>()
+ ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "profile"))
+ .ToLocal(&profile_v)) {
+ fprintf(stderr, "'profile' from CPU profile result is undefined\n");
+ return MaybeLocal<String>();
+ }
+
+ Local<String> result_s;
+ if (!v8::JSON::Stringify(context, profile_v).ToLocal(&result_s)) {
+ fprintf(stderr, "Failed to stringify CPU profile result\n");
+ return MaybeLocal<String>();
+ }
+
+ return result_s;
+}
+
+void V8CpuProfilerConnection::Start() {
+ Debug(env(), DebugCategory::INSPECTOR_PROFILER, "Sending Profiler.start\n");
+ Isolate* isolate = env()->isolate();
+ Local<String> enable = FIXED_ONE_BYTE_STRING(
+ isolate, R"({"id": 1, "method": "Profiler.enable"})");
+ Local<String> start = FIXED_ONE_BYTE_STRING(
+ isolate, R"({"id": 2, "method": "Profiler.start"})");
+ DispatchMessage(enable);
+ DispatchMessage(start);
+}
+
+void V8CpuProfilerConnection::End() {
+ CHECK_EQ(ending_, false);
+ ending_ = true;
+ Debug(env(), DebugCategory::INSPECTOR_PROFILER, "Sending Profiler.stop\n");
+ Isolate* isolate = env()->isolate();
+ HandleScope scope(isolate);
+ Local<String> end =
+ FIXED_ONE_BYTE_STRING(isolate, R"({"id": 3, "method": "Profiler.stop"})");
+ DispatchMessage(end);
+}
+
// For now, we only support coverage profiling, but we may add more
// in the future.
void EndStartedProfilers(Environment* env) {
@@ -190,6 +304,12 @@ void EndStartedProfilers(Environment* env) {
env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n");
connection->End();
}
+
+ connection = env->cpu_profiler_connection();
+ if (connection != nullptr && !connection->ending()) {
+ Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending cpu profiling\n");
+ connection->End();
+ }
}
void StartCoverageCollection(Environment* env) {
@@ -198,6 +318,26 @@ void StartCoverageCollection(Environment* env) {
env->coverage_connection()->Start();
}
+void StartCpuProfiling(Environment* env, const std::string& profile_path) {
+ std::string path;
+ if (profile_path.empty()) {
+ char cwd[CWD_BUFSIZE];
+ size_t size = CWD_BUFSIZE;
+ int err = uv_cwd(cwd, &size);
+ // TODO(joyeecheung): fallback to exec path / argv[0]
+ CHECK_EQ(err, 0);
+ CHECK_GT(size, 0);
+ DiagnosticFilename filename(env, "CPU", "cpuprofile");
+ path = cwd + std::string(kPathSeparator) + (*filename);
+ } else {
+ path = profile_path;
+ }
+ env->set_cpu_profile_path(std::move(path));
+ env->set_cpu_profiler_connection(
+ std::make_unique<V8CpuProfilerConnection>(env));
+ env->cpu_profiler_connection()->Start();
+}
+
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString());
Environment* env = Environment::GetCurrent(args);