GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: inspector_profiler.cc Lines: 254 287 88.5 %
Date: 2021-09-21 04:13:38 Branches: 73 112 65.2 %

Line Branch Exec Source
1
#include "inspector_profiler.h"
2
#include "base_object-inl.h"
3
#include "debug_utils-inl.h"
4
#include "diagnosticfilename-inl.h"
5
#include "memory_tracker-inl.h"
6
#include "node_errors.h"
7
#include "node_external_reference.h"
8
#include "node_file.h"
9
#include "node_internals.h"
10
#include "util-inl.h"
11
#include "v8-inspector.h"
12
13
#include <cinttypes>
14
#include <sstream>
15
16
namespace node {
17
namespace profiler {
18
19
using errors::TryCatchScope;
20
using v8::Context;
21
using v8::Function;
22
using v8::FunctionCallbackInfo;
23
using v8::HandleScope;
24
using v8::Isolate;
25
using v8::Local;
26
using v8::MaybeLocal;
27
using v8::NewStringType;
28
using v8::Object;
29
using v8::String;
30
using v8::Value;
31
32
using v8_inspector::StringView;
33
34
5407
V8ProfilerConnection::V8ProfilerConnection(Environment* env)
35
    : session_(env->inspector_agent()->Connect(
36
5407
          std::make_unique<V8ProfilerConnection::V8ProfilerSessionDelegate>(
37
              this),
38
          false)),
39
10814
      env_(env) {}
40
41
16228
uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
42
                                               const char* params,
43
                                               bool is_profile_request) {
44
32456
  std::stringstream ss;
45
16228
  uint32_t id = next_id();
46
16228
  ss << R"({ "id": )" << id;
47
  DCHECK(method != nullptr);
48
16228
  ss << R"(, "method": ")" << method << '"';
49
16228
  if (params != nullptr) {
50
5407
    ss << R"(, "params": )" << params;
51
  }
52
16228
  ss << " }";
53
16228
  std::string message = ss.str();
54
  const uint8_t* message_data =
55
16228
      reinterpret_cast<const uint8_t*>(message.c_str());
56
  // Save the id of the profile request to identify its response.
57
16228
  if (is_profile_request) {
58
5401
    profile_ids_.insert(id);
59
  }
60
16228
  Debug(env(),
61
        DebugCategory::INSPECTOR_PROFILER,
62
        "Dispatching message %s\n",
63
16228
        message.c_str());
64
16228
  session_->Dispatch(StringView(message_data, message.length()));
65
16228
  return id;
66
}
67
68
5368
static void WriteResult(Environment* env,
69
                        const char* path,
70
                        Local<String> result) {
71
5368
  int ret = WriteFileSync(env->isolate(), path, result);
72
5368
  if (ret != 0) {
73
    char err_buf[128];
74
10
    uv_err_name_r(ret, err_buf, sizeof(err_buf));
75
10
    fprintf(stderr, "%s: Failed to write file %s\n", err_buf, path);
76
10
    return;
77
  }
78
  Debug(env, DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path);
79
}
80
81
16228
void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
82
    const v8_inspector::StringView& message) {
83
16228
  Environment* env = connection_->env();
84
16228
  Isolate* isolate = env->isolate();
85
16228
  HandleScope handle_scope(isolate);
86
16228
  Local<Context> context = env->context();
87
16228
  Context::Scope context_scope(context);
88
89
16228
  const char* type = connection_->type();
90
  // Convert StringView to a Local<String>.
91
  Local<String> message_str;
92
16228
  if (!String::NewFromTwoByte(isolate,
93
                              message.characters16(),
94
                              NewStringType::kNormal,
95
16228
                              message.length())
96
16228
           .ToLocal(&message_str)) {
97
    fprintf(
98
        stderr, "Failed to convert %s profile message to V8 string\n", type);
99
    return;
100
  }
101
102
  Debug(env,
103
        DebugCategory::INSPECTOR_PROFILER,
104
        "Receive %s profile message\n",
105
        type);
106
107
  Local<Value> parsed;
108

48684
  if (!v8::JSON::Parse(context, message_str).ToLocal(&parsed) ||
109
16228
      !parsed->IsObject()) {
110
    fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type);
111
    return;
112
  }
113
114
16228
  Local<Object> response = parsed.As<Object>();
