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