diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2019-03-23 02:39:52 +0300 |
---|---|---|
committer | Joyee Cheung <joyeec9h3@gmail.com> | 2019-04-06 07:01:45 +0300 |
commit | 864860e9f3d4eed0b0b81af55197d7e525ea6306 (patch) | |
tree | ed0c9cbc1cea99323cd4fa4c5385a11b262be8ee /src/inspector_profiler.cc | |
parent | baa54a5ae78ff04a3e8d8ac97c052304a6f6c18c (diff) |
src: port coverage serialization to C++
This patch moves the serialization of coverage profiles into
C++. With this we no longer need to patch `process.reallyExit`
and hook into the exit events, but instead hook into relevant
places in C++ which are safe from user manipulation. This also
makes the code easier to reuse for other types of profiles.
PR-URL: https://github.com/nodejs/node/pull/26874
Reviewed-By: Ben Coe <bencoe@gmail.com>
Diffstat (limited to 'src/inspector_profiler.cc')
-rw-r--r-- | src/inspector_profiler.cc | 301 |
1 files changed, 153 insertions, 148 deletions
diff --git a/src/inspector_profiler.cc b/src/inspector_profiler.cc index 942a1d148a2..27b1db14c92 100644 --- a/src/inspector_profiler.cc +++ b/src/inspector_profiler.cc @@ -1,6 +1,7 @@ +#include "inspector_profiler.h" #include "base_object-inl.h" #include "debug_utils.h" -#include "inspector_agent.h" +#include "node_file.h" #include "node_internals.h" #include "v8-inspector.h" @@ -8,7 +9,6 @@ namespace node { namespace profiler { using v8::Context; -using v8::EscapableHandleScope; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; @@ -17,188 +17,192 @@ using v8::Local; using v8::MaybeLocal; using v8::NewStringType; using v8::Object; -using v8::ObjectTemplate; using v8::String; using v8::Value; using v8_inspector::StringBuffer; using v8_inspector::StringView; +#ifdef __POSIX__ +const char* const kPathSeparator = "/"; +#else +const char* const kPathSeparator = "\\/"; +#endif + std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate, Local<Value> value) { TwoByteValue buffer(isolate, value); return StringBuffer::create(StringView(*buffer, buffer.length())); } -class V8ProfilerConnection : public BaseObject { - public: - class V8ProfilerSessionDelegate - : public inspector::InspectorSessionDelegate { - public: - explicit V8ProfilerSessionDelegate(V8ProfilerConnection* connection) - : connection_(connection) {} - - void SendMessageToFrontend( - const v8_inspector::StringView& message) override { - Environment* env = connection_->env(); - - Local<Function> fn = connection_->GetMessageCallback(); - bool ending = !fn.IsEmpty(); - Debug(env, - DebugCategory::INSPECTOR_PROFILER, - "Sending message to frontend, ending = %s\n", - ending ? "true" : "false"); - if (!ending) { - return; - } - Isolate* isolate = env->isolate(); - - HandleScope handle_scope(isolate); - Context::Scope context_scope(env->context()); - MaybeLocal<String> v8string = - String::NewFromTwoByte(isolate, - message.characters16(), - NewStringType::kNormal, - message.length()); - Local<Value> args[] = {v8string.ToLocalChecked().As<Value>()}; - USE(fn->Call( - env->context(), connection_->object(), arraysize(args), args)); - } - - private: - V8ProfilerConnection* connection_; - }; - - SET_MEMORY_INFO_NAME(V8ProfilerConnection) - SET_SELF_SIZE(V8ProfilerConnection) - - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackFieldWithSize( - "session", sizeof(*session_), "InspectorSession"); - } +V8ProfilerConnection::V8ProfilerConnection(Environment* env) + : session_(env->inspector_agent()->Connect( + std::make_unique<V8ProfilerConnection::V8ProfilerSessionDelegate>( + this), + false)), + env_(env) {} - explicit V8ProfilerConnection(Environment* env, Local<Object> obj) - : BaseObject(env, obj), session_(nullptr) { - inspector::Agent* inspector = env->inspector_agent(); - std::unique_ptr<inspector::InspectorSession> session = inspector->Connect( - std::make_unique<V8ProfilerSessionDelegate>(this), false); - session_ = std::move(session); - MakeWeak(); - } +void V8ProfilerConnection::DispatchMessage(Local<String> message) { + session_->Dispatch(ToProtocolString(env()->isolate(), message)->string()); +} - void DispatchMessage(Isolate* isolate, Local<String> message) { - session_->Dispatch(ToProtocolString(isolate, message)->string()); +bool V8ProfilerConnection::WriteResult(const char* path, Local<String> result) { + int ret = WriteFileSync(env()->isolate(), path, result); + if (ret != 0) { + char err_buf[128]; + uv_err_name_r(ret, err_buf, sizeof(err_buf)); + fprintf(stderr, "%s: Failed to write file %s\n", err_buf, path); + return false; } + Debug( + env(), DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path); + return true; +} - static MaybeLocal<Object> CreateConnectionObject(Environment* env) { - Isolate* isolate = env->isolate(); - Local<Context> context = env->context(); - EscapableHandleScope scope(isolate); - - Local<ObjectTemplate> t = ObjectTemplate::New(isolate); - t->SetInternalFieldCount(1); - Local<Object> obj; - if (!t->NewInstance(context).ToLocal(&obj)) { - return MaybeLocal<Object>(); - } - - obj->SetAlignedPointerInInternalField(0, nullptr); - return scope.Escape(obj); +void V8CoverageConnection::OnMessage(const v8_inspector::StringView& message) { + Debug(env(), + DebugCategory::INSPECTOR_PROFILER, + "Receive coverage message, ending = %s\n", + ending_ ? "true" : "false"); + if (!ending_) { + return; } - - void Start() { - SetConnection(object()); - StartProfiling(); + Isolate* isolate = env()->isolate(); + Local<Context> context = env()->context(); + HandleScope handle_scope(isolate); + Context::Scope context_scope(context); + Local<String> result; + if (!String::NewFromTwoByte(isolate, + message.characters16(), + NewStringType::kNormal, + message.length()) + .ToLocal(&result)) { + fprintf(stderr, "Failed to covert coverage message\n"); } + WriteCoverage(result); +} - void End(Local<Function> callback) { - SetMessageCallback(callback); - EndProfiling(); +bool V8CoverageConnection::WriteCoverage(Local<String> message) { + const std::string& directory = env()->coverage_directory(); + CHECK(!directory.empty()); + 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 coverage directory %s\n", + err_buf, + directory.c_str()); + return false; } - // Override this to return a JS function that gets called with the message - // sent from the session. - virtual Local<Function> GetMessageCallback() = 0; - virtual void SetMessageCallback(Local<Function> callback) = 0; - // Use DispatchMessage() to dispatch necessary inspector messages - virtual void StartProfiling() = 0; - virtual void EndProfiling() = 0; - virtual void SetConnection(Local<Object> connection) = 0; - - private: - std::unique_ptr<inspector::InspectorSession> session_; -}; - -class V8CoverageConnection : public V8ProfilerConnection { - public: - explicit V8CoverageConnection(Environment* env) - : V8ProfilerConnection(env, - CreateConnectionObject(env).ToLocalChecked()) {} - - Local<Function> GetMessageCallback() override { - return env()->on_coverage_message_function(); + std::string thread_id = std::to_string(env()->thread_id()); + std::string pid = std::to_string(uv_os_getpid()); + std::string timestamp = std::to_string( + static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000)); + char filename[1024]; + snprintf(filename, + sizeof(filename), + "coverage-%s-%s-%s.json", + pid.c_str(), + timestamp.c_str(), + thread_id.c_str()); + std::string target = directory + kPathSeparator + filename; + MaybeLocal<String> result = GetResult(message); + if (result.IsEmpty()) { + return false; } + return WriteResult(target.c_str(), result.ToLocalChecked()); +} - void SetMessageCallback(Local<Function> callback) override { - return env()->set_on_coverage_message_function(callback); +MaybeLocal<String> V8CoverageConnection::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 coverage result as JSON object\n"); + return MaybeLocal<String>(); } - static V8ProfilerConnection* GetConnection(Environment* env) { - return Unwrap<V8CoverageConnection>(env->coverage_connection()); + 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 coverage message\n"); + return MaybeLocal<String>(); } - void SetConnection(Local<Object> obj) override { - env()->set_coverage_connection(obj); + if (result_v->IsUndefined()) { + fprintf(stderr, "'result' from coverage message is undefined\n"); + return MaybeLocal<String>(); } - void StartProfiling() override { - Debug(env(), - DebugCategory::INSPECTOR_PROFILER, - "Sending Profiler.startPreciseCoverage\n"); - Isolate* isolate = env()->isolate(); - Local<String> enable = FIXED_ONE_BYTE_STRING( - isolate, "{\"id\": 1, \"method\": \"Profiler.enable\"}"); - Local<String> start = FIXED_ONE_BYTE_STRING( - isolate, - "{" - "\"id\": 2," - "\"method\": \"Profiler.startPreciseCoverage\"," - "\"params\": {\"callCount\": true, \"detailed\": true}" - "}"); - DispatchMessage(isolate, enable); - DispatchMessage(isolate, start); + Local<String> result_s; + if (!v8::JSON::Stringify(context, result_v).ToLocal(&result_s)) { + fprintf(stderr, "Failed to stringify coverage result\n"); + return MaybeLocal<String>(); } - void EndProfiling() override { - Debug(env(), - DebugCategory::INSPECTOR_PROFILER, - "Sending Profiler.takePreciseCoverage\n"); - Isolate* isolate = env()->isolate(); - Local<String> end = - FIXED_ONE_BYTE_STRING(isolate, - "{" - "\"id\": 3," - "\"method\": \"Profiler.takePreciseCoverage\"" - "}"); - DispatchMessage(isolate, end); - } + return result_s; +} - private: - std::unique_ptr<inspector::InspectorSession> session_; -}; +void V8CoverageConnection::Start() { + Debug(env(), + DebugCategory::INSPECTOR_PROFILER, + "Sending Profiler.startPreciseCoverage\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.startPreciseCoverage", + "params": { "callCount": true, "detailed": true } + })"); + DispatchMessage(enable); + DispatchMessage(start); +} + +void V8CoverageConnection::End() { + CHECK_EQ(ending_, false); + ending_ = true; + Debug(env(), + DebugCategory::INSPECTOR_PROFILER, + "Sending Profiler.takePreciseCoverage\n"); + Isolate* isolate = env()->isolate(); + HandleScope scope(isolate); + Local<String> end = FIXED_ONE_BYTE_STRING(isolate, R"({ + "id": 3, + "method": "Profiler.takePreciseCoverage" + })"); + DispatchMessage(end); +} + +// For now, we only support coverage profiling, but we may add more +// in the future. +void EndStartedProfilers(Environment* env) { + Debug(env, DebugCategory::INSPECTOR_PROFILER, "EndStartedProfilers\n"); + V8ProfilerConnection* connection = env->coverage_connection(); + if (connection != nullptr && !connection->ending()) { + Debug( + env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n"); + connection->End(); + } +} void StartCoverageCollection(Environment* env) { - V8CoverageConnection* connection = new V8CoverageConnection(env); - connection->Start(); + CHECK_NULL(env->coverage_connection()); + env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env)); + env->coverage_connection()->Start(); } -static void EndCoverageCollection(const FunctionCallbackInfo<Value>& args) { +static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) { + CHECK(args[0]->IsString()); Environment* env = Environment::GetCurrent(args); - CHECK(args[0]->IsFunction()); - Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n"); - V8ProfilerConnection* connection = V8CoverageConnection::GetConnection(env); - CHECK_NOT_NULL(connection); - connection->End(args[0].As<Function>()); + node::Utf8Value directory(env->isolate(), args[0].As<String>()); + env->set_coverage_directory(*directory); } static void Initialize(Local<Object> target, @@ -206,8 +210,9 @@ static void Initialize(Local<Object> target, Local<Context> context, void* priv) { Environment* env = Environment::GetCurrent(context); - env->SetMethod(target, "endCoverage", EndCoverageCollection); + env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory); } + } // namespace profiler } // namespace node |