115
  Local<Value> id_v;
116
32456
  if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "id"))
117

32456
           .ToLocal(&id_v) ||
118
16228
      !id_v->IsUint32()) {
119
    Utf8Value str(isolate, message_str);
120
    fprintf(
121
        stderr, "Cannot retrieve id from the response message:\n%s\n", *str);
122
    return;
123
  }
124
16228
  uint32_t id = id_v.As<v8::Uint32>()->Value();
125
126
16228
  if (!connection_->HasProfileId(id)) {
127
10827
    Utf8Value str(isolate, message_str);
128
10827
    Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str);
129
10827
    return;
130
  } else {
131
    Debug(env,
132
          DebugCategory::INSPECTOR_PROFILER,
133
          "Writing profile response (id = %" PRIu64 ")\n",
134
10802
          static_cast<uint64_t>(id));
135
  }
136
137
  // Get message.result from the response.
138
  Local<Value> result_v;
139
10802
  if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
140
5401
           .ToLocal(&result_v)) {
141
    fprintf(stderr, "Failed to get 'result' from %s profile response\n", type);
142
    return;
143
  }
144
145
5401
  if (!result_v->IsObject()) {
146
3
    fprintf(
147
        stderr, "'result' from %s profile response is not an object\n", type);
148
3
    return;
149
  }
150
151
10796
  connection_->WriteProfile(result_v.As<Object>());
152
5398
  connection_->RemoveProfileId(id);
153
}
154
155
5368
static bool EnsureDirectory(const std::string& directory, const char* type) {
156
10736
  fs::FSReqWrapSync req_wrap_sync;
157
5368
  int ret = fs::MKDirpSync(nullptr, &req_wrap_sync.req, directory, 0777,
158
                           nullptr);
159

5368
  if (ret < 0 && ret != UV_EEXIST) {
160
    char err_buf[128];
161
    uv_err_name_r(ret, err_buf, sizeof(err_buf));
162
    fprintf(stderr,
163
            "%s: Failed to create %s profile directory %s\n",
164
            err_buf,
165
            type,
166
            directory.c_str());
167
    return false;
168
  }
169
5368
  return true;
170
}
171
172
5350
std::string V8CoverageConnection::GetFilename() const {
173
10700
  std::string thread_id = std::to_string(env()->thread_id());
174
10700
  std::string pid = std::to_string(uv_os_getpid());
175
  std::string timestamp = std::to_string(
176
5350
      static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000));
177
  char filename[1024];
178
5350
  snprintf(filename,
179
           sizeof(filename),
180
           "coverage-%s-%s-%s.json",
181
           pid.c_str(),
182
           timestamp.c_str(),
183
           thread_id.c_str());
184
5350
  return filename;
185
}
186
187
18
void V8ProfilerConnection::WriteProfile(Local<Object> result) {
188
18
  Local<Context> context = env_->context();
189
190
  // Generate the profile output from the subclass.
191
  Local<Object> profile;
192
36
  if (!GetProfile(result).ToLocal(&profile)) {
193
    return;
194
  }
195
196
  Local<String> result_s;
197
36
  if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
198
    fprintf(stderr, "Failed to stringify %s profile result\n", type());
199
    return;
200
  }
201
202
  // Create the directory if necessary.
203
18
  std::string directory = GetDirectory();
204
  DCHECK(!directory.empty());
205
18
  if (!EnsureDirectory(directory, type())) {
206
    return;
207
  }
208
209
36
  std::string filename = GetFilename();
210
  DCHECK(!filename.empty());
211
36
  std::string path = directory + kPathSeparator + filename;
212
213
18
  WriteResult(env_, path.c_str(), result_s);
