michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* Profiling-related API */ michael@0: michael@0: #include "builtin/Profilers.h" michael@0: michael@0: #include michael@0: michael@0: #ifdef MOZ_CALLGRIND michael@0: # include michael@0: #endif michael@0: michael@0: #ifdef __APPLE__ michael@0: #ifdef MOZ_INSTRUMENTS michael@0: # include "devtools/Instruments.h" michael@0: #endif michael@0: #ifdef MOZ_SHARK michael@0: # include "devtools/sharkctl.h" michael@0: #endif michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: # include michael@0: # define getpid _getpid michael@0: #endif michael@0: michael@0: #include "vm/Probes.h" michael@0: michael@0: #include "jscntxtinlines.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::ArrayLength; michael@0: michael@0: /* Thread-unsafe error management */ michael@0: michael@0: static char gLastError[2000]; michael@0: michael@0: static void michael@0: #ifdef __GNUC__ michael@0: __attribute__((unused,format(printf,1,2))) michael@0: #endif michael@0: UnsafeError(const char *format, ...) michael@0: { michael@0: va_list args; michael@0: va_start(args, format); michael@0: (void) vsnprintf(gLastError, sizeof(gLastError), format, args); michael@0: va_end(args); michael@0: michael@0: gLastError[sizeof(gLastError) - 1] = '\0'; michael@0: } michael@0: michael@0: JS_PUBLIC_API(const char *) michael@0: JS_UnsafeGetLastProfilingError() michael@0: { michael@0: return gLastError; michael@0: } michael@0: michael@0: #ifdef __APPLE__ michael@0: static bool michael@0: StartOSXProfiling(const char *profileName, pid_t pid) michael@0: { michael@0: bool ok = true; michael@0: const char* profiler = nullptr; michael@0: #ifdef MOZ_SHARK michael@0: ok = Shark::Start(); michael@0: profiler = "Shark"; michael@0: #endif michael@0: #ifdef MOZ_INSTRUMENTS michael@0: ok = Instruments::Start(pid); michael@0: profiler = "Instruments"; michael@0: #endif michael@0: if (!ok) { michael@0: if (profileName) michael@0: UnsafeError("Failed to start %s for %s", profiler, profileName); michael@0: else michael@0: UnsafeError("Failed to start %s", profiler); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_StartProfiling(const char *profileName, pid_t pid) michael@0: { michael@0: bool ok = true; michael@0: #ifdef __APPLE__ michael@0: ok = StartOSXProfiling(profileName, pid); michael@0: #endif michael@0: #ifdef __linux__ michael@0: if (!js_StartPerf()) michael@0: ok = false; michael@0: #endif michael@0: return ok; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_StopProfiling(const char *profileName) michael@0: { michael@0: bool ok = true; michael@0: #ifdef __APPLE__ michael@0: #ifdef MOZ_SHARK michael@0: Shark::Stop(); michael@0: #endif michael@0: #ifdef MOZ_INSTRUMENTS michael@0: Instruments::Stop(profileName); michael@0: #endif michael@0: #endif michael@0: #ifdef __linux__ michael@0: if (!js_StopPerf()) michael@0: ok = false; michael@0: #endif michael@0: return ok; michael@0: } michael@0: michael@0: /* michael@0: * Start or stop whatever platform- and configuration-specific profiling michael@0: * backends are available. michael@0: */ michael@0: static bool michael@0: ControlProfilers(bool toState) michael@0: { michael@0: bool ok = true; michael@0: michael@0: if (! probes::ProfilingActive && toState) { michael@0: #ifdef __APPLE__ michael@0: #if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) michael@0: const char* profiler; michael@0: #ifdef MOZ_SHARK michael@0: ok = Shark::Start(); michael@0: profiler = "Shark"; michael@0: #endif michael@0: #ifdef MOZ_INSTRUMENTS michael@0: ok = Instruments::Resume(); michael@0: profiler = "Instruments"; michael@0: #endif michael@0: if (!ok) { michael@0: UnsafeError("Failed to start %s", profiler); michael@0: } michael@0: #endif michael@0: #endif michael@0: #ifdef MOZ_CALLGRIND michael@0: if (! js_StartCallgrind()) { michael@0: UnsafeError("Failed to start Callgrind"); michael@0: ok = false; michael@0: } michael@0: #endif michael@0: } else if (probes::ProfilingActive && ! toState) { michael@0: #ifdef __APPLE__ michael@0: #ifdef MOZ_SHARK michael@0: Shark::Stop(); michael@0: #endif michael@0: #ifdef MOZ_INSTRUMENTS michael@0: Instruments::Pause(); michael@0: #endif michael@0: #endif michael@0: #ifdef MOZ_CALLGRIND michael@0: if (! js_StopCallgrind()) { michael@0: UnsafeError("failed to stop Callgrind"); michael@0: ok = false; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: probes::ProfilingActive = toState; michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: /* michael@0: * Pause/resume whatever profiling mechanism is currently compiled michael@0: * in, if applicable. This will not affect things like dtrace. michael@0: * michael@0: * Do not mix calls to these APIs with calls to the individual michael@0: * profilers' pause/resume functions, because only overall state is michael@0: * tracked, not the state of each profiler. michael@0: */ michael@0: JS_PUBLIC_API(bool) michael@0: JS_PauseProfilers(const char *profileName) michael@0: { michael@0: return ControlProfilers(false); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_ResumeProfilers(const char *profileName) michael@0: { michael@0: return ControlProfilers(true); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_DumpProfile(const char *outfile, const char *profileName) michael@0: { michael@0: bool ok = true; michael@0: #ifdef MOZ_CALLGRIND michael@0: js_DumpCallgrind(outfile); michael@0: #endif michael@0: return ok; michael@0: } michael@0: michael@0: #ifdef MOZ_PROFILING michael@0: michael@0: struct RequiredStringArg { michael@0: JSContext *mCx; michael@0: char *mBytes; michael@0: RequiredStringArg(JSContext *cx, const CallArgs &args, size_t argi, const char *caller) michael@0: : mCx(cx), mBytes(nullptr) michael@0: { michael@0: if (args.length() <= argi) { michael@0: JS_ReportError(cx, "%s: not enough arguments", caller); michael@0: } else if (!args[argi].isString()) { michael@0: JS_ReportError(cx, "%s: invalid arguments (string expected)", caller); michael@0: } else { michael@0: mBytes = JS_EncodeString(cx, args[argi].toString()); michael@0: } michael@0: } michael@0: operator void*() { michael@0: return (void*) mBytes; michael@0: } michael@0: ~RequiredStringArg() { michael@0: js_free(mBytes); michael@0: } michael@0: }; michael@0: michael@0: static bool michael@0: StartProfiling(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: args.rval().setBoolean(JS_StartProfiling(nullptr, getpid())); michael@0: return true; michael@0: } michael@0: michael@0: RequiredStringArg profileName(cx, args, 0, "startProfiling"); michael@0: if (!profileName) michael@0: return false; michael@0: michael@0: if (args.length() == 1) { michael@0: args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, getpid())); michael@0: return true; michael@0: } michael@0: michael@0: if (!args[1].isInt32()) { michael@0: JS_ReportError(cx, "startProfiling: invalid arguments (int expected)"); michael@0: return false; michael@0: } michael@0: pid_t pid = static_cast(args[1].toInt32()); michael@0: args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, pid)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: StopProfiling(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: args.rval().setBoolean(JS_StopProfiling(nullptr)); michael@0: return true; michael@0: } michael@0: michael@0: RequiredStringArg profileName(cx, args, 0, "stopProfiling"); michael@0: if (!profileName) michael@0: return false; michael@0: args.rval().setBoolean(JS_StopProfiling(profileName.mBytes)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: PauseProfilers(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: args.rval().setBoolean(JS_PauseProfilers(nullptr)); michael@0: return true; michael@0: } michael@0: michael@0: RequiredStringArg profileName(cx, args, 0, "pauseProfiling"); michael@0: if (!profileName) michael@0: return false; michael@0: args.rval().setBoolean(JS_PauseProfilers(profileName.mBytes)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ResumeProfilers(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: args.rval().setBoolean(JS_ResumeProfilers(nullptr)); michael@0: return true; michael@0: } michael@0: michael@0: RequiredStringArg profileName(cx, args, 0, "resumeProfiling"); michael@0: if (!profileName) michael@0: return false; michael@0: args.rval().setBoolean(JS_ResumeProfilers(profileName.mBytes)); michael@0: return true; michael@0: } michael@0: michael@0: /* Usage: DumpProfile([filename[, profileName]]) */ michael@0: static bool michael@0: DumpProfile(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: bool ret; michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: ret = JS_DumpProfile(nullptr, nullptr); michael@0: } else { michael@0: RequiredStringArg filename(cx, args, 0, "dumpProfile"); michael@0: if (!filename) michael@0: return false; michael@0: michael@0: if (args.length() == 1) { michael@0: ret = JS_DumpProfile(filename.mBytes, nullptr); michael@0: } else { michael@0: RequiredStringArg profileName(cx, args, 1, "dumpProfile"); michael@0: if (!profileName) michael@0: return false; michael@0: michael@0: ret = JS_DumpProfile(filename.mBytes, profileName.mBytes); michael@0: } michael@0: } michael@0: michael@0: args.rval().setBoolean(ret); michael@0: return true; michael@0: } michael@0: michael@0: #if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) michael@0: michael@0: static bool michael@0: IgnoreAndReturnTrue(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setBoolean(true); michael@0: return true; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: #ifdef MOZ_CALLGRIND michael@0: static bool michael@0: StartCallgrind(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setBoolean(js_StartCallgrind()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: StopCallgrind(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setBoolean(js_StopCallgrind()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DumpCallgrind(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: args.rval().setBoolean(js_DumpCallgrind(nullptr)); michael@0: return true; michael@0: } michael@0: michael@0: RequiredStringArg outFile(cx, args, 0, "dumpCallgrind"); michael@0: if (!outFile) michael@0: return false; michael@0: michael@0: args.rval().setBoolean(js_DumpCallgrind(outFile.mBytes)); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static const JSFunctionSpec profiling_functions[] = { michael@0: JS_FN("startProfiling", StartProfiling, 1,0), michael@0: JS_FN("stopProfiling", StopProfiling, 1,0), michael@0: JS_FN("pauseProfilers", PauseProfilers, 1,0), michael@0: JS_FN("resumeProfilers", ResumeProfilers, 1,0), michael@0: JS_FN("dumpProfile", DumpProfile, 2,0), michael@0: #if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS) michael@0: /* Keep users of the old shark API happy. */ michael@0: JS_FN("connectShark", IgnoreAndReturnTrue, 0,0), michael@0: JS_FN("disconnectShark", IgnoreAndReturnTrue, 0,0), michael@0: JS_FN("startShark", StartProfiling, 0,0), michael@0: JS_FN("stopShark", StopProfiling, 0,0), michael@0: #endif michael@0: #ifdef MOZ_CALLGRIND michael@0: JS_FN("startCallgrind", StartCallgrind, 0,0), michael@0: JS_FN("stopCallgrind", StopCallgrind, 0,0), michael@0: JS_FN("dumpCallgrind", DumpCallgrind, 1,0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: #endif michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_DefineProfilingFunctions(JSContext *cx, JSObject *objArg) michael@0: { michael@0: RootedObject obj(cx, objArg); michael@0: michael@0: assertSameCompartment(cx, obj); michael@0: #ifdef MOZ_PROFILING michael@0: return JS_DefineFunctions(cx, obj, profiling_functions); michael@0: #else michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: #ifdef MOZ_CALLGRIND michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js_StartCallgrind() michael@0: { michael@0: JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_START_INSTRUMENTATION); michael@0: JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_ZERO_STATS); michael@0: return true; michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js_StopCallgrind() michael@0: { michael@0: JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_STOP_INSTRUMENTATION); michael@0: return true; michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js_DumpCallgrind(const char *outfile) michael@0: { michael@0: if (outfile) { michael@0: JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS_AT(outfile)); michael@0: } else { michael@0: JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #endif /* MOZ_CALLGRIND */ michael@0: michael@0: #ifdef __linux__ michael@0: michael@0: /* michael@0: * Code for starting and stopping |perf|, the Linux profiler. michael@0: * michael@0: * Output from profiling is written to mozperf.data in your cwd. michael@0: * michael@0: * To enable, set MOZ_PROFILE_WITH_PERF=1 in your environment. michael@0: * michael@0: * To pass additional parameters to |perf record|, provide them in the michael@0: * MOZ_PROFILE_PERF_FLAGS environment variable. If this variable does not michael@0: * exist, we default it to "--call-graph". (If you don't want --call-graph but michael@0: * don't want to pass any other args, define MOZ_PROFILE_PERF_FLAGS to the empty michael@0: * string.) michael@0: * michael@0: * If you include --pid or --output in MOZ_PROFILE_PERF_FLAGS, you're just michael@0: * asking for trouble. michael@0: * michael@0: * Our split-on-spaces logic is lame, so don't expect MOZ_PROFILE_PERF_FLAGS to michael@0: * work if you pass an argument which includes a space (e.g. michael@0: * MOZ_PROFILE_PERF_FLAGS="-e 'foo bar'"). michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: static bool perfInitialized = false; michael@0: static pid_t perfPid = 0; michael@0: michael@0: bool js_StartPerf() michael@0: { michael@0: const char *outfile = "mozperf.data"; michael@0: michael@0: if (perfPid != 0) { michael@0: UnsafeError("js_StartPerf: called while perf was already running!\n"); michael@0: return false; michael@0: } michael@0: michael@0: // Bail if MOZ_PROFILE_WITH_PERF is empty or undefined. michael@0: if (!getenv("MOZ_PROFILE_WITH_PERF") || michael@0: !strlen(getenv("MOZ_PROFILE_WITH_PERF"))) { michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Delete mozperf.data the first time through -- we're going to append to it michael@0: * later on, so we want it to be clean when we start out. michael@0: */ michael@0: if (!perfInitialized) { michael@0: perfInitialized = true; michael@0: unlink(outfile); michael@0: char cwd[4096]; michael@0: printf("Writing perf profiling data to %s/%s\n", michael@0: getcwd(cwd, sizeof(cwd)), outfile); michael@0: } michael@0: michael@0: pid_t mainPid = getpid(); michael@0: michael@0: pid_t childPid = fork(); michael@0: if (childPid == 0) { michael@0: /* perf record --append --pid $mainPID --output=$outfile $MOZ_PROFILE_PERF_FLAGS */ michael@0: michael@0: char mainPidStr[16]; michael@0: snprintf(mainPidStr, sizeof(mainPidStr), "%d", mainPid); michael@0: const char *defaultArgs[] = {"perf", "record", "--append", michael@0: "--pid", mainPidStr, "--output", outfile}; michael@0: michael@0: Vector args; michael@0: args.append(defaultArgs, ArrayLength(defaultArgs)); michael@0: michael@0: const char *flags = getenv("MOZ_PROFILE_PERF_FLAGS"); michael@0: if (!flags) { michael@0: flags = "--call-graph"; michael@0: } michael@0: michael@0: char *flags2 = (char *)js_malloc(strlen(flags) + 1); michael@0: if (!flags2) michael@0: return false; michael@0: strcpy(flags2, flags); michael@0: michael@0: // Split |flags2| on spaces. (Don't bother to free it -- we're going to michael@0: // exec anyway.) michael@0: char *toksave; michael@0: char *tok = strtok_r(flags2, " ", &toksave); michael@0: while (tok) { michael@0: args.append(tok); michael@0: tok = strtok_r(nullptr, " ", &toksave); michael@0: } michael@0: michael@0: args.append((char*) nullptr); michael@0: michael@0: execvp("perf", const_cast(args.begin())); michael@0: michael@0: /* Reached only if execlp fails. */ michael@0: fprintf(stderr, "Unable to start perf.\n"); michael@0: exit(1); michael@0: } michael@0: else if (childPid > 0) { michael@0: perfPid = childPid; michael@0: michael@0: /* Give perf a chance to warm up. */ michael@0: usleep(500 * 1000); michael@0: return true; michael@0: } michael@0: else { michael@0: UnsafeError("js_StartPerf: fork() failed\n"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool js_StopPerf() michael@0: { michael@0: if (perfPid == 0) { michael@0: UnsafeError("js_StopPerf: perf is not running.\n"); michael@0: return true; michael@0: } michael@0: michael@0: if (kill(perfPid, SIGINT)) { michael@0: UnsafeError("js_StopPerf: kill failed\n"); michael@0: michael@0: // Try to reap the process anyway. michael@0: waitpid(perfPid, nullptr, WNOHANG); michael@0: } michael@0: else { michael@0: waitpid(perfPid, nullptr, 0); michael@0: } michael@0: michael@0: perfPid = 0; michael@0: return true; michael@0: } michael@0: michael@0: #endif /* __linux__ */