LCOV - code coverage report
Current view: top level - js/src/builtin - Profilers.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 3 170 1.8 %
Date: 2017-07-14 16:53:18 Functions: 1 21 4.8 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13