214
}
215
216
5380
void V8CoverageConnection::WriteProfile(Local<Object> result) {
217
5380
  Isolate* isolate = env_->isolate();
218
5380
  Local<Context> context = env_->context();
219
5380
  HandleScope handle_scope(isolate);
220
5380
  Context::Scope context_scope(context);
221
222
  // This is only set up during pre-execution (when the environment variables
223
  // becomes available in the JS land). If it's empty, we don't have coverage
224
  // directory path (which is resolved in JS land at the moment) either, so
225
  // the best we could to is to just discard the profile and do nothing.
226
  // This should only happen in half-baked Environments created using the
227
  // embedder API.
228
10760
  if (env_->source_map_cache_getter().IsEmpty()) {
229
28
    return;
230
  }
231
232
  // Generate the profile output from the subclass.
233
  Local<Object> profile;
234
10704
  if (!GetProfile(result).ToLocal(&profile)) {
235
    return;
236
  }
237
238
  // append source-map cache information to coverage object:
239
  Local<Value> source_map_cache_v;
240
  {
241
5352
    TryCatchScope try_catch(env());
242
    {
243
5352
      Isolate::AllowJavascriptExecutionScope allow_js_here(isolate);
244
5352
      Local<Function> source_map_cache_getter = env_->source_map_cache_getter();
245
10704
      if (!source_map_cache_getter->Call(
246
10704
              context, Undefined(isolate), 0, nullptr)
247
5352
              .ToLocal(&source_map_cache_v)) {
248
2
        return;
249
      }
250
    }
251

5350
    if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
252
      PrintCaughtException(isolate, context, try_catch);
253
    }
254
  }
255
  // Avoid writing to disk if no source-map data:
256
10700
  if (!source_map_cache_v->IsUndefined()) {
257
31
    profile->Set(context, FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
258
93
                source_map_cache_v).ToChecked();
259
  }
260
261
  Local<String> result_s;
262
10700
  if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
263
    fprintf(stderr, "Failed to stringify %s profile result\n", type());
264
    return;
265
  }
266
267
  // Create the directory if necessary.
268
5350
  std::string directory = GetDirectory();
269
  DCHECK(!directory.empty());
270
5350
  if (!EnsureDirectory(directory, type())) {
271
    return;
272
  }
273
274
10700
  std::string filename = GetFilename();
275
  DCHECK(!filename.empty());
276
10700
  std::string path = directory + kPathSeparator + filename;
277
278
5350
  WriteResult(env_, path.c_str(), result_s);
279
}
280
281
5352
MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
282
5352
  return result;
