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: 246 281 87.5 %
Date: 2020-11-20 19:51:53 Branches: 81 124 65.3 %

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

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

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

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

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

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

18704
NODE_MODULE_CONTEXT_AWARE_INTERNAL(profiler, node::profiler::Initialize)