tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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__ */