283
}
284
285
5350
std::string V8CoverageConnection::GetDirectory() const {
286
5350
  return env()->coverage_directory();
287
}
288
289
5389
void V8CoverageConnection::Start() {
290
5389
  DispatchMessage("Profiler.enable");
291
5389
  DispatchMessage("Profiler.startPreciseCoverage",
292
                  R"({ "callCount": true, "detailed": true })");
293
5389
}
294
295
5383
void V8CoverageConnection::TakeCoverage() {
296
5383
  DispatchMessage("Profiler.takePreciseCoverage", nullptr, true);
297
5383
}
298
299
1
void V8CoverageConnection::StopCoverage() {
300
1
  DispatchMessage("Profiler.stopPreciseCoverage");
301
1
}
302
303
5377
void V8CoverageConnection::End() {
304
5377
  Debug(env_,
305
      DebugCategory::INSPECTOR_PROFILER,
306
5377
      "V8CoverageConnection::End(), ending = %d\n", ending_);
307
5377
  if (ending_) {
308
    return;
309
  }
310
5377
  ending_ = true;
311
5377
  TakeCoverage();
312
}
313
314
12
std::string V8CpuProfilerConnection::GetDirectory() const {
315
12
  return env()->cpu_prof_dir();
316
}
317
318
12
std::string V8CpuProfilerConnection::GetFilename() const {
319
12
  return env()->cpu_prof_name();
320
}
321
322
12
MaybeLocal<Object> V8CpuProfilerConnection::GetProfile(Local<Object> result) {
323
  Local<Value> profile_v;
324
24
  if (!result
325
12
           ->Get(env()->context(),
326
24
                 FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
327
12
           .ToLocal(&profile_v)) {
328
    fprintf(stderr, "'profile' from CPU profile result is undefined\n");
329
    return MaybeLocal<Object>();
330
  }
331
12
  if (!profile_v->IsObject()) {
332
    fprintf(stderr, "'profile' from CPU profile result is not an Object\n");
333
    return MaybeLocal<Object>();
334
  }
335
12
  return profile_v.As<Object>();
336
}
337
338
12
void V8CpuProfilerConnection::Start() {
339
12
  DispatchMessage("Profiler.enable");
340
12
  DispatchMessage("Profiler.start");
341
24
  std::string params = R"({ "interval": )";
342
12
  params += std::to_string(env()->cpu_prof_interval());
343
12
  params += " }";
344
12
  DispatchMessage("Profiler.setSamplingInterval", params.c_str());
345
12
}
346
347
12
void V8CpuProfilerConnection::End() {
348
12
  Debug(env_,
349
      DebugCategory::INSPECTOR_PROFILER,
350
12
      "V8CpuProfilerConnection::End(), ending = %d\n", ending_);
351
12
  if (ending_) {
352
    return;
353
  }
354
12
  ending_ = true;
355
12
  DispatchMessage("Profiler.stop", nullptr, true);
356
}
357
358
6
std::string V8HeapProfilerConnection::GetDirectory() const {
359
6
  return env()->heap_prof_dir();
360
}
361
362
6
std::string V8HeapProfilerConnection::GetFilename() const {
363
6
  return env()->heap_prof_name();
364
}
365
366
6
MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
367
  Local<Value> profile_v;
368
12
  if (!result
369
6
           ->Get(env()->context(),
370
12
                 FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
371
6
           .ToLocal(&profile_v)) {
372
    fprintf(stderr, "'profile' from heap profile result is undefined\n");
373
    return MaybeLocal<Object>();
374
  }
375
6
  if (!profile_v->IsObject()) {
376
    fprintf(stderr, "'profile' from heap profile result is not an Object\n");
377
    return MaybeLocal<Object>();
378
  }
379
6
  return profile_v.As<Object>();
380
}
381
382
6
void V8HeapProfilerConnection::Start() {
383
6
  DispatchMessage("HeapProfiler.enable");
384
12
  std::string params = R"({ "samplingInterval": )";
385
6
  params += std::to_string(env()->heap_prof_interval());
386
6
  params += " }";
387
6
  DispatchMessage("HeapProfiler.startSampling", params.c_str());
388
6
}
389
390
6
void V8HeapProfilerConnection::End() {
391
6
  Debug(env_,
392
      DebugCategory::INSPECTOR_PROFILER,
393
6
      "V8HeapProfilerConnection::End(), ending = %d\n", ending_);
394
6
  if (ending_) {
395
    return;
396
  }
397
6
  ending_ = true;
398
6
  DispatchMessage("HeapProfiler.stopSampling", nullptr, true);
399
}
400
401
// For now, we only support coverage profiling, but we may add more
402
// in the future.
403
5380
static void EndStartedProfilers(Environment* env) {
404
  // TODO(joyeechueng): merge these connections and use one session per env.
405
  Debug(env, DebugCategory::INSPECTOR_PROFILER, "EndStartedProfilers\n");
406
5380
  V8ProfilerConnection* connection = env->cpu_profiler_connection();
407
5380
  if (connection != nullptr) {
408
12
    connection->End();
409
  }
410
411
5380
  connection = env->heap_profiler_connection();
412
5380
  if (connection != nullptr) {
413
6
    connection->End();
414
  }
415
416
5380
  connection = env->coverage_connection();
417
5380
  if (connection != nullptr) {
418
5377
    connection->End();
419
  }
420
5380
}
421
422
5392
void StartProfilers(Environment* env) {
423
5392
  AtExit(env, [](void* env) {
424
5380
    EndStartedProfilers(static_cast<Environment*>(env));
425
5380
  }, env);
426
427
5392
  Isolate* isolate = env->isolate();
428
10784
  Local<String> coverage_str = env->env_vars()->Get(
429
5392
      isolate, FIXED_ONE_BYTE_STRING(isolate, "NODE_V8_COVERAGE"))
430
5392
      .FromMaybe(Local<String>());
431

10783
  if (!coverage_str.IsEmpty() && coverage_str->Length() > 0) {
432
5389
    CHECK_NULL(env->coverage_connection());
433
5389
    env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
434
5389
    env->coverage_connection()->Start();
435
  }
436
5392
  if (env->options()->cpu_prof) {
437
12
    const std::string& dir = env->options()->cpu_prof_dir;
438
12
    env->set_cpu_prof_interval(env->options()->cpu_prof_interval);
439
12
    env->set_cpu_prof_dir(dir.empty() ? env->GetCwd() : dir);
440
12
    if (env->options()->cpu_prof_name.empty()) {
441
10
      DiagnosticFilename filename(env, "CPU", "cpuprofile");
442
10
      env->set_cpu_prof_name(*filename);
443
    } else {
444
2
      env->set_cpu_prof_name(env->options()->cpu_prof_name);
445
    }
446
12
    CHECK_NULL(env->cpu_profiler_connection());
447
12
    env->set_cpu_profiler_connection(
448
24
        std::make_unique<V8CpuProfilerConnection>(env));
449
12
    env->cpu_profiler_connection()->Start();
450
  }
451
5392
  if (env->options()->heap_prof) {
452
6
    const std::string& dir = env->options()->heap_prof_dir;
453
6
    env->set_heap_prof_interval(env->options()->heap_prof_interval);
454
6
    env->set_heap_prof_dir(dir.empty() ? env->GetCwd() : dir);
455
6
    if (env->options()->heap_prof_name.empty()) {
456
6
      DiagnosticFilename filename(env, "Heap", "heapprofile");
457
6
      env->set_heap_prof_name(*filename);
458
    } else {
459
      env->set_heap_prof_name(env->options()->heap_prof_name);
460
    }
461
6
    env->set_heap_profiler_connection(
462
12
        std::make_unique<profiler::V8HeapProfilerConnection>(env));
463
6
    env->heap_profiler_connection()->Start();
464
  }
465
5392
}
466
467
5366
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
468
10732
  CHECK(args[0]->IsString());
469
5366
  Environment* env = Environment::GetCurrent(args);
470
16098
  node::Utf8Value directory(env->isolate(), args[0].As<String>());
471
5366
  env->set_coverage_directory(*directory);
472
5366
}
473
474
475
5366
static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
476
5366
  CHECK(args[0]->IsFunction());
