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

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

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

5917
  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
5917
  return true;
170
}
171
172
5899
std::string V8CoverageConnection::GetFilename() const {
173
  uint64_t timestamp =
174
5899
      static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000);
175
  return SPrintF("coverage-%s-%s-%s.json",
176
17697
      uv_os_getpid(),
177
      timestamp,
178
5899
      env()->thread_id());
179
}
180
181
18
void V8ProfilerConnection::WriteProfile(Local<Object> result) {
182
18
  Local<Context> context = env_->context();
183
184
  // Generate the profile output from the subclass.
185
  Local<Object> profile;
186
36
  if (!GetProfile(result).ToLocal(&profile)) {
187
    return;
188
  }
189
190
  Local<String> result_s;
191
36
  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
18
  std::string directory = GetDirectory();
198
  DCHECK(!directory.empty());
199
18
  if (!EnsureDirectory(directory, type())) {
200
    return;
201
  }
202
203
36
  std::string filename = GetFilename();
204
  DCHECK(!filename.empty());
205
36
  std::string path = directory + kPathSeparator + filename;
206
207
18
  WriteResult(env_, path.c_str(), result_s);
208
}
209
210
5928
void V8CoverageConnection::WriteProfile(Local<Object> result) {
211
5928
  Isolate* isolate = env_->isolate();
212
5928
  Local<Context> context = env_->context();
213
5928
  HandleScope handle_scope(isolate);
214
5928
  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
11856
  if (env_->source_map_cache_getter().IsEmpty()) {
223
28
    return;
224
  }
225
226
  // Generate the profile output from the subclass.
227
  Local<Object> profile;
228
11800
  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
5900
    TryCatchScope try_catch(env());
236
    {
237
5900
      Isolate::AllowJavascriptExecutionScope allow_js_here(isolate);
238
5900
      Local<Function> source_map_cache_getter = env_->source_map_cache_getter();
239
11800
      if (!source_map_cache_getter->Call(
240
11800
              context, Undefined(isolate), 0, nullptr)
241
5900
              .ToLocal(&source_map_cache_v)) {
242
1
        return;
243
      }
244
    }
245

5899
    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
11798
  if (!source_map_cache_v->IsUndefined()) {
251
32
    profile->Set(context, FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
252
96
                source_map_cache_v).ToChecked();
253
  }
254
255
  Local<String> result_s;
256
11798
  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
5899
  std::string directory = GetDirectory();
263
  DCHECK(!directory.empty());
264
5899
  if (!EnsureDirectory(directory, type())) {
265
    return;
266
  }
267
268
11798
  std::string filename = GetFilename();
269
  DCHECK(!filename.empty());
270
11798
  std::string path = directory + kPathSeparator + filename;
271
272
5899
  WriteResult(env_, path.c_str(), result_s);
273
}
274
275
5900
MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
276
5900
  return result;
