js/src/builtin/Profilers.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:7ce09a18f5f2
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__ */

mercurial