GCC Code Coverage Report
Directory: ../ Exec Total Coverage
File: /home/iojs/build/workspace/node-test-commit-linux-coverage-daily/nodes/benchmark/out/../src/inspector_profiler.cc Lines: 254 287 88.5 %
Date: 2021-04-30 04:12:06 Branches: 84 124 67.7 %

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
5221
V8ProfilerConnection::V8ProfilerConnection(Environment* env)
35
    : session_(env->inspector_agent()->Connect(
36
10442
          std::make_unique<V8ProfilerConnection::V8ProfilerSessionDelegate>(
37
              this),
38
          false)),
39
15663
      env_(env) {}
40
41
15669
uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
42
                                               const char* params,
43
                                               bool is_profile_request) {
44
31339
  std::stringstream ss;
45
15670
  uint32_t id = next_id();
46
15670
  ss << R"({ "id": )" << id;
47
  DCHECK(method != nullptr);
48
15670
  ss << R"(, "method": ")" << method << '"';
49
15670
  if (params != nullptr) {
50
5221
    ss << R"(, "params": )" << params;
51
  }
52
15670
  ss << " }";
53
31340
  std::string message = ss.str();
54
  const uint8_t* message_data =
55
15670
      reinterpret_cast<const uint8_t*>(message.c_str());
56
  // Save the id of the profile request to identify its response.
57
15670
  if (is_profile_request) {
58
5215
    profile_ids_.insert(id);
59
  }
60
15670
  Debug(env(),
61
        DebugCategory::INSPECTOR_PROFILER,
62
        "Dispatching message %s\n",
63
31340
        message.c_str());
64
15670
  session_->Dispatch(StringView(message_data, message.length()));
65
31340
  return id;
66
}
67
68
5185
static void WriteResult(Environment* env,
69
                        const char* path,
70
                        Local<String> result) {
71
5185
  int ret = WriteFileSync(env->isolate(), path, result);
72
5185
  if (ret != 0) {
73
    char err_buf[128];
74
2
    uv_err_name_r(ret, err_buf, sizeof(err_buf));
75
2
    fprintf(stderr, "%s: Failed to write file %s\n", err_buf, path);
76
2
    return;
77
  }
78
  Debug(env, DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path);
79
}
80
81
15670
void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
82
    const v8_inspector::StringView& message) {
83
15670
  Environment* env = connection_->env();
84
15670
  Isolate* isolate = env->isolate();
85
20882
  HandleScope handle_scope(isolate);
86
15670
  Local<Context> context = env->context();
87
5212
  Context::Scope context_scope(context);
88
89
15670
  const char* type = connection_->type();
90
  // Convert StringView to a Local<String>.
91
  Local<String> message_str;
92
31340
  if (!String::NewFromTwoByte(isolate,
93
                              message.characters16(),
94
                              NewStringType::kNormal,
95
15670
                              message.length())
96
15670
           .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

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

47010
           .ToLocal(&id_v) ||
118
15670
      !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
31340
  uint32_t id = id_v.As<v8::Uint32>()->Value();
125
126
15670
  if (!connection_->HasProfileId(id)) {
127
20910
    Utf8Value str(isolate, message_str);
128
20910
    Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str);
129
10455
    return;
130
  } else {
131
    Debug(env,
132
          DebugCategory::INSPECTOR_PROFILER,
133
          "Writing profile response (id = %" PRIu64 ")\n",
134
10430
          static_cast<uint64_t>(id));
135
  }
136
137
  // Get message.result from the response.
138
  Local<Value> result_v;
139
15645
  if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
140
5215
           .ToLocal(&result_v)) {
141
    fprintf(stderr, "Failed to get 'result' from %s profile response\n", type);
142
    return;
143
  }
144
145
5215
  if (!result_v->IsObject()) {
146
    fprintf(
147
3
        stderr, "'result' from %s profile response is not an object\n", type);
148
3
    return;
149
  }
150
151
10424
  connection_->WriteProfile(result_v.As<Object>());
152
5212
  connection_->RemoveProfileId(id);
153
}
154
155
5185
static bool EnsureDirectory(const std::string& directory, const char* type) {
156
10370
  fs::FSReqWrapSync req_wrap_sync;
157
  int ret = fs::MKDirpSync(nullptr, &req_wrap_sync.req, directory, 0777,
158
5185
                           nullptr);
159

5185
  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
5185
  return true;
170
}
171
172
5161
std::string V8CoverageConnection::GetFilename() const {
173
10322
  std::string thread_id = std::to_string(env()->thread_id());
174
10322
  std::string pid = std::to_string(uv_os_getpid());
175
  std::string timestamp = std::to_string(
176
10322
      static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000));
