1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/builtin/Profilers.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,578 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* Profiling-related API */ 1.11 + 1.12 +#include "builtin/Profilers.h" 1.13 + 1.14 +#include <stdarg.h> 1.15 + 1.16 +#ifdef MOZ_CALLGRIND 1.17 +# include <valgrind/callgrind.h> 1.18 +#endif 1.19 + 1.20 +#ifdef __APPLE__ 1.21 +#ifdef MOZ_INSTRUMENTS 1.22 +# include "devtools/Instruments.h" 1.23 +#endif 1.24 +#ifdef MOZ_SHARK 1.25 +# include "devtools/sharkctl.h" 1.26 +#endif 1.27 +#endif 1.28 + 1.29 +#ifdef XP_WIN 1.30 +# include <process.h> 1.31 +# define getpid _getpid 1.32 +#endif 1.33 + 1.34 +#include "vm/Probes.h" 1.35 + 1.36 +#include "jscntxtinlines.h" 1.37 + 1.38 +using namespace js; 1.39 + 1.40 +using mozilla::ArrayLength; 1.41 + 1.42 +/* Thread-unsafe error management */ 1.43 + 1.44 +static char gLastError[2000]; 1.45 + 1.46 +static void 1.47 +#ifdef __GNUC__ 1.48 +__attribute__((unused,format(printf,1,2))) 1.49 +#endif 1.50 +UnsafeError(const char *format, ...) 1.51 +{ 1.52 + va_list args; 1.53 + va_start(args, format); 1.54 + (void) vsnprintf(gLastError, sizeof(gLastError), format, args); 1.55 + va_end(args); 1.56 + 1.57 + gLastError[sizeof(gLastError) - 1] = '\0'; 1.58 +} 1.59 + 1.60 +JS_PUBLIC_API(const char *) 1.61 +JS_UnsafeGetLastProfilingError() 1.62 +{ 1.63 + return gLastError; 1.64 +} 1.65 + 1.66 +#ifdef __APPLE__ 1.67 +static bool 1.68 +StartOSXProfiling(const char *profileName, pid_t pid) 1.69 +{ 1.70 + bool ok = true; 1.71 + const char* profiler = nullptr; 1.72 +#ifdef MOZ_SHARK 1.73 + ok = Shark::Start(); 1.74 + profiler = "Shark"; 1.75 +#endif 1.76 +#ifdef MOZ_INSTRUMENTS 1.77 + ok = Instruments::Start(pid); 1.78 + profiler = "Instruments"; 1.79 +#endif 1.80 + if (!ok) { 1.81 + if (profileName) 1.82 + UnsafeError("Failed to start %s for %s", profiler, profileName); 1.83 + else 1.84 + UnsafeError("Failed to start %s", profiler); 1.85 + return false; 1.86 + } 1.87 + return true; 1.88 +} 1.89 +#endif 1.90 + 1.91 +JS_PUBLIC_API(bool) 1.92 +JS_StartProfiling(const char *profileName, pid_t pid) 1.93 +{ 1.94 + bool ok = true; 1.95 +#ifdef __APPLE__ 1.96 + ok = StartOSXProfiling(profileName, pid); 1.97 +#endif 1.98 +#ifdef __linux__ 1.99 + if (!js_StartPerf()) 1.100 + ok = false; 1.101 +#endif 1.102 + return ok; 1.103 +} 1.104 + 1.105 +JS_PUBLIC_API(bool) 1.106 +JS_StopProfiling(const char *profileName) 1.107 +{ 1.108 + bool ok = true; 1.109 +#ifdef __APPLE__ 1.110 +#ifdef MOZ_SHARK 1.111 + Shark::Stop(); 1.112 +#endif 1.113 +#ifdef MOZ_INSTRUMENTS 1.114 + Instruments::Stop(profileName); 1.115 +#endif 1.116 +#endif 1.117 +#ifdef __linux__ 1.118 + if (!js_StopPerf()) 1.119 + ok = false; 1.120 +#endif 1.121 + return ok; 1.122 +} 1.123 + 1.124 +/* 1.125 + * Start or stop whatever platform- and configuration-specific profiling 1.126 + * backends are available. 1.127 + */ 1.128 +static bool 1.129 +ControlProfilers(bool toState) 1.130 +{ 1.131 + bool ok = true; 1.132 + 1.133 + if (! probes::ProfilingActive && toState) { 1.134 +#ifdef __APPLE__ 1.135 +#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) 1.136 + const char* profiler; 1.137 +#ifdef MOZ_SHARK 1.138 + ok = Shark::Start(); 1.139 + profiler = "Shark"; 1.140 +#endif 1.141 +#ifdef MOZ_INSTRUMENTS 1.142 + ok = Instruments::Resume(); 1.143 + profiler = "Instruments"; 1.144 +#endif 1.145 + if (!ok) { 1.146 + UnsafeError("Failed to start %s", profiler); 1.147 + } 1.148 +#endif 1.149 +#endif 1.150 +#ifdef MOZ_CALLGRIND 1.151 + if (! js_StartCallgrind()) { 1.152 + UnsafeError("Failed to start Callgrind"); 1.153 + ok = false; 1.154 + } 1.155 +#endif 1.156 + } else if (probes::ProfilingActive && ! toState) { 1.157 +#ifdef __APPLE__ 1.158 +#ifdef MOZ_SHARK 1.159 + Shark::Stop(); 1.160 +#endif 1.161 +#ifdef MOZ_INSTRUMENTS 1.162 + Instruments::Pause(); 1.163 +#endif 1.164 +#endif 1.165 +#ifdef MOZ_CALLGRIND 1.166 + if (! js_StopCallgrind()) { 1.167 + UnsafeError("failed to stop Callgrind"); 1.168 + ok = false; 1.169 + } 1.170 +#endif 1.171 + } 1.172 + 1.173 + probes::ProfilingActive = toState; 1.174 + 1.175 + return ok; 1.176 +} 1.177 + 1.178 +/* 1.179 + * Pause/resume whatever profiling mechanism is currently compiled 1.180 + * in, if applicable. This will not affect things like dtrace. 1.181 + * 1.182 + * Do not mix calls to these APIs with calls to the individual 1.183 + * profilers' pause/resume functions, because only overall state is 1.184 + * tracked, not the state of each profiler. 1.185 + */ 1.186 +JS_PUBLIC_API(bool) 1.187 +JS_PauseProfilers(const char *profileName) 1.188 +{ 1.189 + return ControlProfilers(false); 1.190 +} 1.191 + 1.192 +JS_PUBLIC_API(bool) 1.193 +JS_ResumeProfilers(const char *profileName) 1.194 +{ 1.195 + return ControlProfilers(true); 1.196 +} 1.197 + 1.198 +JS_PUBLIC_API(bool) 1.199 +JS_DumpProfile(const char *outfile, const char *profileName) 1.200 +{ 1.201 + bool ok = true; 1.202 +#ifdef MOZ_CALLGRIND 1.203 + js_DumpCallgrind(outfile); 1.204 +#endif 1.205 + return ok; 1.206 +} 1.207 + 1.208 +#ifdef MOZ_PROFILING 1.209 + 1.210 +struct RequiredStringArg { 1.211 + JSContext *mCx; 1.212 + char *mBytes; 1.213 + RequiredStringArg(JSContext *cx, const CallArgs &args, size_t argi, const char *caller) 1.214 + : mCx(cx), mBytes(nullptr) 1.215 + { 1.216 + if (args.length() <= argi) { 1.217 + JS_ReportError(cx, "%s: not enough arguments", caller); 1.218 + } else if (!args[argi].isString()) { 1.219 + JS_ReportError(cx, "%s: invalid arguments (string expected)", caller); 1.220 + } else { 1.221 + mBytes = JS_EncodeString(cx, args[argi].toString()); 1.222 + } 1.223 + } 1.224 + operator void*() { 1.225 + return (void*) mBytes; 1.226 + } 1.227 + ~RequiredStringArg() { 1.228 + js_free(mBytes); 1.229 + } 1.230 +}; 1.231 + 1.232 +static bool 1.233 +StartProfiling(JSContext *cx, unsigned argc, jsval *vp) 1.234 +{ 1.235 + CallArgs args = CallArgsFromVp(argc, vp); 1.236 + if (args.length() == 0) { 1.237 + args.rval().setBoolean(JS_StartProfiling(nullptr, getpid())); 1.238 + return true; 1.239 + } 1.240 + 1.241 + RequiredStringArg profileName(cx, args, 0, "startProfiling"); 1.242 + if (!profileName) 1.243 + return false; 1.244 + 1.245 + if (args.length() == 1) { 1.246 + args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, getpid())); 1.247 + return true; 1.248 + } 1.249 + 1.250 + if (!args[1].isInt32()) { 1.251 + JS_ReportError(cx, "startProfiling: invalid arguments (int expected)"); 1.252 + return false; 1.253 + } 1.254 + pid_t pid = static_cast<pid_t>(args[1].toInt32()); 1.255 + args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, pid)); 1.256 + return true; 1.257 +} 1.258 + 1.259 +static bool 1.260 +StopProfiling(JSContext *cx, unsigned argc, jsval *vp) 1.261 +{ 1.262 + CallArgs args = CallArgsFromVp(argc, vp); 1.263 + if (args.length() == 0) { 1.264 + args.rval().setBoolean(JS_StopProfiling(nullptr)); 1.265 + return true; 1.266 + } 1.267 + 1.268 + RequiredStringArg profileName(cx, args, 0, "stopProfiling"); 1.269 + if (!profileName) 1.270 + return false; 1.271 + args.rval().setBoolean(JS_StopProfiling(profileName.mBytes)); 1.272 + return true; 1.273 +} 1.274 + 1.275 +static bool 1.276 +PauseProfilers(JSContext *cx, unsigned argc, jsval *vp) 1.277 +{ 1.278 + CallArgs args = CallArgsFromVp(argc, vp); 1.279 + if (args.length() == 0) { 1.280 + args.rval().setBoolean(JS_PauseProfilers(nullptr)); 1.281 + return true; 1.282 + } 1.283 + 1.284 + RequiredStringArg profileName(cx, args, 0, "pauseProfiling"); 1.285 + if (!profileName) 1.286 + return false; 1.287 + args.rval().setBoolean(JS_PauseProfilers(profileName.mBytes)); 1.288 + return true; 1.289 +} 1.290 + 1.291 +static bool 1.292 +ResumeProfilers(JSContext *cx, unsigned argc, jsval *vp) 1.293 +{ 1.294 + CallArgs args = CallArgsFromVp(argc, vp); 1.295 + if (args.length() == 0) { 1.296 + args.rval().setBoolean(JS_ResumeProfilers(nullptr)); 1.297 + return true; 1.298 + } 1.299 + 1.300 + RequiredStringArg profileName(cx, args, 0, "resumeProfiling"); 1.301 + if (!profileName) 1.302 + return false; 1.303 + args.rval().setBoolean(JS_ResumeProfilers(profileName.mBytes)); 1.304 + return true; 1.305 +} 1.306 + 1.307 +/* Usage: DumpProfile([filename[, profileName]]) */ 1.308 +static bool 1.309 +DumpProfile(JSContext *cx, unsigned argc, jsval *vp) 1.310 +{ 1.311 + bool ret; 1.312 + CallArgs args = CallArgsFromVp(argc, vp); 1.313 + if (args.length() == 0) { 1.314 + ret = JS_DumpProfile(nullptr, nullptr); 1.315 + } else { 1.316 + RequiredStringArg filename(cx, args, 0, "dumpProfile"); 1.317 + if (!filename) 1.318 + return false; 1.319 + 1.320 + if (args.length() == 1) { 1.321 + ret = JS_DumpProfile(filename.mBytes, nullptr); 1.322 + } else { 1.323 + RequiredStringArg profileName(cx, args, 1, "dumpProfile"); 1.324 + if (!profileName) 1.325 + return false; 1.326 + 1.327 + ret = JS_DumpProfile(filename.mBytes, profileName.mBytes); 1.328 + } 1.329 + } 1.330 + 1.331 + args.rval().setBoolean(ret); 1.332 + return true; 1.333 +} 1.334 + 1.335 +#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) 1.336 + 1.337 +static bool 1.338 +IgnoreAndReturnTrue(JSContext *cx, unsigned argc, jsval *vp) 1.339 +{ 1.340 + CallArgs args = CallArgsFromVp(argc, vp); 1.341 + args.rval().setBoolean(true); 1.342 + return true; 1.343 +} 1.344 + 1.345 +#endif 1.346 + 1.347 +#ifdef MOZ_CALLGRIND 1.348 +static bool 1.349 +StartCallgrind(JSContext *cx, unsigned argc, jsval *vp) 1.350 +{ 1.351 + CallArgs args = CallArgsFromVp(argc, vp); 1.352 + args.rval().setBoolean(js_StartCallgrind()); 1.353 + return true; 1.354 +} 1.355 + 1.356 +static bool 1.357 +StopCallgrind(JSContext *cx, unsigned argc, jsval *vp) 1.358 +{ 1.359 + CallArgs args = CallArgsFromVp(argc, vp); 1.360 + args.rval().setBoolean(js_StopCallgrind()); 1.361 + return true; 1.362 +} 1.363 + 1.364 +static bool 1.365 +DumpCallgrind(JSContext *cx, unsigned argc, jsval *vp) 1.366 +{ 1.367 + CallArgs args = CallArgsFromVp(argc, vp); 1.368 + if (args.length() == 0) { 1.369 + args.rval().setBoolean(js_DumpCallgrind(nullptr)); 1.370 + return true; 1.371 + } 1.372 + 1.373 + RequiredStringArg outFile(cx, args, 0, "dumpCallgrind"); 1.374 + if (!outFile) 1.375 + return false; 1.376 + 1.377 + args.rval().setBoolean(js_DumpCallgrind(outFile.mBytes)); 1.378 + return true; 1.379 +} 1.380 +#endif 1.381 + 1.382 +static const JSFunctionSpec profiling_functions[] = { 1.383 + JS_FN("startProfiling", StartProfiling, 1,0), 1.384 + JS_FN("stopProfiling", StopProfiling, 1,0), 1.385 + JS_FN("pauseProfilers", PauseProfilers, 1,0), 1.386 + JS_FN("resumeProfilers", ResumeProfilers, 1,0), 1.387 + JS_FN("dumpProfile", DumpProfile, 2,0), 1.388 +#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) 1.389 + /* Keep users of the old shark API happy. */ 1.390 + JS_FN("connectShark", IgnoreAndReturnTrue, 0,0), 1.391 + JS_FN("disconnectShark", IgnoreAndReturnTrue, 0,0), 1.392 + JS_FN("startShark", StartProfiling, 0,0), 1.393 + JS_FN("stopShark", StopProfiling, 0,0), 1.394 +#endif 1.395 +#ifdef MOZ_CALLGRIND 1.396 + JS_FN("startCallgrind", StartCallgrind, 0,0), 1.397 + JS_FN("stopCallgrind", StopCallgrind, 0,0), 1.398 + JS_FN("dumpCallgrind", DumpCallgrind, 1,0), 1.399 +#endif 1.400 + JS_FS_END 1.401 +}; 1.402 + 1.403 +#endif 1.404 + 1.405 +JS_PUBLIC_API(bool) 1.406 +JS_DefineProfilingFunctions(JSContext *cx, JSObject *objArg) 1.407 +{ 1.408 + RootedObject obj(cx, objArg); 1.409 + 1.410 + assertSameCompartment(cx, obj); 1.411 +#ifdef MOZ_PROFILING 1.412 + return JS_DefineFunctions(cx, obj, profiling_functions); 1.413 +#else 1.414 + return true; 1.415 +#endif 1.416 +} 1.417 + 1.418 +#ifdef MOZ_CALLGRIND 1.419 + 1.420 +JS_FRIEND_API(bool) 1.421 +js_StartCallgrind() 1.422 +{ 1.423 + JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_START_INSTRUMENTATION); 1.424 + JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_ZERO_STATS); 1.425 + return true; 1.426 +} 1.427 + 1.428 +JS_FRIEND_API(bool) 1.429 +js_StopCallgrind() 1.430 +{ 1.431 + JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_STOP_INSTRUMENTATION); 1.432 + return true; 1.433 +} 1.434 + 1.435 +JS_FRIEND_API(bool) 1.436 +js_DumpCallgrind(const char *outfile) 1.437 +{ 1.438 + if (outfile) { 1.439 + JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS_AT(outfile)); 1.440 + } else { 1.441 + JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS); 1.442 + } 1.443 + 1.444 + return true; 1.445 +} 1.446 + 1.447 +#endif /* MOZ_CALLGRIND */ 1.448 + 1.449 +#ifdef __linux__ 1.450 + 1.451 +/* 1.452 + * Code for starting and stopping |perf|, the Linux profiler. 1.453 + * 1.454 + * Output from profiling is written to mozperf.data in your cwd. 1.455 + * 1.456 + * To enable, set MOZ_PROFILE_WITH_PERF=1 in your environment. 1.457 + * 1.458 + * To pass additional parameters to |perf record|, provide them in the 1.459 + * MOZ_PROFILE_PERF_FLAGS environment variable. If this variable does not 1.460 + * exist, we default it to "--call-graph". (If you don't want --call-graph but 1.461 + * don't want to pass any other args, define MOZ_PROFILE_PERF_FLAGS to the empty 1.462 + * string.) 1.463 + * 1.464 + * If you include --pid or --output in MOZ_PROFILE_PERF_FLAGS, you're just 1.465 + * asking for trouble. 1.466 + * 1.467 + * Our split-on-spaces logic is lame, so don't expect MOZ_PROFILE_PERF_FLAGS to 1.468 + * work if you pass an argument which includes a space (e.g. 1.469 + * MOZ_PROFILE_PERF_FLAGS="-e 'foo bar'"). 1.470 + */ 1.471 + 1.472 +#include <signal.h> 1.473 +#include <sys/wait.h> 1.474 +#include <unistd.h> 1.475 + 1.476 +static bool perfInitialized = false; 1.477 +static pid_t perfPid = 0; 1.478 + 1.479 +bool js_StartPerf() 1.480 +{ 1.481 + const char *outfile = "mozperf.data"; 1.482 + 1.483 + if (perfPid != 0) { 1.484 + UnsafeError("js_StartPerf: called while perf was already running!\n"); 1.485 + return false; 1.486 + } 1.487 + 1.488 + // Bail if MOZ_PROFILE_WITH_PERF is empty or undefined. 1.489 + if (!getenv("MOZ_PROFILE_WITH_PERF") || 1.490 + !strlen(getenv("MOZ_PROFILE_WITH_PERF"))) { 1.491 + return true; 1.492 + } 1.493 + 1.494 + /* 1.495 + * Delete mozperf.data the first time through -- we're going to append to it 1.496 + * later on, so we want it to be clean when we start out. 1.497 + */ 1.498 + if (!perfInitialized) { 1.499 + perfInitialized = true; 1.500 + unlink(outfile); 1.501 + char cwd[4096]; 1.502 + printf("Writing perf profiling data to %s/%s\n", 1.503 + getcwd(cwd, sizeof(cwd)), outfile); 1.504 + } 1.505 + 1.506 + pid_t mainPid = getpid(); 1.507 + 1.508 + pid_t childPid = fork(); 1.509 + if (childPid == 0) { 1.510 + /* perf record --append --pid $mainPID --output=$outfile $MOZ_PROFILE_PERF_FLAGS */ 1.511 + 1.512 + char mainPidStr[16]; 1.513 + snprintf(mainPidStr, sizeof(mainPidStr), "%d", mainPid); 1.514 + const char *defaultArgs[] = {"perf", "record", "--append", 1.515 + "--pid", mainPidStr, "--output", outfile}; 1.516 + 1.517 + Vector<const char*, 0, SystemAllocPolicy> args; 1.518 + args.append(defaultArgs, ArrayLength(defaultArgs)); 1.519 + 1.520 + const char *flags = getenv("MOZ_PROFILE_PERF_FLAGS"); 1.521 + if (!flags) { 1.522 + flags = "--call-graph"; 1.523 + } 1.524 + 1.525 + char *flags2 = (char *)js_malloc(strlen(flags) + 1); 1.526 + if (!flags2) 1.527 + return false; 1.528 + strcpy(flags2, flags); 1.529 + 1.530 + // Split |flags2| on spaces. (Don't bother to free it -- we're going to 1.531 + // exec anyway.) 1.532 + char *toksave; 1.533 + char *tok = strtok_r(flags2, " ", &toksave); 1.534 + while (tok) { 1.535 + args.append(tok); 1.536 + tok = strtok_r(nullptr, " ", &toksave); 1.537 + } 1.538 + 1.539 + args.append((char*) nullptr); 1.540 + 1.541 + execvp("perf", const_cast<char**>(args.begin())); 1.542 + 1.543 + /* Reached only if execlp fails. */ 1.544 + fprintf(stderr, "Unable to start perf.\n"); 1.545 + exit(1); 1.546 + } 1.547 + else if (childPid > 0) { 1.548 + perfPid = childPid; 1.549 + 1.550 + /* Give perf a chance to warm up. */ 1.551 + usleep(500 * 1000); 1.552 + return true; 1.553 + } 1.554 + else { 1.555 + UnsafeError("js_StartPerf: fork() failed\n"); 1.556 + return false; 1.557 + } 1.558 +} 1.559 + 1.560 +bool js_StopPerf() 1.561 +{ 1.562 + if (perfPid == 0) { 1.563 + UnsafeError("js_StopPerf: perf is not running.\n"); 1.564 + return true; 1.565 + } 1.566 + 1.567 + if (kill(perfPid, SIGINT)) { 1.568 + UnsafeError("js_StopPerf: kill failed\n"); 1.569 + 1.570 + // Try to reap the process anyway. 1.571 + waitpid(perfPid, nullptr, WNOHANG); 1.572 + } 1.573 + else { 1.574 + waitpid(perfPid, nullptr, 0); 1.575 + } 1.576 + 1.577 + perfPid = 0; 1.578 + return true; 1.579 +} 1.580 + 1.581 +#endif /* __linux__ */