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

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

36386
           .ToLocal(&id_v) ||
118
18193
      !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
18193
  uint32_t id = id_v.As<v8::Uint32>()->Value();
125
126
18193
  if (!connection_->HasProfileId(id)) {
127
12137
    Utf8Value str(isolate, message_str);
128
12137
    Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str);
129
12137
    return;
130
  } else {
131
    Debug(env,
132
          DebugCategory::INSPECTOR_PROFILER,
133
          "Writing profile response (id = %" PRIu64 ")\n",
134
12112
          static_cast<uint64_t>(id));
135
  }
136
137
  // Get message.result from the response.
138
  Local<Value> result_v;
139
12112
  if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
140
6056
           .ToLocal(&result_v)) {
141
    fprintf(stderr, "Failed to get 'result' from %s profile response\n", type);
142
    return;
143
  }
144
145
6056
  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
12106
  connection_->WriteProfile(result_v.As<Object>());
152
6053
  connection_->RemoveProfileId(id);
153
}
154
155
6024
static bool EnsureDirectory(const std::string& directory, const char* type) {
156
12048
  fs::FSReqWrapSync req_wrap_sync;
157
6024
  int ret = fs::MKDirpSync(nullptr, &req_wrap_sync.req, directory, 0777,
158
                           nullptr);
159

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

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

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