177
  char filename[1024];
178
5161
  snprintf(filename,
179
           sizeof(filename),
180
           "coverage-%s-%s-%s.json",
181
           pid.c_str(),
182
           timestamp.c_str(),
183
5161
           thread_id.c_str());
184
10322
  return filename;
185
}
186
187
24
void V8ProfilerConnection::WriteProfile(Local<Object> result) {
188
24
  Local<Context> context = env_->context();
189
190
  // Generate the profile output from the subclass.
191
  Local<Object> profile;
192
48
  if (!GetProfile(result).ToLocal(&profile)) {
193
    return;
194
  }
195
196
  Local<String> result_s;
197
48
  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
48
  std::string directory = GetDirectory();
204
  DCHECK(!directory.empty());
205
24
  if (!EnsureDirectory(directory, type())) {
206
    return;
207
  }
208
209
48
  std::string filename = GetFilename();
210
  DCHECK(!filename.empty());
211
48
  std::string path = directory + kPathSeparator + filename;
212
213
24
  WriteResult(env_, path.c_str(), result_s);
214
}
215
216
5188
void V8CoverageConnection::WriteProfile(Local<Object> result) {
217
5188
  Isolate* isolate = env_->isolate();
218
5188
  Local<Context> context = env_->context();
219
10349
  HandleScope handle_scope(isolate);
220
5161
  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
10376
  if (env_->source_map_cache_getter().IsEmpty()) {
229
26
    return;
230
  }
231
232
  // Generate the profile output from the subclass.
233
  Local<Object> profile;
234
10324
  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
10323
    TryCatchScope try_catch(env());
242
    {
243
10323
      Isolate::AllowJavascriptExecutionScope allow_js_here(isolate);
244
5162
      Local<Function> source_map_cache_getter = env_->source_map_cache_getter();
245
15486
      if (!source_map_cache_getter->Call(
246
10324
              context, Undefined(isolate), 0, nullptr)
247
5162
              .ToLocal(&source_map_cache_v)) {
248
1
        return;
249
      }
250
    }
251

5161
    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
10322
  if (!source_map_cache_v->IsUndefined()) {
257
86
    profile->Set(context, FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
258
129
                source_map_cache_v).ToChecked();
259
  }
260
261
  Local<String> result_s;
262
10322
  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
10322
  std::string directory = GetDirectory();
269
  DCHECK(!directory.empty());
270
5161
  if (!EnsureDirectory(directory, type())) {
271
    return;
272
  }
273
274
10322
  std::string filename = GetFilename();
275
  DCHECK(!filename.empty());
276
10322
  std::string path = directory + kPathSeparator + filename;
277
278
5161
  WriteResult(env_, path.c_str(), result_s);
279
}
280
281
5162
MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
282
5162
  return result;
