• 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_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 <sstream>
13 
14 namespace node {
15 namespace profiler {
16 
17 using errors::TryCatchScope;
18 using v8::Context;
19 using v8::Function;
20 using v8::FunctionCallbackInfo;
21 using v8::HandleScope;
22 using v8::Isolate;
23 using v8::Local;
24 using v8::MaybeLocal;
25 using v8::NewStringType;
26 using v8::Object;
27 using v8::String;
28 using v8::Value;
29 
30 using v8_inspector::StringView;
31 
V8ProfilerConnection(Environment * env)32 V8ProfilerConnection::V8ProfilerConnection(Environment* env)
33     : session_(env->inspector_agent()->Connect(
34           std::make_unique<V8ProfilerConnection::V8ProfilerSessionDelegate>(
35               this),
36           false)),
37       env_(env) {}
38 
DispatchMessage(const char * method,const char * params)39 size_t V8ProfilerConnection::DispatchMessage(const char* method,
40                                              const char* params) {
41   std::stringstream ss;
42   size_t id = next_id();
43   ss << R"({ "id": )" << id;
44   DCHECK(method != nullptr);
45   ss << R"(, "method": ")" << method << '"';
46   if (params != nullptr) {
47     ss << R"(, "params": )" << params;
48   }
49   ss << " }";
50   std::string message = ss.str();
51   const uint8_t* message_data =
52       reinterpret_cast<const uint8_t*>(message.c_str());
53   Debug(env(),
54         DebugCategory::INSPECTOR_PROFILER,
55         "Dispatching message %s\n",
56         message.c_str());
57   session_->Dispatch(StringView(message_data, message.length()));
58   // TODO(joyeecheung): use this to identify the ending message.
59   return id;
60 }
61 
62 static void WriteResult(Environment* env,
63                         const char* path,
64                         Local<String> result) {
65   int ret = WriteFileSync(env->isolate(), path, result);
66   if (ret != 0) {
67     char err_buf[128];
68     uv_err_name_r(ret, err_buf, sizeof(err_buf));
69     fprintf(stderr, "%s: Failed to write file %s\n", err_buf, path);
70     return;
71   }
72   Debug(env, DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path);
73 }
74 
75 void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
76     const v8_inspector::StringView& message) {
77   Environment* env = connection_->env();
78   Isolate* isolate = env->isolate();
79   HandleScope handle_scope(isolate);
80   Context::Scope context_scope(env->context());
81 
82   // TODO(joyeecheung): always parse the message so that we can use the id to
83   // identify ending messages as well as printing the message in the debug
84   // output when there is an error.
85   const char* type = connection_->type();
86   Debug(env,
87         DebugCategory::INSPECTOR_PROFILER,
88         "Receive %s profile message, ending = %s\n",
89         type,
90         connection_->ending() ? "true" : "false");
91   if (!connection_->ending()) {
92     return;
93   }
94 
95   // Convert StringView to a Local<String>.
96   Local<String> message_str;
97   if (!String::NewFromTwoByte(isolate,
98                               message.characters16(),
99                               NewStringType::kNormal,
100                               message.length())
101            .ToLocal(&message_str)) {
102     fprintf(stderr, "Failed to convert %s profile message\n", type);
103     return;
104   }
105 
106   connection_->WriteProfile(message_str);
107 }
108 
109 static bool EnsureDirectory(const std::string& directory, const char* type) {
110   fs::FSReqWrapSync req_wrap_sync;
111   int ret = fs::MKDirpSync(nullptr, &req_wrap_sync.req, directory, 0777,
112                            nullptr);
113   if (ret < 0 && ret != UV_EEXIST) {
114     char err_buf[128];
115     uv_err_name_r(ret, err_buf, sizeof(err_buf));
116     fprintf(stderr,
117             "%s: Failed to create %s profile directory %s\n",
118             err_buf,
119             type,
120             directory.c_str());
121     return false;
122   }
123   return true;
124 }
125 
126 std::string V8CoverageConnection::GetFilename() const {
127   std::string thread_id = std::to_string(env()->thread_id());
128   std::string pid = std::to_string(uv_os_getpid());
129   std::string timestamp = std::to_string(
130       static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000));
131   char filename[1024];
132   snprintf(filename,
133            sizeof(filename),
134            "coverage-%s-%s-%s.json",
135            pid.c_str(),
136            timestamp.c_str(),
137            thread_id.c_str());
138   return filename;
139 }
140 
141 static MaybeLocal<Object> ParseProfile(Environment* env,
142                                        Local<String> message,
143                                        const char* type) {
144   Local<Context> context = env->context();
145   Isolate* isolate = env->isolate();
146 
147   // Get message.result from the response
148   Local<Value> parsed;
149   if (!v8::JSON::Parse(context, message).ToLocal(&parsed) ||
150       !parsed->IsObject()) {
151     fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type);
152     return MaybeLocal<Object>();
153   }
154 
155   Local<Value> result_v;
156   if (!parsed.As<Object>()
157            ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
158            .ToLocal(&result_v)) {
159     fprintf(stderr, "Failed to get 'result' from %s profile message\n", type);
160     return MaybeLocal<Object>();
161   }
162 
163   if (!result_v->IsObject()) {
164     fprintf(
165         stderr, "'result' from %s profile message is not an object\n", type);
166     return MaybeLocal<Object>();
167   }
168 
169   return result_v.As<Object>();
170 }
171 
172 void V8ProfilerConnection::WriteProfile(Local<String> message) {
173   Local<Context> context = env_->context();
174 
175   // Get message.result from the response.
176   Local<Object> result;
177   if (!ParseProfile(env_, message, type()).ToLocal(&result)) {
178     return;
179   }
180   // Generate the profile output from the subclass.
181   Local<Object> profile;
182   if (!GetProfile(result).ToLocal(&profile)) {
183     return;
184   }
185 
186   Local<String> result_s;
187   if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
188     fprintf(stderr, "Failed to stringify %s profile result\n", type());
189     return;
190   }
191 
192   // Create the directory if necessary.
193   std::string directory = GetDirectory();
194   DCHECK(!directory.empty());
195   if (!EnsureDirectory(directory, type())) {
196     return;
197   }
198 
199   std::string filename = GetFilename();
200   DCHECK(!filename.empty());
201   std::string path = directory + kPathSeparator + filename;
202 
203   WriteResult(env_, path.c_str(), result_s);
204 }
205 
206 void V8CoverageConnection::WriteProfile(Local<String> message) {
207   Isolate* isolate = env_->isolate();
208   Local<Context> context = env_->context();
209   HandleScope handle_scope(isolate);
210   Context::Scope context_scope(context);
211 
212   // This is only set up during pre-execution (when the environment variables
213   // becomes available in the JS land). If it's empty, we don't have coverage
214   // directory path (which is resolved in JS land at the moment) either, so
215   // the best we could to is to just discard the profile and do nothing.
216   // This should only happen in half-baked Environments created using the
217   // embedder API.
218   if (env_->source_map_cache_getter().IsEmpty()) {
219     return;
220   }
221 
222   // Get message.result from the response.
223   Local<Object> result;
224   if (!ParseProfile(env_, message, type()).ToLocal(&result)) {
225     return;
226   }
227   // Generate the profile output from the subclass.
228   Local<Object> profile;
229   if (!GetProfile(result).ToLocal(&profile)) {
230     return;
231   }
232 
233   // append source-map cache information to coverage object:
234   Local<Value> source_map_cache_v;
235   {
236     TryCatchScope try_catch(env());
237     {
238       Isolate::AllowJavascriptExecutionScope allow_js_here(isolate);
239       Local<Function> source_map_cache_getter = env_->source_map_cache_getter();
240       if (!source_map_cache_getter->Call(
241               context, Undefined(isolate), 0, nullptr)
242               .ToLocal(&source_map_cache_v)) {
243         return;
244       }
245     }
246     if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
247       PrintCaughtException(isolate, context, try_catch);
248     }
249   }
250   // Avoid writing to disk if no source-map data:
251   if (!source_map_cache_v->IsUndefined()) {
252     profile->Set(context, FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
253                 source_map_cache_v).ToChecked();
254   }
255 
256   Local<String> result_s;
257   if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
258     fprintf(stderr, "Failed to stringify %s profile result\n", type());
259     return;
260   }
261 
262   // Create the directory if necessary.
263   std::string directory = GetDirectory();
264   DCHECK(!directory.empty());
265   if (!EnsureDirectory(directory, type())) {
266     return;
267   }
268 
269   std::string filename = GetFilename();
270   DCHECK(!filename.empty());
271   std::string path = directory + kPathSeparator + filename;
272 
273   WriteResult(env_, path.c_str(), result_s);
274 }
275 
276 MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
277   return result;
278 }
279 
280 std::string V8CoverageConnection::GetDirectory() const {
281   return env()->coverage_directory();
282 }
283 
284 void V8CoverageConnection::Start() {
285   DispatchMessage("Profiler.enable");
286   DispatchMessage("Profiler.startPreciseCoverage",
287                   R"({ "callCount": true, "detailed": true })");
288 }
289 
290 void V8CoverageConnection::End() {
291   CHECK_EQ(ending_, false);
292   ending_ = true;
293   DispatchMessage("Profiler.takePreciseCoverage");
294 }
295 
296 std::string V8CpuProfilerConnection::GetDirectory() const {
297   return env()->cpu_prof_dir();
298 }
299 
300 std::string V8CpuProfilerConnection::GetFilename() const {
301   return env()->cpu_prof_name();
302 }
303 
304 MaybeLocal<Object> V8CpuProfilerConnection::GetProfile(Local<Object> result) {
305   Local<Value> profile_v;
306   if (!result
307            ->Get(env()->context(),
308                  FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
309            .ToLocal(&profile_v)) {
310     fprintf(stderr, "'profile' from CPU profile result is undefined\n");
311     return MaybeLocal<Object>();
312   }
313   if (!profile_v->IsObject()) {
314     fprintf(stderr, "'profile' from CPU profile result is not an Object\n");
315     return MaybeLocal<Object>();
316   }
317   return profile_v.As<Object>();
318 }
319 
320 void V8CpuProfilerConnection::Start() {
321   DispatchMessage("Profiler.enable");
322   DispatchMessage("Profiler.start");
323   std::string params = R"({ "interval": )";
324   params += std::to_string(env()->cpu_prof_interval());
325   params += " }";
326   DispatchMessage("Profiler.setSamplingInterval", params.c_str());
327 }
328 
329 void V8CpuProfilerConnection::End() {
330   CHECK_EQ(ending_, false);
331   ending_ = true;
332   DispatchMessage("Profiler.stop");
333 }
334 
335 std::string V8HeapProfilerConnection::GetDirectory() const {
336   return env()->heap_prof_dir();
337 }
338 
339 std::string V8HeapProfilerConnection::GetFilename() const {
340   return env()->heap_prof_name();
341 }
342 
343 MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
344   Local<Value> profile_v;
345   if (!result
346            ->Get(env()->context(),
347                  FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
348            .ToLocal(&profile_v)) {
349     fprintf(stderr, "'profile' from heap profile result is undefined\n");
350     return MaybeLocal<Object>();
351   }
352   if (!profile_v->IsObject()) {
353     fprintf(stderr, "'profile' from heap profile result is not an Object\n");
354     return MaybeLocal<Object>();
355   }
356   return profile_v.As<Object>();
357 }
358 
359 void V8HeapProfilerConnection::Start() {
360   DispatchMessage("HeapProfiler.enable");
361   std::string params = R"({ "samplingInterval": )";
362   params += std::to_string(env()->heap_prof_interval());
363   params += " }";
364   DispatchMessage("HeapProfiler.startSampling", params.c_str());
365 }
366 
367 void V8HeapProfilerConnection::End() {
368   CHECK_EQ(ending_, false);
369   ending_ = true;
370   DispatchMessage("HeapProfiler.stopSampling");
371 }
372 
373 // For now, we only support coverage profiling, but we may add more
374 // in the future.
375 static void EndStartedProfilers(Environment* env) {
376   Debug(env, DebugCategory::INSPECTOR_PROFILER, "EndStartedProfilers\n");
377   V8ProfilerConnection* connection = env->cpu_profiler_connection();
378   if (connection != nullptr && !connection->ending()) {
379     Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending cpu profiling\n");
380     connection->End();
381   }
382 
383   connection = env->heap_profiler_connection();
384   if (connection != nullptr && !connection->ending()) {
385     Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending heap profiling\n");
386     connection->End();
387   }
388 
389   connection = env->coverage_connection();
390   if (connection != nullptr && !connection->ending()) {
391     Debug(
392         env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n");
393     connection->End();
394   }
395 }
396 
397 std::string GetCwd(Environment* env) {
398   char cwd[PATH_MAX_BYTES];
399   size_t size = PATH_MAX_BYTES;
400   const int err = uv_cwd(cwd, &size);
401 
402   if (err == 0) {
403     CHECK_GT(size, 0);
404     return cwd;
405   }
406 
407   // This can fail if the cwd is deleted. In that case, fall back to
408   // exec_path.
409   const std::string& exec_path = env->exec_path();
410   return exec_path.substr(0, exec_path.find_last_of(kPathSeparator));
411 }
412 
413 void StartProfilers(Environment* env) {
414   AtExit(env, [](void* env) {
415     EndStartedProfilers(static_cast<Environment*>(env));
416   }, env);
417 
418   Isolate* isolate = env->isolate();
419   Local<String> coverage_str = env->env_vars()->Get(
420       isolate, FIXED_ONE_BYTE_STRING(isolate, "NODE_V8_COVERAGE"))
421       .FromMaybe(Local<String>());
422   if (!coverage_str.IsEmpty() && coverage_str->Length() > 0) {
423     CHECK_NULL(env->coverage_connection());
424     env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
425     env->coverage_connection()->Start();
426   }
427   if (env->options()->cpu_prof) {
428     const std::string& dir = env->options()->cpu_prof_dir;
429     env->set_cpu_prof_interval(env->options()->cpu_prof_interval);
430     env->set_cpu_prof_dir(dir.empty() ? GetCwd(env) : dir);
431     if (env->options()->cpu_prof_name.empty()) {
432       DiagnosticFilename filename(env, "CPU", "cpuprofile");
433       env->set_cpu_prof_name(*filename);
434     } else {
435       env->set_cpu_prof_name(env->options()->cpu_prof_name);
436     }
437     CHECK_NULL(env->cpu_profiler_connection());
438     env->set_cpu_profiler_connection(
439         std::make_unique<V8CpuProfilerConnection>(env));
440     env->cpu_profiler_connection()->Start();
441   }
442   if (env->options()->heap_prof) {
443     const std::string& dir = env->options()->heap_prof_dir;
444     env->set_heap_prof_interval(env->options()->heap_prof_interval);
445     env->set_heap_prof_dir(dir.empty() ? GetCwd(env) : dir);
446     if (env->options()->heap_prof_name.empty()) {
447       DiagnosticFilename filename(env, "Heap", "heapprofile");
448       env->set_heap_prof_name(*filename);
449     } else {
450       env->set_heap_prof_name(env->options()->heap_prof_name);
451     }
452     env->set_heap_profiler_connection(
453         std::make_unique<profiler::V8HeapProfilerConnection>(env));
454     env->heap_profiler_connection()->Start();
455   }
456 }
457 
458 static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
459   CHECK(args[0]->IsString());
460   Environment* env = Environment::GetCurrent(args);
461   node::Utf8Value directory(env->isolate(), args[0].As<String>());
462   env->set_coverage_directory(*directory);
463 }
464 
465 
466 static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
467   CHECK(args[0]->IsFunction());
468   Environment* env = Environment::GetCurrent(args);
469   env->set_source_map_cache_getter(args[0].As<Function>());
470 }
471 
472 static void Initialize(Local<Object> target,
473                        Local<Value> unused,
474                        Local<Context> context,
475                        void* priv) {
476   Environment* env = Environment::GetCurrent(context);
477   env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory);
478   env->SetMethod(target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
479 }
480 
481 }  // namespace profiler
482 }  // namespace node
483 
484 NODE_MODULE_CONTEXT_AWARE_INTERNAL(profiler, node::profiler::Initialize)
485