summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/Profilers.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/builtin/Profilers.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/Profilers.cpp')
-rw-r--r--js/src/builtin/Profilers.cpp566
1 files changed, 566 insertions, 0 deletions
diff --git a/js/src/builtin/Profilers.cpp b/js/src/builtin/Profilers.cpp
new file mode 100644
index 0000000000..06a825e111
--- /dev/null
+++ b/js/src/builtin/Profilers.cpp
@@ -0,0 +1,566 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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"
+
+#include "mozilla/Compiler.h"
+#include "mozilla/Sprintf.h"
+
+#include <iterator>
+#include <stdarg.h>
+
+#include "util/GetPidProvider.h" // getpid()
+
+#ifdef MOZ_CALLGRIND
+# include <valgrind/callgrind.h>
+#endif
+
+#ifdef __APPLE__
+# ifdef MOZ_INSTRUMENTS
+# include "devtools/Instruments.h"
+# endif
+#endif
+
+#include "js/CharacterEncoding.h"
+#include "js/PropertyAndElement.h" // JS_DefineFunctions
+#include "js/PropertySpec.h"
+#include "js/Utility.h"
+#include "util/Text.h"
+#include "vm/Probes.h"
+
+#include "vm/JSContext-inl.h"
+
+using namespace js;
+
+/* Thread-unsafe error management */
+
+static char gLastError[2000];
+
+#if defined(__APPLE__) || defined(__linux__) || defined(MOZ_CALLGRIND)
+static void MOZ_FORMAT_PRINTF(1, 2) UnsafeError(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ (void)VsprintfLiteral(gLastError, format, args);
+ va_end(args);
+}
+#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
+ ok = js_DumpCallgrind(outfile);
+#endif
+ return ok;
+}
+
+#ifdef MOZ_PROFILING
+
+static UniqueChars RequiredStringArg(JSContext* cx, const CallArgs& args,
+ size_t argi, const char* caller) {
+ if (args.length() <= argi) {
+ JS_ReportErrorASCII(cx, "%s: not enough arguments", caller);
+ return nullptr;
+ }
+
+ if (!args[argi].isString()) {
+ JS_ReportErrorASCII(cx, "%s: invalid arguments (string expected)", caller);
+ return nullptr;
+ }
+
+ return JS_EncodeStringToLatin1(cx, args[argi].toString());
+}
+
+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;
+ }
+
+ UniqueChars profileName = RequiredStringArg(cx, args, 0, "startProfiling");
+ if (!profileName) {
+ return false;
+ }
+
+ if (args.length() == 1) {
+ args.rval().setBoolean(JS_StartProfiling(profileName.get(), getpid()));
+ return true;
+ }
+
+ if (!args[1].isInt32()) {
+ JS_ReportErrorASCII(cx, "startProfiling: invalid arguments (int expected)");
+ return false;
+ }
+ pid_t pid = static_cast<pid_t>(args[1].toInt32());
+ args.rval().setBoolean(JS_StartProfiling(profileName.get(), 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;
+ }
+
+ UniqueChars profileName = RequiredStringArg(cx, args, 0, "stopProfiling");
+ if (!profileName) {
+ return false;
+ }
+ args.rval().setBoolean(JS_StopProfiling(profileName.get()));
+ 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;
+ }
+
+ UniqueChars profileName = RequiredStringArg(cx, args, 0, "pauseProfiling");
+ if (!profileName) {
+ return false;
+ }
+ args.rval().setBoolean(JS_PauseProfilers(profileName.get()));
+ 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;
+ }
+
+ UniqueChars profileName = RequiredStringArg(cx, args, 0, "resumeProfiling");
+ if (!profileName) {
+ return false;
+ }
+ args.rval().setBoolean(JS_ResumeProfilers(profileName.get()));
+ 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 {
+ UniqueChars filename = RequiredStringArg(cx, args, 0, "dumpProfile");
+ if (!filename) {
+ return false;
+ }
+
+ if (args.length() == 1) {
+ ret = JS_DumpProfile(filename.get(), nullptr);
+ } else {
+ UniqueChars profileName = RequiredStringArg(cx, args, 1, "dumpProfile");
+ if (!profileName) {
+ return false;
+ }
+
+ ret = JS_DumpProfile(filename.get(), profileName.get());
+ }
+ }
+
+ args.rval().setBoolean(ret);
+ return true;
+}
+
+static bool GetMaxGCPauseSinceClear(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setNumber(
+ cx->runtime()->gc.stats().getMaxGCPauseSinceClear().ToMicroseconds());
+ return true;
+}
+
+static bool ClearMaxGCPauseAccumulator(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setNumber(
+ cx->runtime()->gc.stats().clearMaxGCPauseAccumulator().ToMicroseconds());
+ 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;
+ }
+
+ UniqueChars outFile = RequiredStringArg(cx, args, 0, "dumpCallgrind");
+ if (!outFile) {
+ return false;
+ }
+
+ args.rval().setBoolean(js_DumpCallgrind(outFile.get()));
+ 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) {
+ cx->check(obj);
+#ifdef MOZ_PROFILING
+ return JS_DefineFunctions(cx, obj, profiling_functions);
+#else
+ return true;
+#endif
+}
+
+#ifdef MOZ_CALLGRIND
+
+/* Wrapper for various macros to stop warnings coming from their expansions. */
+# if defined(__clang__)
+# define JS_SILENCE_UNUSED_VALUE_IN_EXPR(expr) \
+ JS_BEGIN_MACRO \
+ _Pragma("clang diagnostic push") /* If these _Pragmas cause warnings \
+ for you, try disabling ccache. */ \
+ _Pragma("clang diagnostic ignored \"-Wunused-value\"") { \
+ expr; \
+ } \
+ _Pragma("clang diagnostic pop") \
+ JS_END_MACRO
+# elif MOZ_IS_GCC
+
+# define JS_SILENCE_UNUSED_VALUE_IN_EXPR(expr) \
+ JS_BEGIN_MACRO \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wunused-but-set-variable\"") \
+ expr; \
+ _Pragma("GCC diagnostic pop") \
+ JS_END_MACRO
+# endif
+
+# if !defined(JS_SILENCE_UNUSED_VALUE_IN_EXPR)
+# define JS_SILENCE_UNUSED_VALUE_IN_EXPR(expr) \
+ JS_BEGIN_MACRO \
+ expr; \
+ JS_END_MACRO
+# endif
+
+JS_PUBLIC_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_PUBLIC_API bool js_StopCallgrind() {
+ JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_STOP_INSTRUMENTATION);
+ return true;
+}
+
+JS_PUBLIC_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 (!getenv("MOZ_PROFILE_WITH_PERF") ||
+ !strlen(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 --pid $mainPID --output=$outfile $MOZ_PROFILE_PERF_FLAGS */
+
+ char mainPidStr[16];
+ SprintfLiteral(mainPidStr, "%d", mainPid);
+ const char* defaultArgs[] = {"perf", "record", "--pid",
+ mainPidStr, "--output", outfile};
+
+ Vector<const char*, 0, SystemAllocPolicy> args;
+ if (!args.append(defaultArgs, std::size(defaultArgs))) {
+ return false;
+ }
+
+ const char* flags = getenv("MOZ_PROFILE_PERF_FLAGS");
+ if (!flags) {
+ flags = "--call-graph";
+ }
+
+ UniqueChars flags2 = DuplicateString(flags);
+ if (!flags2) {
+ return false;
+ }
+
+ // Split |flags2| on spaces.
+ char* toksave;
+ char* tok = strtok_r(flags2.get(), " ", &toksave);
+ while (tok) {
+ if (!args.append(tok)) {
+ return false;
+ }
+ tok = strtok_r(nullptr, " ", &toksave);
+ }
+
+ if (!args.append((char*)nullptr)) {
+ return false;
+ }
+
+ execvp("perf", const_cast<char**>(args.begin()));
+
+ /* Reached only if execlp fails. */
+ fprintf(stderr, "Unable to start perf.\n");
+ exit(1);
+ }
+ if (childPid > 0) {
+ perfPid = childPid;
+
+ /* Give perf a chance to warm up. */
+ usleep(500 * 1000);
+ return true;
+ }
+ 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__ */