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