| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 11eca36 commit 15ffed5
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -214,6 +214,21 @@ v8.setFlagsFromString('--trace_gc'); | |||
| 214 | 214 | setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3); | |
| 215 | 215 | ``` | |
| 216 | 216 | ||
| 217 | + ## `v8.takeCoverage()` | ||
| 218 | + | ||
| 219 | + <!-- YAML | ||
| 220 | + added: REPLACEME | ||
| 221 | + --> | ||
| 222 | + | ||
| 223 | + The `v8.takeCoverage()` method allows the user to write the coverage started by | ||
| 224 | + [`NODE_V8_COVERAGE`][] to disk on demand. This method can be invoked multiple | ||
| 225 | + times during the lifetime of the process, each time the execution counter will | ||
| 226 | + be reset and a new coverage report will be written to the directory specified | ||
| 227 | + by [`NODE_V8_COVERAGE`][]. | ||
| 228 | + | ||
| 229 | + When the process is about to exit, one last coverage will still be written to | ||
| 230 | + disk. | ||
| 231 | + | ||
| 217 | 232 | ## `v8.writeHeapSnapshot([filename])` | |
| 218 | 233 | <!-- YAML | |
| 219 | 234 | added: v11.13.0 | |
@@ -511,6 +526,7 @@ A subclass of [`Deserializer`][] corresponding to the format written by | |||
| 511 | 526 | [`Deserializer`]: #v8_class_v8_deserializer | |
| 512 | 527 | [`Error`]: errors.md#errors_class_error | |
| 513 | 528 | [`GetHeapSpaceStatistics`]: https://v8docs.nodesource.com/node-13.2/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4 | |
| 529 | + [`NODE_V8_COVERAGE`]: cli.html#cli_node_v8_coverage_dir | ||
| 514 | 530 | [`Serializer`]: #v8_class_v8_serializer | |
| 515 | 531 | [`deserializer._readHostObject()`]: #v8_deserializer_readhostobject | |
| 516 | 532 | [`deserializer.transferArrayBuffer()`]: #v8_deserializer_transferarraybuffer_id_arraybuffer | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,6 +37,12 @@ const { | |||
| 37 | 37 | Serializer: _Serializer, | |
| 38 | 38 | Deserializer: _Deserializer | |
| 39 | 39 | } = internalBinding('serdes'); | |
| 40 | + | ||
| 41 | + let profiler = {}; | ||
| 42 | + if (internalBinding('config').hasInspector) { | ||
| 43 | + profiler = internalBinding('profiler'); | ||
| 44 | + } | ||
| 45 | + | ||
| 40 | 46 | const assert = require('internal/assert'); | |
| 41 | 47 | const { copy } = internalBinding('buffer'); | |
| 42 | 48 | const { inspect } = require('internal/util/inspect'); | |
@@ -275,6 +281,7 @@ module.exports = { | |||
| 275 | 281 | DefaultSerializer, | |
| 276 | 282 | DefaultDeserializer, | |
| 277 | 283 | deserialize, | |
| 284 | + takeCoverage: profiler.takeCoverage, | ||
| 278 | 285 | serialize, | |
| 279 | 286 | writeHeapSnapshot, | |
| 280 | 287 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ | |||
| 9 | 9 | #include "util-inl.h" | |
| 10 | 10 | #include "v8-inspector.h" | |
| 11 | 11 | ||
| 12 | + #include <cinttypes> | ||
| 12 | 13 | #include <sstream> | |
| 13 | 14 | ||
| 14 | 15 | namespace node { | |
@@ -36,10 +37,11 @@ V8ProfilerConnection::V8ProfilerConnection(Environment* env) | |||
| 36 | 37 | false)), | |
| 37 | 38 | env_(env) {} | |
| 38 | 39 | ||
| 39 | - size_t V8ProfilerConnection::DispatchMessage(const char* method, | ||
| 40 | - const char* params) { | ||
| 40 | + uint32_t V8ProfilerConnection::DispatchMessage(const char* method, | ||
| 41 | + const char* params, | ||
| 42 | + bool is_profile_request) { | ||
| 41 | 43 | std::stringstream ss; | |
| 42 | - size_t id = next_id(); | ||
| 44 | + uint32_t id = next_id(); | ||
| 43 | 45 | ss << R"({ "id": )" << id; | |
| 44 | 46 | DCHECK(method != nullptr); | |
| 45 | 47 | ss << R"(, "method": ")" << method << '"'; | |
@@ -50,12 +52,15 @@ size_t V8ProfilerConnection::DispatchMessage(const char* method, | |||
| 50 | 52 | std::string message = ss.str(); | |
| 51 | 53 | const uint8_t* message_data = | |
| 52 | 54 | reinterpret_cast<const uint8_t*>(message.c_str()); | |
| 55 | + // Save the id of the profile request to identify its response. | ||
| 56 | + if (is_profile_request) { | ||
| 57 | + profile_ids_.insert(id); | ||
| 58 | + } | ||
| 53 | 59 | Debug(env(), | |
| 54 | 60 | DebugCategory::INSPECTOR_PROFILER, | |
| 55 | 61 | "Dispatching message %s\n", | |
| 56 | 62 | message.c_str()); | |
| 57 | 63 | session_->Dispatch(StringView(message_data, message.length())); | |
| 58 | - // TODO(joyeecheung): use this to identify the ending message. | ||
| 59 | 64 | return id; | |
| 60 | 65 | } | |
| 61 | 66 | ||
@@ -77,33 +82,73 @@ void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend( | |||
| 77 | 82 | Environment* env = connection_->env(); | |
| 78 | 83 | Isolate* isolate = env->isolate(); | |
| 79 | 84 | HandleScope handle_scope(isolate); | |
| 80 | - Context::Scope context_scope(env->context()); | ||
| 85 | + Local<Context> context = env->context(); | ||
| 86 | + Context::Scope context_scope(context); | ||
| 81 | 87 | ||
| 82 | - // TODO(joyeecheung): always parse the message so that we can use the id to | ||
| 83 | - // identify ending messages as well as printing the message in the debug | ||
| 84 | - // output when there is an error. | ||
| 85 | 88 | const char* type = connection_->type(); | |
| 86 | - Debug(env, | ||
| 87 | - DebugCategory::INSPECTOR_PROFILER, | ||
| 88 | - "Receive %s profile message, ending = %s\n", | ||
| 89 | - type, | ||
| 90 | - connection_->ending() ? "true" : "false"); | ||
| 91 | - if (!connection_->ending()) { | ||
| 92 | - return; | ||
| 93 | - } | ||
| 94 | - | ||
| 95 | 89 | // Convert StringView to a Local<String>. | |
| 96 | 90 | Local<String> message_str; | |
| 97 | 91 | if (!String::NewFromTwoByte(isolate, | |
| 98 | 92 | message.characters16(), | |
| 99 | 93 | NewStringType::kNormal, | |
| 100 | 94 | message.length()) | |
| 101 | 95 | .ToLocal(&message_str)) { | |
| 102 | - fprintf(stderr, "Failed to convert %s profile message\n", type); | ||
| 96 | + fprintf( | ||
| 97 | + stderr, "Failed to convert %s profile message to V8 string\n", type); | ||
| 98 | + return; | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + Debug(env, | ||
| 102 | + DebugCategory::INSPECTOR_PROFILER, | ||
| 103 | + "Receive %s profile message\n", | ||
| 104 | + type); | ||
| 105 | + | ||
| 106 | + Local<Value> parsed; | ||
| 107 | + if (!v8::JSON::Parse(context, message_str).ToLocal(&parsed) || | ||
| 108 | + !parsed->IsObject()) { | ||
| 109 | + fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type); | ||
| 103 | 110 | return; | |
| 104 | 111 | } | |
| 105 | 112 | ||
| 106 | - connection_->WriteProfile(message_str); | ||
| 113 | + Local<Object> response = parsed.As<Object>(); | ||
| 114 | + Local<Value> id_v; | ||
| 115 | + if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "id")) | ||
| 116 | + .ToLocal(&id_v) || | ||
| 117 | + !id_v->IsUint32()) { | ||
| 118 | + Utf8Value str(isolate, message_str); | ||
| 119 | + fprintf( | ||
| 120 | + stderr, "Cannot retrieve id from the response message:\n%s\n", *str); | ||
| 121 | + return; | ||
| 122 | + } | ||
| 123 | + uint32_t id = id_v.As<v8::Uint32>()->Value(); | ||
| 124 | + | ||
| 125 | + if (!connection_->HasProfileId(id)) { | ||
| 126 | + Utf8Value str(isolate, message_str); | ||
| 127 | + Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str); | ||
| 128 | + return; | ||
| 129 | + } else { | ||
| 130 | + Debug(env, | ||
| 131 | + DebugCategory::INSPECTOR_PROFILER, | ||
| 132 | + "Writing profile response (id = %" PRIu64 ")\n", | ||
| 133 | + static_cast<uint64_t>(id)); | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + // Get message.result from the response. | ||
| 137 | + Local<Value> result_v; | ||
| 138 | + if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result")) | ||
| 139 | + .ToLocal(&result_v)) { | ||
| 140 | + fprintf(stderr, "Failed to get 'result' from %s profile response\n", type); | ||
| 141 | + return; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + if (!result_v->IsObject()) { | ||
| 145 | + fprintf( | ||
| 146 | + stderr, "'result' from %s profile response is not an object\n", type); | ||
| 147 | + return; | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + connection_->WriteProfile(result_v.As<Object>()); | ||
| 151 | + connection_->RemoveProfileId(id); | ||
| 107 | 152 | } | |
| 108 | 153 | ||
| 109 | 154 | static bool EnsureDirectory(const std::string& directory, const char* type) { | |
@@ -138,45 +183,9 @@ std::string V8CoverageConnection::GetFilename() const { | |||
| 138 | 183 | return filename; | |
| 139 | 184 | } | |
| 140 | 185 | ||
| 141 | - static MaybeLocal<Object> ParseProfile(Environment* env, | ||
| 142 | - Local<String> message, | ||
| 143 | - const char* type) { | ||
| 144 | - Local<Context> context = env->context(); | ||
| 145 | - Isolate* isolate = env->isolate(); | ||
| 146 | - | ||
| 147 | - // Get message.result from the response | ||
| 148 | - Local<Value> parsed; | ||
| 149 | - if (!v8::JSON::Parse(context, message).ToLocal(&parsed) || | ||
| 150 | - !parsed->IsObject()) { | ||
| 151 | - fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type); | ||
| 152 | - return MaybeLocal<Object>(); | ||
| 153 | - } | ||
| 154 | - | ||
| 155 | - Local<Value> result_v; | ||
| 156 | - if (!parsed.As<Object>() | ||
| 157 | - ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result")) | ||
| 158 | - .ToLocal(&result_v)) { | ||
| 159 | - fprintf(stderr, "Failed to get 'result' from %s profile message\n", type); | ||
| 160 | - return MaybeLocal<Object>(); | ||
| 161 | - } | ||
| 162 | - | ||
| 163 | - if (!result_v->IsObject()) { | ||
| 164 | - fprintf( | ||
| 165 | - stderr, "'result' from %s profile message is not an object\n", type); | ||
| 166 | - return MaybeLocal<Object>(); | ||
| 167 | - } | ||
| 168 | - | ||
| 169 | - return result_v.As<Object>(); | ||
| 170 | - } | ||
| 171 | - | ||
| 172 | - void V8ProfilerConnection::WriteProfile(Local<String> message) { | ||
| 186 | + void V8ProfilerConnection::WriteProfile(Local<Object> result) { | ||
| 173 | 187 | Local<Context> context = env_->context(); | |
| 174 | 188 | ||
| 175 | - // Get message.result from the response. | ||
| 176 | - Local<Object> result; | ||
| 177 | - if (!ParseProfile(env_, message, type()).ToLocal(&result)) { | ||
| 178 | - return; | ||
| 179 | - } | ||
| 180 | 189 | // Generate the profile output from the subclass. | |
| 181 | 190 | Local<Object> profile; | |
| 182 | 191 | if (!GetProfile(result).ToLocal(&profile)) { | |
@@ -203,7 +212,7 @@ void V8ProfilerConnection::WriteProfile(Local<String> message) { | |||
| 203 | 212 | WriteResult(env_, path.c_str(), result_s); | |
| 204 | 213 | } | |
| 205 | 214 | ||
| 206 | - void V8CoverageConnection::WriteProfile(Local<String> message) { | ||
| 215 | + void V8CoverageConnection::WriteProfile(Local<Object> result) { | ||
| 207 | 216 | Isolate* isolate = env_->isolate(); | |
| 208 | 217 | Local<Context> context = env_->context(); | |
| 209 | 218 | HandleScope handle_scope(isolate); | |
@@ -219,11 +228,6 @@ void V8CoverageConnection::WriteProfile(Local<String> message) { | |||
| 219 | 228 | return; | |
| 220 | 229 | } | |
| 221 | 230 | ||
| 222 | - // Get message.result from the response. | ||
| 223 | - Local<Object> result; | ||
| 224 | - if (!ParseProfile(env_, message, type()).ToLocal(&result)) { | ||
| 225 | - return; | ||
| 226 | - } | ||
| 227 | 231 | // Generate the profile output from the subclass. | |
| 228 | 232 | Local<Object> profile; | |
| 229 | 233 | if (!GetProfile(result).ToLocal(&profile)) { | |
@@ -287,10 +291,19 @@ void V8CoverageConnection::Start() { | |||
| 287 | 291 | R"({ "callCount": true, "detailed": true })"); | |
| 288 | 292 | } | |
| 289 | 293 | ||
| 294 | + void V8CoverageConnection::TakeCoverage() { | ||
| 295 | + DispatchMessage("Profiler.takePreciseCoverage", nullptr, true); | ||
| 296 | + } | ||
| 297 | + | ||
| 290 | 298 | void V8CoverageConnection::End() { | |
| 291 | - CHECK_EQ(ending_, false); | ||
| 299 | + Debug(env_, | ||
| 300 | + DebugCategory::INSPECTOR_PROFILER, | ||
| 301 | + "V8CoverageConnection::End(), ending = %d\n", ending_); | ||
| 302 | + if (ending_) { | ||
| 303 | + return; | ||
| 304 | + } | ||
| 292 | 305 | ending_ = true; | |
| 293 | - DispatchMessage("Profiler.takePreciseCoverage"); | ||
| 306 | + TakeCoverage(); | ||
| 294 | 307 | } | |
| 295 | 308 | ||
| 296 | 309 | std::string V8CpuProfilerConnection::GetDirectory() const { | |
@@ -327,9 +340,14 @@ void V8CpuProfilerConnection::Start() { | |||
| 327 | 340 | } | |
| 328 | 341 | ||
| 329 | 342 | void V8CpuProfilerConnection::End() { | |
| 330 | - CHECK_EQ(ending_, false); | ||
| 343 | + Debug(env_, | ||
| 344 | + DebugCategory::INSPECTOR_PROFILER, | ||
| 345 | + "V8CpuProfilerConnection::End(), ending = %d\n", ending_); | ||
| 346 | + if (ending_) { | ||
| 347 | + return; | ||
| 348 | + } | ||
| 331 | 349 | ending_ = true; | |
| 332 | - DispatchMessage("Profiler.stop"); | ||
| 350 | + DispatchMessage("Profiler.stop", nullptr, true); | ||
| 333 | 351 | } | |
| 334 | 352 | ||
| 335 | 353 | std::string V8HeapProfilerConnection::GetDirectory() const { | |
@@ -365,31 +383,33 @@ void V8HeapProfilerConnection::Start() { | |||
| 365 | 383 | } | |
| 366 | 384 | ||
| 367 | 385 | void V8HeapProfilerConnection::End() { | |
| 368 | - CHECK_EQ(ending_, false); | ||
| 386 | + Debug(env_, | ||
| 387 | + DebugCategory::INSPECTOR_PROFILER, | ||
| 388 | + "V8HeapProfilerConnection::End(), ending = %d\n", ending_); | ||
| 389 | + if (ending_) { | ||
| 390 | + return; | ||
| 391 | + } | ||
| 369 | 392 | ending_ = true; | |
| 370 | - DispatchMessage("HeapProfiler.stopSampling"); | ||
| 393 | + DispatchMessage("HeapProfiler.stopSampling", nullptr, true); | ||
| 371 | 394 | } | |
| 372 | 395 | ||
| 373 | 396 | // For now, we only support coverage profiling, but we may add more | |
| 374 | 397 | // in the future. | |
| 375 | 398 | static void EndStartedProfilers(Environment* env) { | |
| 399 | + // TODO(joyeechueng): merge these connections and use one session per env. | ||
| 376 | 400 | Debug(env, DebugCategory::INSPECTOR_PROFILER, "EndStartedProfilers\n"); | |
| 377 | 401 | V8ProfilerConnection* connection = env->cpu_profiler_connection(); | |
| 378 | - if (connection != nullptr && !connection->ending()) { | ||
| 379 | - Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending cpu profiling\n"); | ||
| 402 | + if (connection != nullptr) { | ||
| 380 | 403 | connection->End(); | |
| 381 | 404 | } | |
| 382 | 405 | ||
| 383 | 406 | connection = env->heap_profiler_connection(); | |
| 384 | - if (connection != nullptr && !connection->ending()) { | ||
| 385 | - Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending heap profiling\n"); | ||
| 407 | + if (connection != nullptr) { | ||
| 386 | 408 | connection->End(); | |
| 387 | 409 | } | |
| 388 | 410 | ||
| 389 | 411 | connection = env->coverage_connection(); | |
| 390 | - if (connection != nullptr && !connection->ending()) { | ||
| 391 | - Debug( | ||
| 392 | - env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n"); | ||
| 412 | + if (connection != nullptr) { | ||
| 393 | 413 | connection->End(); | |
| 394 | 414 | } | |
| 395 | 415 | } | |
@@ -453,13 +473,30 @@ static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) { | |||
| 453 | 473 | env->set_source_map_cache_getter(args[0].As<Function>()); | |
| 454 | 474 | } | |
| 455 | 475 | ||
| 476 | + static void TakeCoverage(const FunctionCallbackInfo<Value>& args) { | ||
| 477 | + Environment* env = Environment::GetCurrent(args); | ||
| 478 | + V8CoverageConnection* connection = env->coverage_connection(); | ||
| 479 | + | ||
| 480 | + Debug( | ||
| 481 | + env, | ||
| 482 | + DebugCategory::INSPECTOR_PROFILER, | ||
| 483 | + "TakeCoverage, connection %s nullptr\n", | ||
| 484 | + connection == nullptr ? "==" : "!="); | ||
| 485 | + | ||
| 486 | + if (connection != nullptr) { | ||
| 487 | + Debug(env, DebugCategory::INSPECTOR_PROFILER, "taking coverage\n"); | ||
| 488 | + connection->TakeCoverage(); | ||
| 489 | + } | ||
| 490 | + } | ||
| 491 | + | ||
| 456 | 492 | static void Initialize(Local<Object> target, | |
| 457 | 493 | Local<Value> unused, | |
| 458 | 494 | Local<Context> context, | |
| 459 | 495 | void* priv) { | |
| 460 | 496 | Environment* env = Environment::GetCurrent(context); | |
| 461 | 497 | env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory); | |
| 462 | 498 | env->SetMethod(target, "setSourceMapCacheGetter", SetSourceMapCacheGetter); | |
| 499 | + env->SetMethod(target, "takeCoverage", TakeCoverage); | ||
| 463 | 500 | } | |
| 464 | 501 | ||
| 465 | 502 | } // namespace profiler | |
| Back | FazBrowse Home | New Git URL |
0 commit comments