283
}
284
285
5161
std::string V8CoverageConnection::GetDirectory() const {
286
5161
  return env()->coverage_directory();
287
}
288
289
5197
void V8CoverageConnection::Start() {
290
5197
  DispatchMessage("Profiler.enable");
291
5197
  DispatchMessage("Profiler.startPreciseCoverage",
292
5197
                  R"({ "callCount": true, "detailed": true })");
293
5197
}
294
295
5190
void V8CoverageConnection::TakeCoverage() {
296
5190
  DispatchMessage("Profiler.takePreciseCoverage", nullptr, true);
297
5191
}
298
299
1
void V8CoverageConnection::StopCoverage() {
300
1
  DispatchMessage("Profiler.stopPreciseCoverage");
301
1
}
302
303
5184
void V8CoverageConnection::End() {
304
5184
  Debug(env_,
305
      DebugCategory::INSPECTOR_PROFILER,
306
      "V8CoverageConnection::End(), ending = %d\n", ending_);
307
5184
  if (ending_) {
308
    return;
309
  }
310
5184
  ending_ = true;
311
5184
  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
24
           ->Get(env()->context(),
326
48
                 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
      "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
12
std::string V8HeapProfilerConnection::GetDirectory() const {
359
12
  return env()->heap_prof_dir();
360
}
361
362
12
std::string V8HeapProfilerConnection::GetFilename() const {
363
12
  return env()->heap_prof_name();
364
}
365
366
12
MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
367
  Local<Value> profile_v;
368
24
  if (!result
369
24
           ->Get(env()->context(),
370
48
                 FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
371
12
           .ToLocal(&profile_v)) {
372
    fprintf(stderr, "'profile' from heap profile result is undefined\n");
373
    return MaybeLocal<Object>();
374
  }
375
12
  if (!profile_v->IsObject()) {
376
    fprintf(stderr, "'profile' from heap profile result is not an Object\n");
377
    return MaybeLocal<Object>();
378
  }
379
12
  return profile_v.As<Object>();
380
}
381
382
12
void V8HeapProfilerConnection::Start() {
383
12
  DispatchMessage("HeapProfiler.enable");
384
24
  std::string params = R"({ "samplingInterval": )";
385
12
  params += std::to_string(env()->heap_prof_interval());
386
12
  params += " }";
387
12
  DispatchMessage("HeapProfiler.startSampling", params.c_str());
388
12
}
389
390
12
void V8HeapProfilerConnection::End() {
391
12
  Debug(env_,
392
      DebugCategory::INSPECTOR_PROFILER,
393
      "V8HeapProfilerConnection::End(), ending = %d\n", ending_);
394
12
  if (ending_) {
395
    return;
396
  }
397
12
  ending_ = true;
398
12
  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
5189
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
5189
  V8ProfilerConnection* connection = env->cpu_profiler_connection();
407
5189
  if (connection != nullptr) {
408
12
    connection->End();
409
  }
410
411
5189
  connection = env->heap_profiler_connection();
412
5189
  if (connection != nullptr) {
413
12
    connection->End();
414
  }
415
416
5189
  connection = env->coverage_connection();
417
5189
  if (connection != nullptr) {
418
5184
    connection->End();
419
  }
420
5190
}
421
422
5202
void StartProfilers(Environment* env) {
423
25984
  AtExit(env, [](void* env) {
424
5189
    EndStartedProfilers(static_cast<Environment*>(env));
425
20783
  }, env);
426
427
5202
  Isolate* isolate = env->isolate();
428
15606
  Local<String> coverage_str = env->env_vars()->Get(
429
5202
      isolate, FIXED_ONE_BYTE_STRING(isolate, "NODE_V8_COVERAGE"))
430
10404
      .FromMaybe(Local<String>());
431

10402
  if (!coverage_str.IsEmpty() && coverage_str->Length() > 0) {
432
5197
    CHECK_NULL(env->coverage_connection());
433
5197
    env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
434
5197
    env->coverage_connection()->Start();
435
  }
436
5202
  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
20
      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
5202
  if (env->options()->heap_prof) {
452
12
    const std::string& dir = env->options()->heap_prof_dir;
453
12
    env->set_heap_prof_interval(env->options()->heap_prof_interval);
454
12
    env->set_heap_prof_dir(dir.empty() ? env->GetCwd() : dir);
455
12
    if (env->options()->heap_prof_name.empty()) {
456
20
      DiagnosticFilename filename(env, "Heap", "heapprofile");
457
10
      env->set_heap_prof_name(*filename);
458
    } else {
459
2
      env->set_heap_prof_name(env->options()->heap_prof_name);
460
    }
461
12
    env->set_heap_profiler_connection(
462
24
        std::make_unique<profiler::V8HeapProfilerConnection>(env));
463
12
    env->heap_profiler_connection()->Start();
464
  }
465
5202
}
466
467
5176
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
468
15528
  CHECK(args[0]->IsString());
469
5176
  Environment* env = Environment::GetCurrent(args);
470
15528
  node::Utf8Value directory(env->isolate(), args[0].As<String>());
471
5176
  env->set_coverage_directory(*directory);
472
5176
}
473
474
475
5176
static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
476
10352
  CHECK(args[0]->IsFunction());
477
5176
  Environment* env = Environment::GetCurrent(args);
478
10352
  env->set_source_map_cache_getter(args[0].As<Function>());
479
5176
}
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
461
static void Initialize(Local<Object> target,
513
                       Local<Value> unused,
514
                       Local<Context> context,
515
                       void* priv) {
516
461
  Environment* env = Environment::GetCurrent(context);
517
461
  env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory);
518
461
  env->SetMethod(target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
519
461
  env->SetMethod(target, "takeCoverage", TakeCoverage);
520
461
  env->SetMethod(target, "stopCoverage", StopCoverage);
521
461
}
522
523
4762
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
524
4762
  registry->Register(SetCoverageDirectory);
525
4762
  registry->Register(SetSourceMapCacheGetter);
526
4762
  registry->Register(TakeCoverage);
527
4762
  registry->Register(StopCoverage);
528
4762
}
529
530
}  // namespace profiler
531
}  // namespace node
532
533
4820
NODE_MODULE_CONTEXT_AWARE_INTERNAL(profiler, node::profiler::Initialize)
534

19246
NODE_MODULE_EXTERNAL_REFERENCE(profiler,
535
                               node::profiler::RegisterExternalReferences)