277
}
278
279
5899
std::string V8CoverageConnection::GetDirectory() const {
280
5899
  return env()->coverage_directory();
281
}
282
283
5937
void V8CoverageConnection::Start() {
284
5937
  DispatchMessage("Profiler.enable");
285
5937
  DispatchMessage("Profiler.startPreciseCoverage",
286
                  R"({ "callCount": true, "detailed": true })");
287
5937
}
288
289
5931
void V8CoverageConnection::TakeCoverage() {
290
5931
  DispatchMessage("Profiler.takePreciseCoverage", nullptr, true);
291
5931
}
292
293
1
void V8CoverageConnection::StopCoverage() {
294
1
  DispatchMessage("Profiler.stopPreciseCoverage");
295
1
}
296
297
5925
void V8CoverageConnection::End() {
298
5925
  Debug(env_,
299
      DebugCategory::INSPECTOR_PROFILER,
300
5925
      "V8CoverageConnection::End(), ending = %d\n", ending_);
301
5925
  if (ending_) {
302
    return;
303
  }
304
5925
  ending_ = true;
305
5925
  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
6
std::string V8HeapProfilerConnection::GetDirectory() const {
353
6
  return env()->heap_prof_dir();
354
}
355
356
6
std::string V8HeapProfilerConnection::GetFilename() const {
357
6
  return env()->heap_prof_name();
358
}
359
360
6
MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
361
  Local<Value> profile_v;
362
12
  if (!result
363
6
           ->Get(env()->context(),
364
12
                 FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
365
6
           .ToLocal(&profile_v)) {
366
    fprintf(stderr, "'profile' from heap profile result is undefined\n");
367
    return MaybeLocal<Object>();
368
  }
369
6
  if (!profile_v->IsObject()) {
370
    fprintf(stderr, "'profile' from heap profile result is not an Object\n");
371
    return MaybeLocal<Object>();
372
  }
373
6
  return profile_v.As<Object>();
374
}
375
376
6
void V8HeapProfilerConnection::Start() {
377
6
  DispatchMessage("HeapProfiler.enable");
378
12
  std::string params = R"({ "samplingInterval": )";
379
6
  params += std::to_string(env()->heap_prof_interval());
380
6
  params += " }";
381
6
  DispatchMessage("HeapProfiler.startSampling", params.c_str());
382
6
}
383
384
6
void V8HeapProfilerConnection::End() {
385
6
  Debug(env_,
386
      DebugCategory::INSPECTOR_PROFILER,
387
6
      "V8HeapProfilerConnection::End(), ending = %d\n", ending_);
388
6
  if (ending_) {
389
    return;
390
  }
391
6
  ending_ = true;
392
6
  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
5928
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
5928
  V8ProfilerConnection* connection = env->cpu_profiler_connection();
401
5928
  if (connection != nullptr) {
402
12
    connection->End();
403
  }
404
405
5928
  connection = env->heap_profiler_connection();
406
5928
  if (connection != nullptr) {
407
6
    connection->End();
408
  }
409
410
5928
  connection = env->coverage_connection();
411
5928
  if (connection != nullptr) {
412
5925
    connection->End();
413
  }
414
5928
}
415
416
5940
void StartProfilers(Environment* env) {
417
5940
  AtExit(env, [](void* env) {
418
5928
    EndStartedProfilers(static_cast<Environment*>(env));
419
5928
  }, env);
420
421
5940
  Isolate* isolate = env->isolate();
422
11880
  Local<String> coverage_str = env->env_vars()->Get(
423
5940
      isolate, FIXED_ONE_BYTE_STRING(isolate, "NODE_V8_COVERAGE"))
424
5940
      .FromMaybe(Local<String>());
425

11879
  if (!coverage_str.IsEmpty() && coverage_str->Length() > 0) {
426
5937
    CHECK_NULL(env->coverage_connection());
427
5937
    env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
428
5937
    env->coverage_connection()->Start();
429
  }
430
5940
  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
5940
  if (env->options()->heap_prof) {
446
6
    const std::string& dir = env->options()->heap_prof_dir;
447
6
    env->set_heap_prof_interval(env->options()->heap_prof_interval);
448
6
    env->set_heap_prof_dir(dir.empty() ? env->GetCwd() : dir);
449
6
    if (env->options()->heap_prof_name.empty()) {
450
6
      DiagnosticFilename filename(env, "Heap", "heapprofile");
451
6
      env->set_heap_prof_name(*filename);
452
    } else {
453
      env->set_heap_prof_name(env->options()->heap_prof_name);
454
    }
455
6
    env->set_heap_profiler_connection(
456
12
        std::make_unique<profiler::V8HeapProfilerConnection>(env));
457
6
    env->heap_profiler_connection()->Start();
458
  }
459
5940
}
460
461
5914
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
462
11828
  CHECK(args[0]->IsString());
463
5914
  Environment* env = Environment::GetCurrent(args);
464
17742
  node::Utf8Value directory(env->isolate(), args[0].As<String>());
465
5914
  env->set_coverage_directory(*directory);
466
5914
}
467
468
469
5914
static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
470
5914
  CHECK(args[0]->IsFunction());
471
5914
  Environment* env = Environment::GetCurrent(args);
472
11828
  env->set_source_map_cache_getter(args[0].As<Function>());
473
5914
}
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
847
static void Initialize(Local<Object> target,
507
                       Local<Value> unused,
508
                       Local<Context> context,
509
                       void* priv) {
510
847
  Environment* env = Environment::GetCurrent(context);
511
847
  env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory);
512
847
  env->SetMethod(target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
513
847
  env->SetMethod(target, "takeCoverage", TakeCoverage);
514
847
  env->SetMethod(target, "stopCoverage", StopCoverage);
515
847
}
516
517
5110
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
518
5110
  registry->Register(SetCoverageDirectory);
519
5110
  registry->Register(SetSourceMapCacheGetter);
520
5110
  registry->Register(TakeCoverage);
521
5110
  registry->Register(StopCoverage);
522
5110
}
523
524
}  // namespace profiler
525
}  // namespace node
526
527
5168
NODE_MODULE_CONTEXT_AWARE_INTERNAL(profiler, node::profiler::Initialize)
528
5110
NODE_MODULE_EXTERNAL_REFERENCE(profiler,
529
                               node::profiler::RegisterExternalReferences)