js/src/builtin/Profilers.cpp

changeset 0
6474c204b198
     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__ */

mercurial