• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
V8ProfilerConnection(Environment * env)34 V8ProfilerConnection::V8ProfilerConnection(Environment* env)
35     : session_(env->inspector_agent()->Connect(
36           std::make_unique<V8ProfilerConnection::V8ProfilerSessionDelegate>(
37               this),
38           false)),
39       env_(env) {}
40 
DispatchMessage(const char * method,const char * params,bool is_profile_request)41 uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
42                                                const char* params,
43                                                bool is_profile_request) {
44   std::stringstream ss;
45   uint32_t id = next_id();
46   ss << R"({ "id": )" << id;
47   DCHECK(method != nullptr);
48   ss << R"(, "method": ")" << method << '"';
49   if (params != nullptr) {
50     ss << R"(, "params": )" << params;
51   }
52   ss << " }";
53   std::string message = ss.str();
54   const uint8_t* message_data =
55       reinterpret_cast<const uint8_t*>(message.c_str());
56   // Save the id of the profile request to identify its response.
57   if (is_profile_request) {
58     profile_ids_.insert(id);
59   }
60   Debug(env(),
61         DebugCategory::INSPECTOR_PROFILER,
62         "Dispatching message %s\n",
63         message.c_str());
64   session_->Dispatch(StringView(message_data, message.length()));
65   return id;
66 }
67 
68 static void WriteResult(Environment* env,
69                         const char* path,
70                         Local<String> result) {
71   int ret = WriteFileSync(env->isolate(), path, result);
72   if (ret != 0) {
73     char err_buf[128];
74     uv_err_name_r(ret, err_buf, sizeof(err_buf));
75     fprintf(stderr, "%s: Failed to write file %s\n", err_buf, path);
76     return;
77   }
78   Debug(env, DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path);
79 }
80 
81 void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
82     const v8_inspector::StringView& message) {
83   Environment* env = connection_->env();
84   Isolate* isolate = env->isolate();
85   HandleScope handle_scope(isolate);
86   Local<Context> context = env->context();
87   Context::Scope context_scope(context);
88 
89   const char* type = connection_->type();
90   // Convert StringView to a Local<String>.
91   Local<String> message_str;
92   if (!String::NewFromTwoByte(isolate,
93                               message.characters16(),
94                               NewStringType::kNormal,
95                               message.length())
96            .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   if (!v8::JSON::Parse(context, message_str).ToLocal(&parsed) ||
109       !parsed->IsObject()) {
110     fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type);
111     return;
112   }
113 
114   Local<Object> response = parsed.As<Object>();
115   Local<Value> id_v;
116   if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "id"))
117            .ToLocal(&id_v) ||
118       !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   uint32_t id = id_v.As<v8::Uint32>()->Value();
125 
126   if (!connection_->HasProfileId(id)) {
127     Utf8Value str(isolate, message_str);
128     Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str);
129     return;
130   } else {
131     Debug(env,
132           DebugCategory::INSPECTOR_PROFILER,
133           "Writing profile response (id = %" PRIu64 ")\n",
134           static_cast<uint64_t>(id));
135   }
136 
137   // Get message.result from the response.
138   Local<Value> result_v;
139   if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
140            .ToLocal(&result_v)) {
141     fprintf(stderr, "Failed to get 'result' from %s profile response\n", type);
142     return;
143   }
144 
145   if (!result_v->IsObject()) {
146     fprintf(
147         stderr, "'result' from %s profile response is not an object\n", type);
148     return;
149   }
150 
151   connection_->WriteProfile(result_v.As<Object>());
152   connection_->RemoveProfileId(id);
153 }
154 
155 static bool EnsureDirectory(const std::string& directory, const char* type) {
156   fs::FSReqWrapSync req_wrap_sync;
157   int ret = fs::MKDirpSync(nullptr, &req_wrap_sync.req, directory, 0777,
158                            nullptr);
159   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   return true;
170 }
171 
172 std::string V8CoverageConnection::GetFilename() const {
173   uint64_t timestamp =
174       static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000);
175   return SPrintF("coverage-%s-%s-%s.json",
176       uv_os_getpid(),
177       timestamp,
178       env()->thread_id());
179 }
180 
181 void V8ProfilerConnection::WriteProfile(Local<Object> result) {
182   Local<Context> context = env_->context();
183 
184   // Generate the profile output from the subclass.
185   Local<Object> profile;
186   if (!GetProfile(result).ToLocal(&profile)) {
187     return;
188   }
189 
190   Local<String> result_s;
191   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   std::string directory = GetDirectory();
198   DCHECK(!directory.empty());
199   if (!EnsureDirectory(directory, type())) {
200     return;
201   }
202 
203   std::string filename = GetFilename();
204   DCHECK(!filename.empty());
205   std::string path = directory + kPathSeparator + filename;
206 
207   WriteResult(env_, path.c_str(), result_s);
208 }
209 
210 void V8CoverageConnection::WriteProfile(Local<Object> result) {
211   Isolate* isolate = env_->isolate();
212   Local<Context> context = env_->context();
213   HandleScope handle_scope(isolate);
214   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   if (env_->source_map_cache_getter().IsEmpty()) {
223     return;
224   }
225 
226   // Generate the profile output from the subclass.
227   Local<Object> profile;
228   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     TryCatchScope try_catch(env());
236     {
237       Isolate::AllowJavascriptExecutionScope allow_js_here(isolate);
238       Local<Function> source_map_cache_getter = env_->source_map_cache_getter();
239       if (!source_map_cache_getter->Call(
240               context, Undefined(isolate), 0, nullptr)
241               .ToLocal(&source_map_cache_v)) {
242         return;
243       }
244     }
245     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   if (!source_map_cache_v->IsUndefined()) {
251     profile->Set(context, FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
252                 source_map_cache_v).ToChecked();
253   }
254 
255   Local<String> result_s;
256   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   std::string directory = GetDirectory();
263   DCHECK(!directory.empty());
264   if (!EnsureDirectory(directory, type())) {
265     return;
266   }
267 
268   std::string filename = GetFilename();
269   DCHECK(!filename.empty());
270   std::string path = directory + kPathSeparator + filename;
271 
272   WriteResult(env_, path.c_str(), result_s);
273 }
274 
275 MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
276   return result;
277 }
278 
279 std::string V8CoverageConnection::GetDirectory() const {
280   return env()->coverage_directory();
281 }
282 
283 void V8CoverageConnection::Start() {
284   DispatchMessage("Profiler.enable");
285   DispatchMessage("Profiler.startPreciseCoverage",
286                   R"({ "callCount": true, "detailed": true })");
287 }
288 
289 void V8CoverageConnection::TakeCoverage() {
290   DispatchMessage("Profiler.takePreciseCoverage", nullptr, true);
291 }
292 
293 void V8CoverageConnection::StopCoverage() {
294   DispatchMessage("Profiler.stopPreciseCoverage");
295 }
296 
297 void V8CoverageConnection::End() {
298   Debug(env_,
299       DebugCategory::INSPECTOR_PROFILER,
300       "V8CoverageConnection::End(), ending = %d\n", ending_);
301   if (ending_) {
302     return;
303   }
304   ending_ = true;
305   TakeCoverage();
306 }
307 
308 std::string V8CpuProfilerConnection::GetDirectory() const {
309   return env()->cpu_prof_dir();
310 }
311 
312 std::string V8CpuProfilerConnection::GetFilename() const {
313   return env()->cpu_prof_name();
314 }
315 
316 MaybeLocal<Object> V8CpuProfilerConnection::GetProfile(Local<Object> result) {
317   Local<Value> profile_v;
318   if (!result
319            ->Get(env()->context(),
320                  FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
321            .ToLocal(&profile_v)) {
322     fprintf(stderr, "'profile' from CPU profile result is undefined\n");
323     return MaybeLocal<Object>();
324   }
325   if (!profile_v->IsObject()) {
326     fprintf(stderr, "'profile' from CPU profile result is not an Object\n");
327     return MaybeLocal<Object>();
328   }
329   return profile_v.As<Object>();
330 }
331 
332 void V8CpuProfilerConnection::Start() {
333   DispatchMessage("Profiler.enable");
334   std::string params = R"({ "interval": )";
335   params += std::to_string(env()->cpu_prof_interval());
336   params += " }";
337   DispatchMessage("Profiler.setSamplingInterval", params.c_str());
338   DispatchMessage("Profiler.start");
339 }
340 
341 void V8CpuProfilerConnection::End() {
342   Debug(env_,
343       DebugCategory::INSPECTOR_PROFILER,
344       "V8CpuProfilerConnection::End(), ending = %d\n", ending_);
345   if (ending_) {
346     return;
347   }
348   ending_ = true;
349   DispatchMessage("Profiler.stop", nullptr, true);
350 }
351 
352 std::string V8HeapProfilerConnection::GetDirectory() const {
353   return env()->heap_prof_dir();
354 }
355 
356 std::string V8HeapProfilerConnection::GetFilename() const {
357   return env()->heap_prof_name();
358 }
359 
360 MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
361   Local<Value> profile_v;
362   if (!result
363            ->Get(env()->context(),
364                  FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
365            .ToLocal(&profile_v)) {
366     fprintf(stderr, "'profile' from heap profile result is undefined\n");
367     return MaybeLocal<Object>();
368   }
369   if (!profile_v->IsObject()) {
370     fprintf(stderr, "'profile' from heap profile result is not an Object\n");
371     return MaybeLocal<Object>();
372   }
373   return profile_v.As<Object>();
374 }
375 
376 void V8HeapProfilerConnection::Start() {
377   DispatchMessage("HeapProfiler.enable");
378   std::string params = R"({ "samplingInterval": )";
379   params += std::to_string(env()->heap_prof_interval());
380   params += " }";
381   DispatchMessage("HeapProfiler.startSampling", params.c_str());
382 }
383 
384 void V8HeapProfilerConnection::End() {
385   Debug(env_,
386       DebugCategory::INSPECTOR_PROFILER,
387       "V8HeapProfilerConnection::End(), ending = %d\n", ending_);
388   if (ending_) {
389     return;
390   }
391   ending_ = true;
392   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 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   V8ProfilerConnection* connection = env->cpu_profiler_connection();
401   if (connection != nullptr) {
402     connection->End();
403   }
404 
405   connection = env->heap_profiler_connection();
406   if (connection != nullptr) {
407     connection->End();
408   }
409 
410   connection = env->coverage_connection();
411   if (connection != nullptr) {
412     connection->End();
413   }
414 }
415 
416 void StartProfilers(Environment* env) {
417   AtExit(env, [](void* env) {
418     EndStartedProfilers(static_cast<Environment*>(env));
419   }, env);
420 
421   Isolate* isolate = env->isolate();
422   Local<String> coverage_str = env->env_vars()->Get(
423       isolate, FIXED_ONE_BYTE_STRING(isolate, "NODE_V8_COVERAGE"))
424       .FromMaybe(Local<String>());
425   if ((!coverage_str.IsEmpty() && coverage_str->Length() > 0) ||
426       env->options()->test_runner_coverage) {
427     CHECK_NULL(env->coverage_connection());
428     env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
429     env->coverage_connection()->Start();
430   }
431   if (env->options()->cpu_prof) {
432     const std::string& dir = env->options()->cpu_prof_dir;
433     env->set_cpu_prof_interval(env->options()->cpu_prof_interval);
434     env->set_cpu_prof_dir(dir.empty() ? env->GetCwd() : dir);
435     if (env->options()->cpu_prof_name.empty()) {
436       DiagnosticFilename filename(env, "CPU", "cpuprofile");
437       env->set_cpu_prof_name(*filename);
438     } else {
439       env->set_cpu_prof_name(env->options()->cpu_prof_name);
440     }
441     CHECK_NULL(env->cpu_profiler_connection());
442     env->set_cpu_profiler_connection(
443         std::make_unique<V8CpuProfilerConnection>(env));
444     env->cpu_profiler_connection()->Start();
445   }
446   if (env->options()->heap_prof) {
447     const std::string& dir = env->options()->heap_prof_dir;
448     env->set_heap_prof_interval(env->options()->heap_prof_interval);
449     env->set_heap_prof_dir(dir.empty() ? env->GetCwd() : dir);
450     if (env->options()->heap_prof_name.empty()) {
451       DiagnosticFilename filename(env, "Heap", "heapprofile");
452       env->set_heap_prof_name(*filename);
453     } else {
454       env->set_heap_prof_name(env->options()->heap_prof_name);
455     }
456     env->set_heap_profiler_connection(
457         std::make_unique<profiler::V8HeapProfilerConnection>(env));
458     env->heap_profiler_connection()->Start();
459   }
460 }
461 
462 static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
463   CHECK(args[0]->IsString());
464   Environment* env = Environment::GetCurrent(args);
465   node::Utf8Value directory(env->isolate(), args[0].As<String>());
466   env->set_coverage_directory(*directory);
467 }
468 
469 
470 static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
471   CHECK(args[0]->IsFunction());
472   Environment* env = Environment::GetCurrent(args);
473   env->set_source_map_cache_getter(args[0].As<Function>());
474 }
475 
476 static void TakeCoverage(const FunctionCallbackInfo<Value>& args) {
477   Environment* env = Environment::GetCurrent(args);
478   V8CoverageConnection* connection = env->coverage_connection();
479 
480   Debug(
481     env,
482     DebugCategory::INSPECTOR_PROFILER,
483     "TakeCoverage, connection %s nullptr\n",
484     connection == nullptr ? "==" : "!=");
485 
486   if (connection != nullptr) {
487     Debug(env, DebugCategory::INSPECTOR_PROFILER, "taking coverage\n");
488     connection->TakeCoverage();
489   }
490 }
491 
492 static void StopCoverage(const FunctionCallbackInfo<Value>& args) {
493   Environment* env = Environment::GetCurrent(args);
494   V8CoverageConnection* connection = env->coverage_connection();
495 
496   Debug(env,
497         DebugCategory::INSPECTOR_PROFILER,
498         "StopCoverage, connection %s nullptr\n",
499         connection == nullptr ? "==" : "!=");
500 
501   if (connection != nullptr) {
502     Debug(env, DebugCategory::INSPECTOR_PROFILER, "Stopping coverage\n");
503     connection->StopCoverage();
504   }
505 }
506 
507 static void Initialize(Local<Object> target,
508                        Local<Value> unused,
509                        Local<Context> context,
510                        void* priv) {
511   SetMethod(context, target, "setCoverageDirectory", SetCoverageDirectory);
512   SetMethod(
513       context, target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
514   SetMethod(context, target, "takeCoverage", TakeCoverage);
515   SetMethod(context, target, "stopCoverage", StopCoverage);
516 }
517 
518 void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
519   registry->Register(SetCoverageDirectory);
520   registry->Register(SetSourceMapCacheGetter);
521   registry->Register(TakeCoverage);
522   registry->Register(StopCoverage);
523 }
524 
525 }  // namespace profiler
526 }  // namespace node
527 
528 NODE_BINDING_CONTEXT_AWARE_INTERNAL(profiler, node::profiler::Initialize)
529 NODE_BINDING_EXTERNAL_REFERENCE(profiler,
530                                 node::profiler::RegisterExternalReferences)
531