summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core/platform.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/core/platform.cpp')
-rw-r--r--tools/profiler/core/platform.cpp1266
1 files changed, 1266 insertions, 0 deletions
diff --git a/tools/profiler/core/platform.cpp b/tools/profiler/core/platform.cpp
new file mode 100644
index 000000000..0d3cb1648
--- /dev/null
+++ b/tools/profiler/core/platform.cpp
@@ -0,0 +1,1266 @@
+/* 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/. */
+
+#include <ostream>
+#include <fstream>
+#include <sstream>
+#include <errno.h>
+
+#include "platform.h"
+#include "PlatformMacros.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "GeckoProfiler.h"
+#ifndef SPS_STANDALONE
+#include "ProfilerIOInterposeObserver.h"
+#include "mozilla/StaticPtr.h"
+#endif
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Sprintf.h"
+#include "PseudoStack.h"
+#include "GeckoSampler.h"
+#ifndef SPS_STANDALONE
+#include "nsIObserverService.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsXULAppAPI.h"
+#include "nsProfilerStartParams.h"
+#include "mozilla/Services.h"
+#include "nsThreadUtils.h"
+#endif
+#include "ProfilerMarkers.h"
+
+#ifdef MOZ_TASK_TRACER
+#include "GeckoTaskTracer.h"
+#endif
+
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+ #include "FennecJNIWrappers.h"
+#endif
+
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+#include "FennecJNINatives.h"
+#endif
+
+#ifndef SPS_STANDALONE
+#if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux)
+# define USE_LUL_STACKWALK
+# include "lul/LulMain.h"
+# include "lul/platform-linux-lul.h"
+#endif
+#endif
+
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+class GeckoJavaSampler : public java::GeckoJavaSampler::Natives<GeckoJavaSampler>
+{
+private:
+ GeckoJavaSampler();
+
+public:
+ static double GetProfilerTime() {
+ if (!profiler_is_active()) {
+ return 0.0;
+ }
+ return profiler_time();
+ };
+};
+#endif
+
+MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack;
+MOZ_THREAD_LOCAL(GeckoSampler *) tlsTicker;
+MOZ_THREAD_LOCAL(void *) tlsStackTop;
+// We need to track whether we've been initialized otherwise
+// we end up using tlsStack without initializing it.
+// Because tlsStack is totally opaque to us we can't reuse
+// it as the flag itself.
+bool stack_key_initialized;
+
+mozilla::TimeStamp sLastTracerEvent; // is raced on
+mozilla::TimeStamp sStartTime;
+int sFrameNumber = 0;
+int sLastFrameNumber = 0;
+int sInitCount = 0; // Each init must have a matched shutdown.
+static bool sIsProfiling = false; // is raced on
+static bool sIsGPUProfiling = false; // is raced on
+static bool sIsLayersDump = false; // is raced on
+static bool sIsDisplayListDump = false; // is raced on
+static bool sIsRestyleProfiling = false; // is raced on
+
+// Environment variables to control the profiler
+const char* PROFILER_HELP = "MOZ_PROFILER_HELP";
+const char* PROFILER_INTERVAL = "MOZ_PROFILER_INTERVAL";
+const char* PROFILER_ENTRIES = "MOZ_PROFILER_ENTRIES";
+const char* PROFILER_STACK = "MOZ_PROFILER_STACK_SCAN";
+const char* PROFILER_FEATURES = "MOZ_PROFILING_FEATURES";
+
+/* we don't need to worry about overflow because we only treat the
+ * case of them being the same as special. i.e. we only run into
+ * a problem if 2^32 events happen between samples that we need
+ * to know are associated with different events */
+
+// Values harvested from env vars, that control the profiler.
+static int sUnwindInterval; /* in milliseconds */
+static int sUnwindStackScan; /* max # of dubious frames allowed */
+static int sProfileEntries; /* how many entries do we store? */
+
+std::vector<ThreadInfo*>* Sampler::sRegisteredThreads = nullptr;
+mozilla::UniquePtr< ::Mutex> Sampler::sRegisteredThreadsMutex;
+
+GeckoSampler* Sampler::sActiveSampler;
+
+#ifndef SPS_STANDALONE
+static mozilla::StaticAutoPtr<mozilla::ProfilerIOInterposeObserver>
+ sInterposeObserver;
+#endif
+
+// The name that identifies the gecko thread for calls to
+// profiler_register_thread.
+static const char * gGeckoThreadName = "GeckoMain";
+
+void Sampler::Startup() {
+ sRegisteredThreads = new std::vector<ThreadInfo*>();
+ sRegisteredThreadsMutex = OS::CreateMutex("sRegisteredThreads mutex");
+
+ // We could create the sLUL object and read unwind info into it at
+ // this point. That would match the lifetime implied by destruction
+ // of it in Sampler::Shutdown just below. However, that gives a big
+ // delay on startup, even if no profiling is actually to be done.
+ // So, instead, sLUL is created on demand at the first call to
+ // Sampler::Start.
+}
+
+void Sampler::Shutdown() {
+ while (sRegisteredThreads->size() > 0) {
+ delete sRegisteredThreads->back();
+ sRegisteredThreads->pop_back();
+ }
+
+ sRegisteredThreadsMutex = nullptr;
+ delete sRegisteredThreads;
+
+ // UnregisterThread can be called after shutdown in XPCShell. Thus
+ // we need to point to null to ignore such a call after shutdown.
+ sRegisteredThreadsMutex = nullptr;
+ sRegisteredThreads = nullptr;
+
+#if defined(USE_LUL_STACKWALK)
+ // Delete the sLUL object, if it actually got created.
+ if (sLUL) {
+ delete sLUL;
+ sLUL = nullptr;
+ }
+#endif
+}
+
+StackOwningThreadInfo::StackOwningThreadInfo(const char* aName, int aThreadId,
+ bool aIsMainThread,
+ PseudoStack* aPseudoStack,
+ void* aStackTop)
+ : ThreadInfo(aName, aThreadId, aIsMainThread, aPseudoStack, aStackTop)
+{
+ aPseudoStack->ref();
+}
+
+StackOwningThreadInfo::~StackOwningThreadInfo()
+{
+ PseudoStack* stack = Stack();
+ if (stack) {
+ stack->deref();
+ }
+}
+
+void
+StackOwningThreadInfo::SetPendingDelete()
+{
+ PseudoStack* stack = Stack();
+ if (stack) {
+ stack->deref();
+ }
+ ThreadInfo::SetPendingDelete();
+}
+
+ProfilerMarker::ProfilerMarker(const char* aMarkerName,
+ ProfilerMarkerPayload* aPayload,
+ double aTime)
+ : mMarkerName(strdup(aMarkerName))
+ , mPayload(aPayload)
+ , mTime(aTime)
+{
+}
+
+ProfilerMarker::~ProfilerMarker() {
+ free(mMarkerName);
+ delete mPayload;
+}
+
+void
+ProfilerMarker::SetGeneration(uint32_t aGenID) {
+ mGenID = aGenID;
+}
+
+double
+ProfilerMarker::GetTime() const {
+ return mTime;
+}
+
+void ProfilerMarker::StreamJSON(SpliceableJSONWriter& aWriter,
+ UniqueStacks& aUniqueStacks) const
+{
+ // Schema:
+ // [name, time, data]
+
+ aWriter.StartArrayElement();
+ {
+ aUniqueStacks.mUniqueStrings.WriteElement(aWriter, GetMarkerName());
+ aWriter.DoubleElement(mTime);
+ // TODO: Store the callsite for this marker if available:
+ // if have location data
+ // b.NameValue(marker, "location", ...);
+ if (mPayload) {
+ aWriter.StartObjectElement();
+ {
+ mPayload->StreamPayload(aWriter, aUniqueStacks);
+ }
+ aWriter.EndObject();
+ }
+ }
+ aWriter.EndArray();
+}
+
+/* Has MOZ_PROFILER_VERBOSE been set? */
+
+// Verbosity control for the profiler. The aim is to check env var
+// MOZ_PROFILER_VERBOSE only once. However, we may need to temporarily
+// override that so as to print the profiler's help message. That's
+// what moz_profiler_set_verbosity is for.
+
+enum class ProfilerVerbosity : int8_t { UNCHECKED, NOTVERBOSE, VERBOSE };
+
+// Raced on, potentially
+static ProfilerVerbosity profiler_verbosity = ProfilerVerbosity::UNCHECKED;
+
+bool moz_profiler_verbose()
+{
+ if (profiler_verbosity == ProfilerVerbosity::UNCHECKED) {
+ if (getenv("MOZ_PROFILER_VERBOSE") != nullptr)
+ profiler_verbosity = ProfilerVerbosity::VERBOSE;
+ else
+ profiler_verbosity = ProfilerVerbosity::NOTVERBOSE;
+ }
+
+ return profiler_verbosity == ProfilerVerbosity::VERBOSE;
+}
+
+void moz_profiler_set_verbosity(ProfilerVerbosity pv)
+{
+ MOZ_ASSERT(pv == ProfilerVerbosity::UNCHECKED ||
+ pv == ProfilerVerbosity::VERBOSE);
+ profiler_verbosity = pv;
+}
+
+
+bool set_profiler_interval(const char* interval) {
+ if (interval) {
+ errno = 0;
+ long int n = strtol(interval, (char**)nullptr, 10);
+ if (errno == 0 && n >= 1 && n <= 1000) {
+ sUnwindInterval = n;
+ return true;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool set_profiler_entries(const char* entries) {
+ if (entries) {
+ errno = 0;
+ long int n = strtol(entries, (char**)nullptr, 10);
+ if (errno == 0 && n > 0) {
+ sProfileEntries = n;
+ return true;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool set_profiler_scan(const char* scanCount) {
+ if (scanCount) {
+ errno = 0;
+ long int n = strtol(scanCount, (char**)nullptr, 10);
+ if (errno == 0 && n >= 0 && n <= 100) {
+ sUnwindStackScan = n;
+ return true;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool is_native_unwinding_avail() {
+# if defined(HAVE_NATIVE_UNWIND)
+ return true;
+#else
+ return false;
+#endif
+}
+
+// Read env vars at startup, so as to set:
+// sUnwindInterval, sProfileEntries, sUnwindStackScan.
+void read_profiler_env_vars()
+{
+ /* Set defaults */
+ sUnwindInterval = 0; /* We'll have to look elsewhere */
+ sProfileEntries = 0;
+
+ const char* interval = getenv(PROFILER_INTERVAL);
+ const char* entries = getenv(PROFILER_ENTRIES);
+ const char* scanCount = getenv(PROFILER_STACK);
+
+ if (getenv(PROFILER_HELP)) {
+ // Enable verbose output
+ moz_profiler_set_verbosity(ProfilerVerbosity::VERBOSE);
+ profiler_usage();
+ // Now force the next enquiry of moz_profiler_verbose to re-query
+ // env var MOZ_PROFILER_VERBOSE.
+ moz_profiler_set_verbosity(ProfilerVerbosity::UNCHECKED);
+ }
+
+ if (!set_profiler_interval(interval) ||
+ !set_profiler_entries(entries) ||
+ !set_profiler_scan(scanCount)) {
+ profiler_usage();
+ } else {
+ LOG( "SPS:");
+ LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")",
+ (int)sUnwindInterval);
+ LOGF("SPS: Entry store size = %d (zero means \"platform default\")",
+ (int)sProfileEntries);
+ LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).",
+ (int)sUnwindStackScan);
+ LOG( "SPS:");
+ }
+}
+
+void profiler_usage() {
+ LOG( "SPS: ");
+ LOG( "SPS: Environment variable usage:");
+ LOG( "SPS: ");
+ LOG( "SPS: MOZ_PROFILER_HELP");
+ LOG( "SPS: If set to any value, prints this message.");
+ LOG( "SPS: ");
+ LOG( "SPS: MOZ_PROFILER_INTERVAL=<number> (milliseconds, 1 to 1000)");
+ LOG( "SPS: If unset, platform default is used.");
+ LOG( "SPS: ");
+ LOG( "SPS: MOZ_PROFILER_ENTRIES=<number> (count, minimum of 1)");
+ LOG( "SPS: If unset, platform default is used.");
+ LOG( "SPS: ");
+ LOG( "SPS: MOZ_PROFILER_VERBOSE");
+ LOG( "SPS: If set to any value, increases verbosity (recommended).");
+ LOG( "SPS: ");
+ LOG( "SPS: MOZ_PROFILER_STACK_SCAN=<number> (default is zero)");
+ LOG( "SPS: The number of dubious (stack-scanned) frames allowed");
+ LOG( "SPS: ");
+ LOG( "SPS: MOZ_PROFILER_LUL_TEST");
+ LOG( "SPS: If set to any value, runs LUL unit tests at startup of");
+ LOG( "SPS: the unwinder thread, and prints a short summary of results.");
+ LOG( "SPS: ");
+ LOGF("SPS: This platform %s native unwinding.",
+ is_native_unwinding_avail() ? "supports" : "does not support");
+ LOG( "SPS: ");
+
+ /* Re-set defaults */
+ sUnwindInterval = 0; /* We'll have to look elsewhere */
+ sProfileEntries = 0;
+ sUnwindStackScan = 0;
+
+ LOG( "SPS:");
+ LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")",
+ (int)sUnwindInterval);
+ LOGF("SPS: Entry store size = %d (zero means \"platform default\")",
+ (int)sProfileEntries);
+ LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).",
+ (int)sUnwindStackScan);
+ LOG( "SPS:");
+
+ return;
+}
+
+void set_tls_stack_top(void* stackTop)
+{
+ // Round |stackTop| up to the end of the containing page. We may
+ // as well do this -- there's no danger of a fault, and we might
+ // get a few more base-of-the-stack frames as a result. This
+ // assumes that no target has a page size smaller than 4096.
+ uintptr_t stackTopR = (uintptr_t)stackTop;
+ if (stackTop) {
+ stackTopR = (stackTopR & ~(uintptr_t)4095) + (uintptr_t)4095;
+ }
+ tlsStackTop.set((void*)stackTopR);
+}
+
+bool is_main_thread_name(const char* aName) {
+ if (!aName) {
+ return false;
+ }
+ return strcmp(aName, gGeckoThreadName) == 0;
+}
+
+#ifndef SPS_STANDALONE
+#ifdef HAVE_VA_COPY
+#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar)
+#elif defined(HAVE_VA_LIST_AS_ARRAY)
+#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0]
+#else
+#define VARARGS_ASSIGN(foo, bar) (foo) = (bar)
+#endif
+
+void
+mozilla_sampler_log(const char *fmt, va_list args)
+{
+ if (profiler_is_active()) {
+ // nsAutoCString AppendPrintf would be nicer but
+ // this is mozilla external code
+ char buf[2048];
+ va_list argsCpy;
+ VARARGS_ASSIGN(argsCpy, args);
+ int required = VsprintfLiteral(buf, fmt, argsCpy);
+ va_end(argsCpy);
+
+ if (required < 0) {
+ return; // silently drop for now
+ } else if (required < 2048) {
+ profiler_tracing("log", buf, TRACING_EVENT);
+ } else {
+ char* heapBuf = new char[required+1];
+ va_list argsCpy;
+ VARARGS_ASSIGN(argsCpy, args);
+ vsnprintf(heapBuf, required+1, fmt, argsCpy);
+ va_end(argsCpy);
+ // EVENT_BACKTRACE could be used to get a source
+ // for all log events. This could be a runtime
+ // flag later.
+ profiler_tracing("log", heapBuf, TRACING_EVENT);
+ delete[] heapBuf;
+ }
+ }
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////
+// BEGIN externally visible functions
+
+void mozilla_sampler_init(void* stackTop)
+{
+ sInitCount++;
+
+ if (stack_key_initialized)
+ return;
+
+#ifdef MOZ_TASK_TRACER
+ mozilla::tasktracer::InitTaskTracer();
+#endif
+
+#ifdef SPS_STANDALONE
+ mozilla::TimeStamp::Startup();
+#endif
+
+ LOG("BEGIN mozilla_sampler_init");
+ if (!tlsPseudoStack.init() || !tlsTicker.init() || !tlsStackTop.init()) {
+ LOG("Failed to init.");
+ return;
+ }
+ bool ignore;
+ sStartTime = mozilla::TimeStamp::ProcessCreation(ignore);
+
+ stack_key_initialized = true;
+
+ Sampler::Startup();
+
+ PseudoStack *stack = PseudoStack::create();
+ tlsPseudoStack.set(stack);
+
+ bool isMainThread = true;
+ Sampler::RegisterCurrentThread(isMainThread ?
+ gGeckoThreadName : "Application Thread",
+ stack, isMainThread, stackTop);
+
+ // Read interval settings from MOZ_PROFILER_INTERVAL and stack-scan
+ // threshhold from MOZ_PROFILER_STACK_SCAN.
+ read_profiler_env_vars();
+
+ // platform specific initialization
+ OS::Startup();
+
+#ifndef SPS_STANDALONE
+ set_stderr_callback(mozilla_sampler_log);
+#endif
+
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+ if (mozilla::jni::IsFennec()) {
+ GeckoJavaSampler::Init();
+ }
+#endif
+
+ // We can't open pref so we use an environment variable
+ // to know if we should trigger the profiler on startup
+ // NOTE: Default
+ const char *val = getenv("MOZ_PROFILER_STARTUP");
+ if (!val || !*val) {
+ return;
+ }
+
+ const char* features[] = {"js"
+ , "leaf"
+ , "threads"
+#if defined(XP_WIN) || defined(XP_MACOSX) \
+ || (defined(SPS_ARCH_arm) && defined(linux)) \
+ || defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux)
+ , "stackwalk"
+#endif
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+ , "java"
+#endif
+ };
+
+ const char* threadFilters[] = { "GeckoMain", "Compositor" };
+
+ profiler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
+ features, MOZ_ARRAY_LENGTH(features),
+ threadFilters, MOZ_ARRAY_LENGTH(threadFilters));
+ LOG("END mozilla_sampler_init");
+}
+
+void mozilla_sampler_shutdown()
+{
+ sInitCount--;
+
+ if (sInitCount > 0)
+ return;
+
+ // Save the profile on shutdown if requested.
+ GeckoSampler *t = tlsTicker.get();
+ if (t) {
+ const char *val = getenv("MOZ_PROFILER_SHUTDOWN");
+ if (val) {
+ std::ofstream stream;
+ stream.open(val);
+ if (stream.is_open()) {
+ t->ToStreamAsJSON(stream);
+ stream.close();
+ }
+ }
+ }
+
+ profiler_stop();
+
+#ifndef SPS_STANDALONE
+ set_stderr_callback(nullptr);
+#endif
+
+ Sampler::Shutdown();
+
+#ifdef SPS_STANDALONE
+ mozilla::TimeStamp::Shutdown();
+#endif
+
+ PseudoStack *stack = tlsPseudoStack.get();
+ stack->deref();
+ tlsPseudoStack.set(nullptr);
+
+#ifdef MOZ_TASK_TRACER
+ mozilla::tasktracer::ShutdownTaskTracer();
+#endif
+}
+
+void mozilla_sampler_save()
+{
+ GeckoSampler *t = tlsTicker.get();
+ if (!t) {
+ return;
+ }
+
+ t->RequestSave();
+ // We're on the main thread already so we don't
+ // have to wait to handle the save request.
+ t->HandleSaveRequest();
+}
+
+mozilla::UniquePtr<char[]> mozilla_sampler_get_profile(double aSinceTime)
+{
+ GeckoSampler *t = tlsTicker.get();
+ if (!t) {
+ return nullptr;
+ }
+
+ return t->ToJSON(aSinceTime);
+}
+
+#ifndef SPS_STANDALONE
+JSObject *mozilla_sampler_get_profile_data(JSContext *aCx, double aSinceTime)
+{
+ GeckoSampler *t = tlsTicker.get();
+ if (!t) {
+ return nullptr;
+ }
+
+ return t->ToJSObject(aCx, aSinceTime);
+}
+
+void mozilla_sampler_get_profile_data_async(double aSinceTime,
+ mozilla::dom::Promise* aPromise)
+{
+ GeckoSampler *t = tlsTicker.get();
+ if (NS_WARN_IF(!t)) {
+ return;
+ }
+
+ t->ToJSObjectAsync(aSinceTime, aPromise);
+}
+
+void mozilla_sampler_get_profiler_start_params(int* aEntrySize,
+ double* aInterval,
+ mozilla::Vector<const char*>* aFilters,
+ mozilla::Vector<const char*>* aFeatures)
+{
+ if (NS_WARN_IF(!aEntrySize) || NS_WARN_IF(!aInterval) ||
+ NS_WARN_IF(!aFilters) || NS_WARN_IF(!aFeatures)) {
+ return;
+ }
+
+ GeckoSampler *t = tlsTicker.get();
+ if (NS_WARN_IF(!t)) {
+ return;
+ }
+
+ *aEntrySize = t->EntrySize();
+ *aInterval = t->interval();
+
+ const ThreadNameFilterList& threadNameFilterList = t->ThreadNameFilters();
+ MOZ_ALWAYS_TRUE(aFilters->resize(threadNameFilterList.length()));
+ for (uint32_t i = 0; i < threadNameFilterList.length(); ++i) {
+ (*aFilters)[i] = threadNameFilterList[i].c_str();
+ }
+
+ const FeatureList& featureList = t->Features();
+ MOZ_ALWAYS_TRUE(aFeatures->resize(featureList.length()));
+ for (size_t i = 0; i < featureList.length(); ++i) {
+ (*aFeatures)[i] = featureList[i].c_str();
+ }
+}
+
+void mozilla_sampler_get_gatherer(nsISupports** aRetVal)
+{
+ if (!aRetVal) {
+ return;
+ }
+
+ if (NS_WARN_IF(!profiler_is_active())) {
+ *aRetVal = nullptr;
+ return;
+ }
+
+ GeckoSampler *t = tlsTicker.get();
+ if (NS_WARN_IF(!t)) {
+ *aRetVal = nullptr;
+ return;
+ }
+
+ t->GetGatherer(aRetVal);
+}
+
+#endif
+
+void mozilla_sampler_save_profile_to_file(const char* aFilename)
+{
+ GeckoSampler *t = tlsTicker.get();
+ if (!t) {
+ return;
+ }
+
+ std::ofstream stream;
+ stream.open(aFilename);
+ if (stream.is_open()) {
+ t->ToStreamAsJSON(stream);
+ stream.close();
+ LOGF("Saved to %s", aFilename);
+ } else {
+ LOG("Fail to open profile log file.");
+ }
+}
+
+
+const char** mozilla_sampler_get_features()
+{
+ static const char* features[] = {
+#if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND)
+ // Walk the C++ stack.
+ "stackwalk",
+#endif
+#if defined(ENABLE_SPS_LEAF_DATA)
+ // Include the C++ leaf node if not stackwalking. DevTools
+ // profiler doesn't want the native addresses.
+ "leaf",
+#endif
+#if !defined(SPS_OS_windows)
+ // Use a seperate thread of walking the stack.
+ "unwinder",
+#endif
+ "java",
+ // Only record samples during periods of bad responsiveness
+ "jank",
+ // Tell the JS engine to emmit pseudostack entries in the
+ // pro/epilogue.
+ "js",
+ // GPU Profiling (may not be supported by the GL)
+ "gpu",
+ // Profile the registered secondary threads.
+ "threads",
+ // Do not include user-identifiable information
+ "privacy",
+ // Dump the layer tree with the textures.
+ "layersdump",
+ // Dump the display list with the textures.
+ "displaylistdump",
+ // Add main thread I/O to the profile
+ "mainthreadio",
+ // Add RSS collection
+ "memory",
+#ifdef MOZ_TASK_TRACER
+ // Start profiling with feature TaskTracer.
+ "tasktracer",
+#endif
+#if defined(XP_WIN)
+ // Add power collection
+ "power",
+#endif
+ nullptr
+ };
+
+ return features;
+}
+
+void mozilla_sampler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize,
+ uint32_t *aGeneration)
+{
+ *aCurrentPosition = 0;
+ *aTotalSize = 0;
+ *aGeneration = 0;
+
+ if (!stack_key_initialized)
+ return;
+
+ GeckoSampler *t = tlsTicker.get();
+ if (!t)
+ return;
+
+ t->GetBufferInfo(aCurrentPosition, aTotalSize, aGeneration);
+}
+
+// Values are only honored on the first start
+void mozilla_sampler_start(int aProfileEntries, double aInterval,
+ const char** aFeatures, uint32_t aFeatureCount,
+ const char** aThreadNameFilters, uint32_t aFilterCount)
+
+{
+ LOG("BEGIN mozilla_sampler_start");
+
+ if (!stack_key_initialized)
+ profiler_init(nullptr);
+
+ /* If the sampling interval was set using env vars, use that
+ in preference to anything else. */
+ if (sUnwindInterval > 0)
+ aInterval = sUnwindInterval;
+
+ /* If the entry count was set using env vars, use that, too: */
+ if (sProfileEntries > 0)
+ aProfileEntries = sProfileEntries;
+
+ // Reset the current state if the profiler is running
+ profiler_stop();
+
+ GeckoSampler* t;
+ t = new GeckoSampler(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
+ aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
+ aFeatures, aFeatureCount,
+ aThreadNameFilters, aFilterCount);
+
+ tlsTicker.set(t);
+ t->Start();
+ if (t->ProfileJS() || t->InPrivacyMode()) {
+ ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
+ std::vector<ThreadInfo*> threads = t->GetRegisteredThreads();
+
+ for (uint32_t i = 0; i < threads.size(); i++) {
+ ThreadInfo* info = threads[i];
+ if (info->IsPendingDelete()) {
+ continue;
+ }
+ ThreadProfile* thread_profile = info->Profile();
+ if (!thread_profile) {
+ continue;
+ }
+ thread_profile->GetPseudoStack()->reinitializeOnResume();
+#ifndef SPS_STANDALONE
+ if (t->ProfileJS()) {
+ thread_profile->GetPseudoStack()->enableJSSampling();
+ }
+ if (t->InPrivacyMode()) {
+ thread_profile->GetPseudoStack()->mPrivacyMode = true;
+ }
+#endif
+ }
+ }
+
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+ if (t->ProfileJava()) {
+ int javaInterval = aInterval;
+ // Java sampling doesn't accuratly keep up with 1ms sampling
+ if (javaInterval < 10) {
+ aInterval = 10;
+ }
+ java::GeckoJavaSampler::Start(javaInterval, 1000);
+ }
+#endif
+
+#ifndef SPS_STANDALONE
+ if (t->AddMainThreadIO()) {
+ if (!sInterposeObserver) {
+ // Lazily create IO interposer observer
+ sInterposeObserver = new mozilla::ProfilerIOInterposeObserver();
+ }
+ mozilla::IOInterposer::Register(mozilla::IOInterposeObserver::OpAll,
+ sInterposeObserver);
+ }
+#endif
+
+ sIsProfiling = true;
+#ifndef SPS_STANDALONE
+ sIsGPUProfiling = t->ProfileGPU();
+ sIsLayersDump = t->LayersDump();
+ sIsDisplayListDump = t->DisplayListDump();
+ sIsRestyleProfiling = t->ProfileRestyle();
+
+ if (Sampler::CanNotifyObservers()) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsTArray<nsCString> featuresArray;
+ nsTArray<nsCString> threadNameFiltersArray;
+
+ for (size_t i = 0; i < aFeatureCount; ++i) {
+ featuresArray.AppendElement(aFeatures[i]);
+ }
+
+ for (size_t i = 0; i < aFilterCount; ++i) {
+ threadNameFiltersArray.AppendElement(aThreadNameFilters[i]);
+ }
+
+ nsCOMPtr<nsIProfilerStartParams> params =
+ new nsProfilerStartParams(aProfileEntries, aInterval, featuresArray,
+ threadNameFiltersArray);
+
+ os->NotifyObservers(params, "profiler-started", nullptr);
+ }
+ }
+#endif
+
+ LOG("END mozilla_sampler_start");
+}
+
+void mozilla_sampler_stop()
+{
+ LOG("BEGIN mozilla_sampler_stop");
+
+ if (!stack_key_initialized)
+ return;
+
+ GeckoSampler *t = tlsTicker.get();
+ if (!t) {
+ LOG("END mozilla_sampler_stop-early");
+ return;
+ }
+
+ bool disableJS = t->ProfileJS();
+
+ t->Stop();
+ delete t;
+ tlsTicker.set(nullptr);
+
+#ifndef SPS_STANDALONE
+ if (disableJS) {
+ PseudoStack *stack = tlsPseudoStack.get();
+ ASSERT(stack != nullptr);
+ stack->disableJSSampling();
+ }
+
+ mozilla::IOInterposer::Unregister(mozilla::IOInterposeObserver::OpAll,
+ sInterposeObserver);
+ sInterposeObserver = nullptr;
+#endif
+
+ sIsProfiling = false;
+#ifndef SPS_STANDALONE
+ sIsGPUProfiling = false;
+ sIsLayersDump = false;
+ sIsDisplayListDump = false;
+ sIsRestyleProfiling = false;
+
+ if (Sampler::CanNotifyObservers()) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os)
+ os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
+ }
+#endif
+
+ LOG("END mozilla_sampler_stop");
+}
+
+bool mozilla_sampler_is_paused() {
+ if (Sampler::GetActiveSampler()) {
+ return Sampler::GetActiveSampler()->IsPaused();
+ } else {
+ return false;
+ }
+}
+
+void mozilla_sampler_pause() {
+ if (Sampler::GetActiveSampler()) {
+ Sampler::GetActiveSampler()->SetPaused(true);
+#ifndef SPS_STANDALONE
+ if (Sampler::CanNotifyObservers()) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os)
+ os->NotifyObservers(nullptr, "profiler-paused", nullptr);
+ }
+#endif
+ }
+}
+
+void mozilla_sampler_resume() {
+ if (Sampler::GetActiveSampler()) {
+ Sampler::GetActiveSampler()->SetPaused(false);
+#ifndef SPS_STANDALONE
+ if (Sampler::CanNotifyObservers()) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os)
+ os->NotifyObservers(nullptr, "profiler-resumed", nullptr);
+ }
+#endif
+ }
+}
+
+bool mozilla_sampler_feature_active(const char* aName)
+{
+ if (!profiler_is_active()) {
+ return false;
+ }
+
+ if (strcmp(aName, "gpu") == 0) {
+ return sIsGPUProfiling;
+ }
+
+ if (strcmp(aName, "layersdump") == 0) {
+ return sIsLayersDump;
+ }
+
+ if (strcmp(aName, "displaylistdump") == 0) {
+ return sIsDisplayListDump;
+ }
+
+ if (strcmp(aName, "restyle") == 0) {
+ return sIsRestyleProfiling;
+ }
+
+ return false;
+}
+
+bool mozilla_sampler_is_active()
+{
+ return sIsProfiling;
+}
+
+void mozilla_sampler_responsiveness(const mozilla::TimeStamp& aTime)
+{
+ sLastTracerEvent = aTime;
+}
+
+void mozilla_sampler_frame_number(int frameNumber)
+{
+ sFrameNumber = frameNumber;
+}
+
+void mozilla_sampler_lock()
+{
+ profiler_stop();
+#ifndef SPS_STANDALONE
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os)
+ os->NotifyObservers(nullptr, "profiler-locked", nullptr);
+#endif
+}
+
+void mozilla_sampler_unlock()
+{
+#ifndef SPS_STANDALONE
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os)
+ os->NotifyObservers(nullptr, "profiler-unlocked", nullptr);
+#endif
+}
+
+bool mozilla_sampler_register_thread(const char* aName, void* aGuessStackTop)
+{
+ if (sInitCount == 0) {
+ return false;
+ }
+
+#if defined(MOZ_WIDGET_GONK) && !defined(MOZ_PROFILING)
+ // The only way to profile secondary threads on b2g
+ // is to build with profiling OR have the profiler
+ // running on startup.
+ if (!profiler_is_active()) {
+ return false;
+ }
+#endif
+
+ MOZ_ASSERT(tlsPseudoStack.get() == nullptr);
+ PseudoStack* stack = PseudoStack::create();
+ tlsPseudoStack.set(stack);
+ bool isMainThread = is_main_thread_name(aName);
+ void* stackTop = GetStackTop(aGuessStackTop);
+ return Sampler::RegisterCurrentThread(aName, stack, isMainThread, stackTop);
+}
+
+void mozilla_sampler_unregister_thread()
+{
+ // Don't check sInitCount count here -- we may be unregistering the
+ // thread after the sampler was shut down.
+ if (!stack_key_initialized) {
+ return;
+ }
+
+ PseudoStack *stack = tlsPseudoStack.get();
+ if (!stack) {
+ return;
+ }
+ stack->deref();
+ tlsPseudoStack.set(nullptr);
+
+ Sampler::UnregisterCurrentThread();
+}
+
+void mozilla_sampler_sleep_start() {
+ if (sInitCount == 0) {
+ return;
+ }
+
+ PseudoStack *stack = tlsPseudoStack.get();
+ if (stack == nullptr) {
+ return;
+ }
+ stack->setSleeping(1);
+}
+
+void mozilla_sampler_sleep_end() {
+ if (sInitCount == 0) {
+ return;
+ }
+
+ PseudoStack *stack = tlsPseudoStack.get();
+ if (stack == nullptr) {
+ return;
+ }
+ stack->setSleeping(0);
+}
+
+bool mozilla_sampler_is_sleeping() {
+ if (sInitCount == 0) {
+ return false;
+ }
+ PseudoStack *stack = tlsPseudoStack.get();
+ if (stack == nullptr) {
+ return false;
+ }
+ return stack->isSleeping();
+}
+
+double mozilla_sampler_time(const mozilla::TimeStamp& aTime)
+{
+ mozilla::TimeDuration delta = aTime - sStartTime;
+ return delta.ToMilliseconds();
+}
+
+double mozilla_sampler_time()
+{
+ return mozilla_sampler_time(mozilla::TimeStamp::Now());
+}
+
+ProfilerBacktrace* mozilla_sampler_get_backtrace()
+{
+ if (!stack_key_initialized)
+ return nullptr;
+
+ // Don't capture a stack if we're not profiling
+ if (!profiler_is_active()) {
+ return nullptr;
+ }
+
+ // Don't capture a stack if we don't want to include personal information
+ if (profiler_in_privacy_mode()) {
+ return nullptr;
+ }
+
+ GeckoSampler* t = tlsTicker.get();
+ if (!t) {
+ return nullptr;
+ }
+
+ return new ProfilerBacktrace(t->GetBacktrace());
+}
+
+void mozilla_sampler_free_backtrace(ProfilerBacktrace* aBacktrace)
+{
+ delete aBacktrace;
+}
+
+// Fill the output buffer with the following pattern:
+// "Lable 1" "\0" "Label 2" "\0" ... "Label N" "\0" "\0"
+// TODO: use the unwinder instead of pseudo stack.
+void mozilla_sampler_get_backtrace_noalloc(char *output, size_t outputSize)
+{
+ MOZ_ASSERT(outputSize >= 2);
+ char *bound = output + outputSize - 2;
+ output[0] = output[1] = '\0';
+ PseudoStack *pseudoStack = tlsPseudoStack.get();
+ if (!pseudoStack) {
+ return;
+ }
+
+ volatile StackEntry *pseudoFrames = pseudoStack->mStack;
+ uint32_t pseudoCount = pseudoStack->stackSize();
+
+ for (uint32_t i = 0; i < pseudoCount; i++) {
+ size_t len = strlen(pseudoFrames[i].label());
+ if (output + len >= bound)
+ break;
+ strcpy(output, pseudoFrames[i].label());
+ output += len;
+ *output++ = '\0';
+ *output = '\0';
+ }
+}
+
+void mozilla_sampler_tracing(const char* aCategory, const char* aInfo,
+ TracingMetadata aMetaData)
+{
+ mozilla_sampler_add_marker(aInfo, new ProfilerMarkerTracing(aCategory, aMetaData));
+}
+
+void mozilla_sampler_tracing(const char* aCategory, const char* aInfo,
+ ProfilerBacktrace* aCause,
+ TracingMetadata aMetaData)
+{
+ mozilla_sampler_add_marker(aInfo, new ProfilerMarkerTracing(aCategory, aMetaData, aCause));
+}
+
+void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload)
+{
+ // Note that aPayload may be allocated by the caller, so we need to make sure
+ // that we free it at some point.
+ mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
+
+ if (!stack_key_initialized)
+ return;
+
+ // Don't insert a marker if we're not profiling to avoid
+ // the heap copy (malloc).
+ if (!profiler_is_active()) {
+ return;
+ }
+
+ // Don't add a marker if we don't want to include personal information
+ if (profiler_in_privacy_mode()) {
+ return;
+ }
+
+ PseudoStack *stack = tlsPseudoStack.get();
+ if (!stack) {
+ return;
+ }
+
+ mozilla::TimeStamp origin = (aPayload && !aPayload->GetStartTime().IsNull()) ?
+ aPayload->GetStartTime() : mozilla::TimeStamp::Now();
+ mozilla::TimeDuration delta = origin - sStartTime;
+ stack->addMarker(aMarker, payload.release(), delta.ToMilliseconds());
+}
+
+#ifndef SPS_STANDALONE
+#include "mozilla/Mutex.h"
+
+class GeckoMutex : public ::Mutex {
+ public:
+ explicit GeckoMutex(const char* aDesc) :
+ mMutex(aDesc)
+ {}
+
+ virtual ~GeckoMutex() {}
+
+ virtual int Lock() {
+ mMutex.Lock();
+ return 0;
+ }
+
+ virtual int Unlock() {
+ mMutex.Unlock();
+ return 0;
+ }
+
+ private:
+ mozilla::Mutex mMutex;
+};
+
+mozilla::UniquePtr< ::Mutex> OS::CreateMutex(const char* aDesc) {
+ return mozilla::MakeUnique<GeckoMutex>(aDesc);
+}
+
+#else
+// Otherwise use c++11 Mutex
+#include <mutex>
+
+class OSXMutex : public ::Mutex {
+ public:
+ OSXMutex(const char* aDesc) :
+ mMutex()
+ {}
+
+ virtual ~OSXMutex() {}
+
+ virtual int Lock() {
+ mMutex.lock();
+ return 0;
+ }
+
+ virtual int Unlock() {
+ mMutex.unlock();
+ return 0;
+ }
+
+ private:
+ std::mutex mMutex;
+};
+
+mozilla::UniquePtr< ::Mutex> OS::CreateMutex(const char* aDesc) {
+ return mozilla::MakeUnique<GeckoMutex>(aDesc);
+}
+
+#endif
+
+// END externally visible functions
+////////////////////////////////////////////////////////////////////////