GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: inspector_profiler.cc Lines: 255 285 89.5 %
Date: 2022-05-15 04:15:05 Branches: 75 112 67.0 %

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

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

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

5992
  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
5992
  return true;
170
}
171
172
5968
std::string V8CoverageConnection::GetFilename() const {
173
  uint64_t timestamp =
174
5968
      static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000);
175
  return SPrintF("coverage-%s-%s-%s.json",
176
17904
      uv_os_getpid(),
177
      timestamp,
178
5968
      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
6001
void V8CoverageConnection::WriteProfile(Local<Object> result) {
211
6001
  Isolate* isolate = env_->isolate();
212
6001
  Local<Context> context = env_->context();
213
6001
  HandleScope handle_scope(isolate);
214
6001
  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
12002
  if (env_->source_map_cache_getter().IsEmpty()) {
223
31
    return;
224
  }
225
226
  // Generate the profile output from the subclass.
227
  Local<Object> profile;
228
11940
  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
5970
    TryCatchScope try_catch(env());
236
    {
237
5970
      Isolate::AllowJavascriptExecutionScope allow_js_here(isolate);
238
5970
      Local<Function> source_map_cache_getter = env_->source_map_cache_getter();
239
11940
      if (!source_map_cache_getter->Call(
240
11940
              context, Undefined(isolate), 0, nullptr)
241
5970
              .ToLocal(&source_map_cache_v)) {
242
1
        return;
243
      }
244
    }
245

5969
    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
11938
  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
11938
  if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
257
1
    fprintf(stderr, "Failed to stringify %s profile result\n", type());
258
1
    return;
259
  }
260
261
  // Create the directory if necessary.
262
5968
  std::string directory = GetDirectory();
263
  DCHECK(!directory.empty());
264
5968
  if (!EnsureDirectory(directory, type())) {
265
    return;
266
  }
267
268
11936
  std::string filename = GetFilename();
269
  DCHECK(!filename.empty());
270
11936
  std::string path = directory + kPathSeparator + filename;
271
272
5968
  WriteResult(env_, path.c_str(), result_s);
273
}
274
275
5970
MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
276
5970
  return result;
277
}
278
279
5968
std::string V8CoverageConnection::GetDirectory() const {
280
5968
  return env()->coverage_directory();
281
}
282
283
6010
void V8CoverageConnection::Start() {
284
6010
  DispatchMessage("Profiler.enable");
285
6010
  DispatchMessage("Profiler.startPreciseCoverage",
286
                  R"({ "callCount": true, "detailed": true })");
287
6010
}
288
289
6004
void V8CoverageConnection::TakeCoverage() {
290
6004
  DispatchMessage("Profiler.takePreciseCoverage", nullptr, true);
291
6004
}
292
293
1
void V8CoverageConnection::StopCoverage() {
294
1
  DispatchMessage("Profiler.stopPreciseCoverage");
295
1
}
296
297
5998
void V8CoverageConnection::End() {
298
5998
  Debug(env_,
299
      DebugCategory::INSPECTOR_PROFILER,
300
5998
      "V8CoverageConnection::End(), ending = %d\n", ending_);
301
5998
  if (ending_) {
302
    return;
303
  }
304
5998
  ending_ = true;
305
5998
  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
6002
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
6002
  V8ProfilerConnection* connection = env->cpu_profiler_connection();
401
6002
  if (connection != nullptr) {
402
12
    connection->End();
403
  }
404
405
6002
  connection = env->heap_profiler_connection();
406
6002
  if (connection != nullptr) {
407
12
    connection->End();
408
  }
409
410
6002
  connection = env->coverage_connection();
411
6002
  if (connection != nullptr) {
412
5998
    connection->End();
413
  }
414
6002
}
415
416
6014
void StartProfilers(Environment* env) {
417
6014
  AtExit(env, [](void* env) {
418
6002
    EndStartedProfilers(static_cast<Environment*>(env));
419
6002
  }, env);
420
421
6014
  Isolate* isolate = env->isolate();
422
12028
  Local<String> coverage_str = env->env_vars()->Get(
423
6014
      isolate, FIXED_ONE_BYTE_STRING(isolate, "NODE_V8_COVERAGE"))
424
6014
      .FromMaybe(Local<String>());
425

12027
  if (!coverage_str.IsEmpty() && coverage_str->Length() > 0) {
426
6010
    CHECK_NULL(env->coverage_connection());
427
6010
    env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
428
6010
    env->coverage_connection()->Start();
429
  }
430
6014
  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
6014
  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
6014
}
460
461
5984
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
462
11968
  CHECK(args[0]->IsString());
463
5984
  Environment* env = Environment::GetCurrent(args);
464
17952
  node::Utf8Value directory(env->isolate(), args[0].As<String>());
465
5984
  env->set_coverage_directory(*directory);
466
5984
}
467
468
469
5984
static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
470
5984
  CHECK(args[0]->IsFunction());
471
5984
  Environment* env = Environment::GetCurrent(args);
472
11968
  env->set_source_map_cache_getter(args[0].As<Function>());
473
5984
}
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
852
static void Initialize(Local<Object> target,
507
                       Local<Value> unused,
508
                       Local<Context> context,
509
                       void* priv) {
510
852
  Environment* env = Environment::GetCurrent(context);
511
852
  env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory);
512
852
  env->SetMethod(target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
513
852
  env->SetMethod(target, "takeCoverage", TakeCoverage);
514
852
  env->SetMethod(target, "stopCoverage", StopCoverage);
515
852
}
516
517
5179
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
518
5179
  registry->Register(SetCoverageDirectory);
519
5179
  registry->Register(SetSourceMapCacheGetter);
520
5179
  registry->Register(TakeCoverage);
521
5179
  registry->Register(StopCoverage);
522
5179
}
523
524
}  // namespace profiler
525
}  // namespace node
526
527
5247
NODE_MODULE_CONTEXT_AWARE_INTERNAL(profiler, node::profiler::Initialize)
528
5179
NODE_MODULE_EXTERNAL_REFERENCE(profiler,
529
                               node::profiler::RegisterExternalReferences)