477
5366
  Environment* env = Environment::GetCurrent(args);
478
10732
  env->set_source_map_cache_getter(args[0].As<Function>());
479
5366
}
480
481
6
static void TakeCoverage(const FunctionCallbackInfo<Value>& args) {
482
6
  Environment* env = Environment::GetCurrent(args);
483
6
  V8CoverageConnection* connection = env->coverage_connection();
484
485
6
  Debug(
486
    env,
487
    DebugCategory::INSPECTOR_PROFILER,
488
    "TakeCoverage, connection %s nullptr\n",
489
    connection == nullptr ? "==" : "!=");
490
491
6
  if (connection != nullptr) {
492
    Debug(env, DebugCategory::INSPECTOR_PROFILER, "taking coverage\n");
493
6
    connection->TakeCoverage();
494
  }
495
6
}
496
497
1
static void StopCoverage(const FunctionCallbackInfo<Value>& args) {
498
1
  Environment* env = Environment::GetCurrent(args);
499
1
  V8CoverageConnection* connection = env->coverage_connection();
500
501
1
  Debug(env,
502
        DebugCategory::INSPECTOR_PROFILER,
503
        "StopCoverage, connection %s nullptr\n",
504
        connection == nullptr ? "==" : "!=");
505
506
1
  if (connection != nullptr) {
507
    Debug(env, DebugCategory::INSPECTOR_PROFILER, "Stopping coverage\n");
508
1
    connection->StopCoverage();
509
  }
510
1
}
511
512
615
static void Initialize(Local<Object> target,
513
                       Local<Value> unused,
514
                       Local<Context> context,
515
                       void* priv) {
516
615
  Environment* env = Environment::GetCurrent(context);
517
615
  env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory);
518
615
  env->SetMethod(target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
519
615
  env->SetMethod(target, "takeCoverage", TakeCoverage);
520
615
  env->SetMethod(target, "stopCoverage", StopCoverage);
521
615
}
522
523
4794
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
524
4794
  registry->Register(SetCoverageDirectory);
525
4794
  registry->Register(SetSourceMapCacheGetter);
526
4794
  registry->Register(TakeCoverage);
527
4794
  registry->Register(StopCoverage);
528
4794
}
529
530
}  // namespace profiler
531
}  // namespace node
532
533
4863
NODE_MODULE_CONTEXT_AWARE_INTERNAL(profiler, node::profiler::Initialize)
534
4794
NODE_MODULE_EXTERNAL_REFERENCE(profiler,
535
                               node::profiler::RegisterExternalReferences)