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