|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* Profiling-related API */ |
|
8 |
|
9 #include "builtin/Profilers.h" |
|
10 |
|
11 #include <stdarg.h> |
|
12 |
|
13 #ifdef MOZ_CALLGRIND |
|
14 # include <valgrind/callgrind.h> |
|
15 #endif |
|
16 |
|
17 #ifdef __APPLE__ |
|
18 #ifdef MOZ_INSTRUMENTS |
|
19 # include "devtools/Instruments.h" |
|
20 #endif |
|
21 #ifdef MOZ_SHARK |
|
22 # include "devtools/sharkctl.h" |
|
23 #endif |
|
24 #endif |
|
25 |
|
26 #ifdef XP_WIN |
|
27 # include <process.h> |
|
28 # define getpid _getpid |
|
29 #endif |
|
30 |
|
31 #include "vm/Probes.h" |
|
32 |
|
33 #include "jscntxtinlines.h" |
|
34 |
|
35 using namespace js; |
|
36 |
|
37 using mozilla::ArrayLength; |
|
38 |
|
39 /* Thread-unsafe error management */ |
|
40 |
|
41 static char gLastError[2000]; |
|
42 |
|
43 static void |
|
44 #ifdef __GNUC__ |
|
45 __attribute__((unused,format(printf,1,2))) |
|
46 #endif |
|
47 UnsafeError(const char *format, ...) |
|
48 { |
|
49 va_list args; |
|
50 va_start(args, format); |
|
51 (void) vsnprintf(gLastError, sizeof(gLastError), format, args); |
|
52 va_end(args); |
|
53 |
|
54 gLastError[sizeof(gLastError) - 1] = '\0'; |
|
55 } |
|
56 |
|
57 JS_PUBLIC_API(const char *) |
|
58 JS_UnsafeGetLastProfilingError() |
|
59 { |
|
60 return gLastError; |
|
61 } |
|
62 |
|
63 #ifdef __APPLE__ |
|
64 static bool |
|
65 StartOSXProfiling(const char *profileName, pid_t pid) |
|
66 { |
|
67 bool ok = true; |
|
68 const char* profiler = nullptr; |
|
69 #ifdef MOZ_SHARK |
|
70 ok = Shark::Start(); |
|
71 profiler = "Shark"; |
|
72 #endif |
|
73 #ifdef MOZ_INSTRUMENTS |
|
74 ok = Instruments::Start(pid); |
|
75 profiler = "Instruments"; |
|
76 #endif |
|
77 if (!ok) { |
|
78 if (profileName) |
|
79 UnsafeError("Failed to start %s for %s", profiler, profileName); |
|
80 else |
|
81 UnsafeError("Failed to start %s", profiler); |
|
82 return false; |
|
83 } |
|
84 return true; |
|
85 } |
|
86 #endif |
|
87 |
|
88 JS_PUBLIC_API(bool) |
|
89 JS_StartProfiling(const char *profileName, pid_t pid) |
|
90 { |
|
91 bool ok = true; |
|
92 #ifdef __APPLE__ |
|
93 ok = StartOSXProfiling(profileName, pid); |
|
94 #endif |
|
95 #ifdef __linux__ |
|
96 if (!js_StartPerf()) |
|
97 ok = false; |
|
98 #endif |
|
99 return ok; |
|
100 } |
|
101 |
|
102 JS_PUBLIC_API(bool) |
|
103 JS_StopProfiling(const char *profileName) |
|
104 { |
|
105 bool ok = true; |
|
106 #ifdef __APPLE__ |
|
107 #ifdef MOZ_SHARK |
|
108 Shark::Stop(); |
|
109 #endif |
|
110 #ifdef MOZ_INSTRUMENTS |
|
111 Instruments::Stop(profileName); |
|
112 #endif |
|
113 #endif |
|
114 #ifdef __linux__ |
|
115 if (!js_StopPerf()) |
|
116 ok = false; |
|
117 #endif |
|
118 return ok; |
|
119 } |
|
120 |
|
121 /* |
|
122 * Start or stop whatever platform- and configuration-specific profiling |
|
123 * backends are available. |
|
124 */ |
|
125 static bool |
|
126 ControlProfilers(bool toState) |
|
127 { |
|
128 bool ok = true; |
|
129 |
|
130 if (! probes::ProfilingActive && toState) { |
|
131 #ifdef __APPLE__ |
|
132 #if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) |
|
133 const char* profiler; |
|
134 #ifdef MOZ_SHARK |
|
135 ok = Shark::Start(); |
|
136 profiler = "Shark"; |
|
137 #endif |
|
138 #ifdef MOZ_INSTRUMENTS |
|
139 ok = Instruments::Resume(); |
|
140 profiler = "Instruments"; |
|
141 #endif |
|
142 if (!ok) { |
|
143 UnsafeError("Failed to start %s", profiler); |
|
144 } |
|
145 #endif |
|
146 #endif |
|
147 #ifdef MOZ_CALLGRIND |
|
148 if (! js_StartCallgrind()) { |
|
149 UnsafeError("Failed to start Callgrind"); |
|
150 ok = false; |
|
151 } |
|
152 #endif |
|
153 } else if (probes::ProfilingActive && ! toState) { |
|
154 #ifdef __APPLE__ |
|
155 #ifdef MOZ_SHARK |
|
156 Shark::Stop(); |
|
157 #endif |
|
158 #ifdef MOZ_INSTRUMENTS |
|
159 Instruments::Pause(); |
|
160 #endif |
|
161 #endif |
|
162 #ifdef MOZ_CALLGRIND |
|
163 if (! js_StopCallgrind()) { |
|
164 UnsafeError("failed to stop Callgrind"); |
|
165 ok = false; |
|
166 } |
|
167 #endif |
|
168 } |
|
169 |
|
170 probes::ProfilingActive = toState; |
|
171 |
|
172 return ok; |
|
173 } |
|
174 |
|
175 /* |
|
176 * Pause/resume whatever profiling mechanism is currently compiled |
|
177 * in, if applicable. This will not affect things like dtrace. |
|
178 * |
|
179 * Do not mix calls to these APIs with calls to the individual |
|
180 * profilers' pause/resume functions, because only overall state is |
|
181 * tracked, not the state of each profiler. |
|
182 */ |
|
183 JS_PUBLIC_API(bool) |
|
184 JS_PauseProfilers(const char *profileName) |
|
185 { |
|
186 return ControlProfilers(false); |
|
187 } |
|
188 |
|
189 JS_PUBLIC_API(bool) |
|
190 JS_ResumeProfilers(const char *profileName) |
|
191 { |
|
192 return ControlProfilers(true); |
|
193 } |
|
194 |
|
195 JS_PUBLIC_API(bool) |
|
196 JS_DumpProfile(const char *outfile, const char *profileName) |
|
197 { |
|
198 bool ok = true; |
|
199 #ifdef MOZ_CALLGRIND |
|
200 js_DumpCallgrind(outfile); |
|
201 #endif |
|
202 return ok; |
|
203 } |
|
204 |
|
205 #ifdef MOZ_PROFILING |
|
206 |
|
207 struct RequiredStringArg { |
|
208 JSContext *mCx; |
|
209 char *mBytes; |
|
210 RequiredStringArg(JSContext *cx, const CallArgs &args, size_t argi, const char *caller) |
|
211 : mCx(cx), mBytes(nullptr) |
|
212 { |
|
213 if (args.length() <= argi) { |
|
214 JS_ReportError(cx, "%s: not enough arguments", caller); |
|
215 } else if (!args[argi].isString()) { |
|
216 JS_ReportError(cx, "%s: invalid arguments (string expected)", caller); |
|
217 } else { |
|
218 mBytes = JS_EncodeString(cx, args[argi].toString()); |
|
219 } |
|
220 } |
|
221 operator void*() { |
|
222 return (void*) mBytes; |
|
223 } |
|
224 ~RequiredStringArg() { |
|
225 js_free(mBytes); |
|
226 } |
|
227 }; |
|
228 |
|
229 static bool |
|
230 StartProfiling(JSContext *cx, unsigned argc, jsval *vp) |
|
231 { |
|
232 CallArgs args = CallArgsFromVp(argc, vp); |
|
233 if (args.length() == 0) { |
|
234 args.rval().setBoolean(JS_StartProfiling(nullptr, getpid())); |
|
235 return true; |
|
236 } |
|
237 |
|
238 RequiredStringArg profileName(cx, args, 0, "startProfiling"); |
|
239 if (!profileName) |
|
240 return false; |
|
241 |
|
242 if (args.length() == 1) { |
|
243 args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, getpid())); |
|
244 return true; |
|
245 } |
|
246 |
|
247 if (!args[1].isInt32()) { |
|
248 JS_ReportError(cx, "startProfiling: invalid arguments (int expected)"); |
|
249 return false; |
|
250 } |
|
251 pid_t pid = static_cast<pid_t>(args[1].toInt32()); |
|
252 args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, pid)); |
|
253 return true; |
|
254 } |
|
255 |
|
256 static bool |
|
257 StopProfiling(JSContext *cx, unsigned argc, jsval *vp) |
|
258 { |
|
259 CallArgs args = CallArgsFromVp(argc, vp); |
|
260 if (args.length() == 0) { |
|
261 args.rval().setBoolean(JS_StopProfiling(nullptr)); |
|
262 return true; |
|
263 } |
|
264 |
|
265 RequiredStringArg profileName(cx, args, 0, "stopProfiling"); |
|
266 if (!profileName) |
|
267 return false; |
|
268 args.rval().setBoolean(JS_StopProfiling(profileName.mBytes)); |
|
269 return true; |
|
270 } |
|
271 |
|
272 static bool |
|
273 PauseProfilers(JSContext *cx, unsigned argc, jsval *vp) |
|
274 { |
|
275 CallArgs args = CallArgsFromVp(argc, vp); |
|
276 if (args.length() == 0) { |
|
277 args.rval().setBoolean(JS_PauseProfilers(nullptr)); |
|
278 return true; |
|
279 } |
|
280 |
|
281 RequiredStringArg profileName(cx, args, 0, "pauseProfiling"); |
|
282 if (!profileName) |
|
283 return false; |
|
284 args.rval().setBoolean(JS_PauseProfilers(profileName.mBytes)); |
|
285 return true; |
|
286 } |
|
287 |
|
288 static bool |
|
289 ResumeProfilers(JSContext *cx, unsigned argc, jsval *vp) |
|
290 { |
|
291 CallArgs args = CallArgsFromVp(argc, vp); |
|
292 if (args.length() == 0) { |
|
293 args.rval().setBoolean(JS_ResumeProfilers(nullptr)); |
|
294 return true; |
|
295 } |
|
296 |
|
297 RequiredStringArg profileName(cx, args, 0, "resumeProfiling"); |
|
298 if (!profileName) |
|
299 return false; |
|
300 args.rval().setBoolean(JS_ResumeProfilers(profileName.mBytes)); |
|
301 return true; |
|
302 } |
|
303 |
|
304 /* Usage: DumpProfile([filename[, profileName]]) */ |
|
305 static bool |
|
306 DumpProfile(JSContext *cx, unsigned argc, jsval *vp) |
|
307 { |
|
308 bool ret; |
|
309 CallArgs args = CallArgsFromVp(argc, vp); |
|
310 if (args.length() == 0) { |
|
311 ret = JS_DumpProfile(nullptr, nullptr); |
|
312 } else { |
|
313 RequiredStringArg filename(cx, args, 0, "dumpProfile"); |
|
314 if (!filename) |
|
315 return false; |
|
316 |
|
317 if (args.length() == 1) { |
|
318 ret = JS_DumpProfile(filename.mBytes, nullptr); |
|
319 } else { |
|
320 RequiredStringArg profileName(cx, args, 1, "dumpProfile"); |
|
321 if (!profileName) |
|
322 return false; |
|
323 |
|
324 ret = JS_DumpProfile(filename.mBytes, profileName.mBytes); |
|
325 } |
|
326 } |
|
327 |
|
328 args.rval().setBoolean(ret); |
|
329 return true; |
|
330 } |
|
331 |
|
332 #if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) |
|
333 |
|
334 static bool |
|
335 IgnoreAndReturnTrue(JSContext *cx, unsigned argc, jsval *vp) |
|
336 { |
|
337 CallArgs args = CallArgsFromVp(argc, vp); |
|
338 args.rval().setBoolean(true); |
|
339 return true; |
|
340 } |
|
341 |
|
342 #endif |
|
343 |
|
344 #ifdef MOZ_CALLGRIND |
|
345 static bool |
|
346 StartCallgrind(JSContext *cx, unsigned argc, jsval *vp) |
|
347 { |
|
348 CallArgs args = CallArgsFromVp(argc, vp); |
|
349 args.rval().setBoolean(js_StartCallgrind()); |
|
350 return true; |
|
351 } |
|
352 |
|
353 static bool |
|
354 StopCallgrind(JSContext *cx, unsigned argc, jsval *vp) |
|
355 { |
|
356 CallArgs args = CallArgsFromVp(argc, vp); |
|
357 args.rval().setBoolean(js_StopCallgrind()); |
|
358 return true; |
|
359 } |
|
360 |
|
361 static bool |
|
362 DumpCallgrind(JSContext *cx, unsigned argc, jsval *vp) |
|
363 { |
|
364 CallArgs args = CallArgsFromVp(argc, vp); |
|
365 if (args.length() == 0) { |
|
366 args.rval().setBoolean(js_DumpCallgrind(nullptr)); |
|
367 return true; |
|
368 } |
|
369 |
|
370 RequiredStringArg outFile(cx, args, 0, "dumpCallgrind"); |
|
371 if (!outFile) |
|
372 return false; |
|
373 |
|
374 args.rval().setBoolean(js_DumpCallgrind(outFile.mBytes)); |
|
375 return true; |
|
376 } |
|
377 #endif |
|
378 |
|
379 static const JSFunctionSpec profiling_functions[] = { |
|
380 JS_FN("startProfiling", StartProfiling, 1,0), |
|
381 JS_FN("stopProfiling", StopProfiling, 1,0), |
|
382 JS_FN("pauseProfilers", PauseProfilers, 1,0), |
|
383 JS_FN("resumeProfilers", ResumeProfilers, 1,0), |
|
384 JS_FN("dumpProfile", DumpProfile, 2,0), |
|
385 #if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) |
|
386 /* Keep users of the old shark API happy. */ |
|
387 JS_FN("connectShark", IgnoreAndReturnTrue, 0,0), |
|
388 JS_FN("disconnectShark", IgnoreAndReturnTrue, 0,0), |
|
389 JS_FN("startShark", StartProfiling, 0,0), |
|
390 JS_FN("stopShark", StopProfiling, 0,0), |
|
391 #endif |
|
392 #ifdef MOZ_CALLGRIND |
|
393 JS_FN("startCallgrind", StartCallgrind, 0,0), |
|
394 JS_FN("stopCallgrind", StopCallgrind, 0,0), |
|
395 JS_FN("dumpCallgrind", DumpCallgrind, 1,0), |
|
396 #endif |
|
397 JS_FS_END |
|
398 }; |
|
399 |
|
400 #endif |
|
401 |
|
402 JS_PUBLIC_API(bool) |
|
403 JS_DefineProfilingFunctions(JSContext *cx, JSObject *objArg) |
|
404 { |
|
405 RootedObject obj(cx, objArg); |
|
406 |
|
407 assertSameCompartment(cx, obj); |
|
408 #ifdef MOZ_PROFILING |
|
409 return JS_DefineFunctions(cx, obj, profiling_functions); |
|
410 #else |
|
411 return true; |
|
412 #endif |
|
413 } |
|
414 |
|
415 #ifdef MOZ_CALLGRIND |
|
416 |
|
417 JS_FRIEND_API(bool) |
|
418 js_StartCallgrind() |
|
419 { |
|
420 JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_START_INSTRUMENTATION); |
|
421 JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_ZERO_STATS); |
|
422 return true; |
|
423 } |
|
424 |
|
425 JS_FRIEND_API(bool) |
|
426 js_StopCallgrind() |
|
427 { |
|
428 JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_STOP_INSTRUMENTATION); |
|
429 return true; |
|
430 } |
|
431 |
|
432 JS_FRIEND_API(bool) |
|
433 js_DumpCallgrind(const char *outfile) |
|
434 { |
|
435 if (outfile) { |
|
436 JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS_AT(outfile)); |
|
437 } else { |
|
438 JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS); |
|
439 } |
|
440 |
|
441 return true; |
|
442 } |
|
443 |
|
444 #endif /* MOZ_CALLGRIND */ |
|
445 |
|
446 #ifdef __linux__ |
|
447 |
|
448 /* |
|
449 * Code for starting and stopping |perf|, the Linux profiler. |
|
450 * |
|
451 * Output from profiling is written to mozperf.data in your cwd. |
|
452 * |
|
453 * To enable, set MOZ_PROFILE_WITH_PERF=1 in your environment. |
|
454 * |
|
455 * To pass additional parameters to |perf record|, provide them in the |
|
456 * MOZ_PROFILE_PERF_FLAGS environment variable. If this variable does not |
|
457 * exist, we default it to "--call-graph". (If you don't want --call-graph but |
|
458 * don't want to pass any other args, define MOZ_PROFILE_PERF_FLAGS to the empty |
|
459 * string.) |
|
460 * |
|
461 * If you include --pid or --output in MOZ_PROFILE_PERF_FLAGS, you're just |
|
462 * asking for trouble. |
|
463 * |
|
464 * Our split-on-spaces logic is lame, so don't expect MOZ_PROFILE_PERF_FLAGS to |
|
465 * work if you pass an argument which includes a space (e.g. |
|
466 * MOZ_PROFILE_PERF_FLAGS="-e 'foo bar'"). |
|
467 */ |
|
468 |
|
469 #include <signal.h> |
|
470 #include <sys/wait.h> |
|
471 #include <unistd.h> |
|
472 |
|
473 static bool perfInitialized = false; |
|
474 static pid_t perfPid = 0; |
|
475 |
|
476 bool js_StartPerf() |
|
477 { |
|
478 const char *outfile = "mozperf.data"; |
|
479 |
|
480 if (perfPid != 0) { |
|
481 UnsafeError("js_StartPerf: called while perf was already running!\n"); |
|
482 return false; |
|
483 } |
|
484 |
|
485 // Bail if MOZ_PROFILE_WITH_PERF is empty or undefined. |
|
486 if (!getenv("MOZ_PROFILE_WITH_PERF") || |
|
487 !strlen(getenv("MOZ_PROFILE_WITH_PERF"))) { |
|
488 return true; |
|
489 } |
|
490 |
|
491 /* |
|
492 * Delete mozperf.data the first time through -- we're going to append to it |
|
493 * later on, so we want it to be clean when we start out. |
|
494 */ |
|
495 if (!perfInitialized) { |
|
496 perfInitialized = true; |
|
497 unlink(outfile); |
|
498 char cwd[4096]; |
|
499 printf("Writing perf profiling data to %s/%s\n", |
|
500 getcwd(cwd, sizeof(cwd)), outfile); |
|
501 } |
|
502 |
|
503 pid_t mainPid = getpid(); |
|
504 |
|
505 pid_t childPid = fork(); |
|
506 if (childPid == 0) { |
|
507 /* perf record --append --pid $mainPID --output=$outfile $MOZ_PROFILE_PERF_FLAGS */ |
|
508 |
|
509 char mainPidStr[16]; |
|
510 snprintf(mainPidStr, sizeof(mainPidStr), "%d", mainPid); |
|
511 const char *defaultArgs[] = {"perf", "record", "--append", |
|
512 "--pid", mainPidStr, "--output", outfile}; |
|
513 |
|
514 Vector<const char*, 0, SystemAllocPolicy> args; |
|
515 args.append(defaultArgs, ArrayLength(defaultArgs)); |
|
516 |
|
517 const char *flags = getenv("MOZ_PROFILE_PERF_FLAGS"); |
|
518 if (!flags) { |
|
519 flags = "--call-graph"; |
|
520 } |
|
521 |
|
522 char *flags2 = (char *)js_malloc(strlen(flags) + 1); |
|
523 if (!flags2) |
|
524 return false; |
|
525 strcpy(flags2, flags); |
|
526 |
|
527 // Split |flags2| on spaces. (Don't bother to free it -- we're going to |
|
528 // exec anyway.) |
|
529 char *toksave; |
|
530 char *tok = strtok_r(flags2, " ", &toksave); |
|
531 while (tok) { |
|
532 args.append(tok); |
|
533 tok = strtok_r(nullptr, " ", &toksave); |
|
534 } |
|
535 |
|
536 args.append((char*) nullptr); |
|
537 |
|
538 execvp("perf", const_cast<char**>(args.begin())); |
|
539 |
|
540 /* Reached only if execlp fails. */ |
|
541 fprintf(stderr, "Unable to start perf.\n"); |
|
542 exit(1); |
|
543 } |
|
544 else if (childPid > 0) { |
|
545 perfPid = childPid; |
|
546 |
|
547 /* Give perf a chance to warm up. */ |
|
548 usleep(500 * 1000); |
|
549 return true; |
|
550 } |
|
551 else { |
|
552 UnsafeError("js_StartPerf: fork() failed\n"); |
|
553 return false; |
|
554 } |
|
555 } |
|
556 |
|
557 bool js_StopPerf() |
|
558 { |
|
559 if (perfPid == 0) { |
|
560 UnsafeError("js_StopPerf: perf is not running.\n"); |
|
561 return true; |
|
562 } |
|
563 |
|
564 if (kill(perfPid, SIGINT)) { |
|
565 UnsafeError("js_StopPerf: kill failed\n"); |
|
566 |
|
567 // Try to reap the process anyway. |
|
568 waitpid(perfPid, nullptr, WNOHANG); |
|
569 } |
|
570 else { |
|
571 waitpid(perfPid, nullptr, 0); |
|
572 } |
|
573 |
|
574 perfPid = 0; |
|
575 return true; |
|
576 } |
|
577 |
|
578 #endif /* __linux__ */ |