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: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "ProfilerIOInterposeObserver.h" michael@0: #include "platform.h" michael@0: #include "PlatformMacros.h" michael@0: #include "prenv.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/ThreadLocal.h" michael@0: #include "PseudoStack.h" michael@0: #include "TableTicker.h" michael@0: #include "UnwinderThread2.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "ProfilerMarkers.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) michael@0: #include "AndroidBridge.h" michael@0: using namespace mozilla::widget::android; michael@0: #endif michael@0: michael@0: mozilla::ThreadLocal tlsPseudoStack; michael@0: mozilla::ThreadLocal tlsTicker; michael@0: mozilla::ThreadLocal tlsStackTop; michael@0: // We need to track whether we've been initialized otherwise michael@0: // we end up using tlsStack without initializing it. michael@0: // Because tlsStack is totally opaque to us we can't reuse michael@0: // it as the flag itself. michael@0: bool stack_key_initialized; michael@0: michael@0: TimeStamp sLastTracerEvent; // is raced on michael@0: TimeStamp sStartTime; michael@0: int sFrameNumber = 0; michael@0: int sLastFrameNumber = 0; michael@0: int sInitCount = 0; // Each init must have a matched shutdown. michael@0: static bool sIsProfiling = false; // is raced on michael@0: michael@0: // env variables to control the profiler michael@0: const char* PROFILER_MODE = "MOZ_PROFILER_MODE"; michael@0: const char* PROFILER_INTERVAL = "MOZ_PROFILER_INTERVAL"; michael@0: const char* PROFILER_ENTRIES = "MOZ_PROFILER_ENTRIES"; michael@0: const char* PROFILER_STACK = "MOZ_PROFILER_STACK_SCAN"; michael@0: const char* PROFILER_FEATURES = "MOZ_PROFILING_FEATURES"; michael@0: michael@0: /* used to keep track of the last event that we sampled during */ michael@0: unsigned int sLastSampledEventGeneration = 0; michael@0: michael@0: /* a counter that's incremented everytime we get responsiveness event michael@0: * note: it might also be worth trackplaing everytime we go around michael@0: * the event loop */ michael@0: unsigned int sCurrentEventGeneration = 0; michael@0: /* we don't need to worry about overflow because we only treat the michael@0: * case of them being the same as special. i.e. we only run into michael@0: * a problem if 2^32 events happen between samples that we need michael@0: * to know are associated with different events */ michael@0: michael@0: std::vector* Sampler::sRegisteredThreads = nullptr; michael@0: mozilla::Mutex* Sampler::sRegisteredThreadsMutex = nullptr; michael@0: michael@0: TableTicker* Sampler::sActiveSampler; michael@0: michael@0: static mozilla::StaticAutoPtr michael@0: sInterposeObserver; michael@0: michael@0: // The name that identifies the gecko thread for calls to michael@0: // profiler_register_thread. For all platform except metro michael@0: // the thread that calls mozilla_sampler_init is considered michael@0: // the gecko thread. With metro the gecko thread is michael@0: // registered later based on this thread name. michael@0: static const char * gGeckoThreadName = "GeckoMain"; michael@0: michael@0: void Sampler::Startup() { michael@0: sRegisteredThreads = new std::vector(); michael@0: sRegisteredThreadsMutex = new mozilla::Mutex("sRegisteredThreads mutex"); michael@0: } michael@0: michael@0: void Sampler::Shutdown() { michael@0: while (sRegisteredThreads->size() > 0) { michael@0: delete sRegisteredThreads->back(); michael@0: sRegisteredThreads->pop_back(); michael@0: } michael@0: michael@0: delete sRegisteredThreadsMutex; michael@0: delete sRegisteredThreads; michael@0: michael@0: // UnregisterThread can be called after shutdown in XPCShell. Thus michael@0: // we need to point to null to ignore such a call after shutdown. michael@0: sRegisteredThreadsMutex = nullptr; michael@0: sRegisteredThreads = nullptr; michael@0: } michael@0: michael@0: ThreadInfo::~ThreadInfo() { michael@0: free(mName); michael@0: michael@0: if (mProfile) michael@0: delete mProfile; michael@0: michael@0: Sampler::FreePlatformData(mPlatformData); michael@0: } michael@0: michael@0: ProfilerMarker::ProfilerMarker(const char* aMarkerName, michael@0: ProfilerMarkerPayload* aPayload, michael@0: float aTime) michael@0: : mMarkerName(strdup(aMarkerName)) michael@0: , mPayload(aPayload) michael@0: , mTime(aTime) michael@0: { michael@0: } michael@0: michael@0: ProfilerMarker::~ProfilerMarker() { michael@0: free(mMarkerName); michael@0: delete mPayload; michael@0: } michael@0: michael@0: void michael@0: ProfilerMarker::SetGeneration(int aGenID) { michael@0: mGenID = aGenID; michael@0: } michael@0: michael@0: float michael@0: ProfilerMarker::GetTime() { michael@0: return mTime; michael@0: } michael@0: michael@0: void ProfilerMarker::StreamJSObject(JSStreamWriter& b) const { michael@0: b.BeginObject(); michael@0: b.NameValue("name", GetMarkerName()); michael@0: // TODO: Store the callsite for this marker if available: michael@0: // if have location data michael@0: // b.NameValue(marker, "location", ...); michael@0: if (mPayload) { michael@0: b.Name("data"); michael@0: mPayload->StreamPayload(b); michael@0: } michael@0: b.NameValue("time", mTime); michael@0: b.EndObject(); michael@0: } michael@0: michael@0: PendingMarkers::~PendingMarkers() { michael@0: clearMarkers(); michael@0: if (mSignalLock != false) { michael@0: // We're releasing the pseudostack while it's still in use. michael@0: // The label macros keep a non ref counted reference to the michael@0: // stack to avoid a TLS. If these are not all cleared we will michael@0: // get a use-after-free so better to crash now. michael@0: abort(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: PendingMarkers::addMarker(ProfilerMarker *aMarker) { michael@0: mSignalLock = true; michael@0: STORE_SEQUENCER(); michael@0: michael@0: MOZ_ASSERT(aMarker); michael@0: mPendingMarkers.insert(aMarker); michael@0: michael@0: // Clear markers that have been overwritten michael@0: while (mStoredMarkers.peek() && michael@0: mStoredMarkers.peek()->HasExpired(mGenID)) { michael@0: delete mStoredMarkers.popHead(); michael@0: } michael@0: STORE_SEQUENCER(); michael@0: mSignalLock = false; michael@0: } michael@0: michael@0: void michael@0: PendingMarkers::updateGeneration(int aGenID) { michael@0: mGenID = aGenID; michael@0: } michael@0: michael@0: void michael@0: PendingMarkers::addStoredMarker(ProfilerMarker *aStoredMarker) { michael@0: aStoredMarker->SetGeneration(mGenID); michael@0: mStoredMarkers.insert(aStoredMarker); michael@0: } michael@0: michael@0: bool sps_version2() michael@0: { michael@0: static int version = 0; // Raced on, potentially michael@0: michael@0: if (version == 0) { michael@0: bool allow2 = false; // Is v2 allowable on this platform? michael@0: # if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \ michael@0: || defined(SPS_PLAT_x86_linux) michael@0: allow2 = true; michael@0: # elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin) \ michael@0: || defined(SPS_PLAT_x86_windows) || defined(SPS_PLAT_x86_android) \ michael@0: || defined(SPS_PLAT_amd64_windows) michael@0: allow2 = false; michael@0: # else michael@0: # error "Unknown platform" michael@0: # endif michael@0: michael@0: bool req2 = PR_GetEnv("MOZ_PROFILER_NEW") != nullptr; // Has v2 been requested? michael@0: michael@0: bool elfhackd = false; michael@0: # if defined(USE_ELF_HACK) michael@0: bool elfhackd = true; michael@0: # endif michael@0: michael@0: if (req2 && allow2) { michael@0: version = 2; michael@0: LOG("------------------- MOZ_PROFILER_NEW set -------------------"); michael@0: } else if (req2 && !allow2) { michael@0: version = 1; michael@0: LOG("--------------- MOZ_PROFILER_NEW requested, ----------------"); michael@0: LOG("---------- but is not available on this platform -----------"); michael@0: } else if (req2 && elfhackd) { michael@0: version = 1; michael@0: LOG("--------------- MOZ_PROFILER_NEW requested, ----------------"); michael@0: LOG("--- but this build was not done with --disable-elf-hack ----"); michael@0: } else { michael@0: version = 1; michael@0: LOG("----------------- MOZ_PROFILER_NEW not set -----------------"); michael@0: } michael@0: } michael@0: return version == 2; michael@0: } michael@0: michael@0: /* Has MOZ_PROFILER_VERBOSE been set? */ michael@0: bool moz_profiler_verbose() michael@0: { michael@0: /* 0 = not checked, 1 = unset, 2 = set */ michael@0: static int status = 0; // Raced on, potentially michael@0: michael@0: if (status == 0) { michael@0: if (PR_GetEnv("MOZ_PROFILER_VERBOSE") != nullptr) michael@0: status = 2; michael@0: else michael@0: status = 1; michael@0: } michael@0: michael@0: return status == 2; michael@0: } michael@0: michael@0: static inline const char* name_UnwMode(UnwMode m) michael@0: { michael@0: switch (m) { michael@0: case UnwINVALID: return "invalid"; michael@0: case UnwNATIVE: return "native"; michael@0: case UnwPSEUDO: return "pseudo"; michael@0: case UnwCOMBINED: return "combined"; michael@0: default: return "??name_UnwMode??"; michael@0: } michael@0: } michael@0: michael@0: bool set_profiler_mode(const char* mode) { michael@0: if (mode) { michael@0: if (0 == strcmp(mode, "pseudo")) { michael@0: sUnwindMode = UnwPSEUDO; michael@0: return true; michael@0: } michael@0: else if (0 == strcmp(mode, "native") && is_native_unwinding_avail()) { michael@0: sUnwindMode = UnwNATIVE; michael@0: return true; michael@0: } michael@0: else if (0 == strcmp(mode, "combined") && is_native_unwinding_avail()) { michael@0: sUnwindMode = UnwCOMBINED; michael@0: return true; michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool set_profiler_interval(const char* interval) { michael@0: if (interval) { michael@0: errno = 0; michael@0: long int n = strtol(interval, (char**)nullptr, 10); michael@0: if (errno == 0 && n >= 1 && n <= 1000) { michael@0: sUnwindInterval = n; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool set_profiler_entries(const char* entries) { michael@0: if (entries) { michael@0: errno = 0; michael@0: long int n = strtol(entries, (char**)nullptr, 10); michael@0: if (errno == 0 && n > 0) { michael@0: sProfileEntries = n; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool set_profiler_scan(const char* scanCount) { michael@0: if (scanCount) { michael@0: errno = 0; michael@0: long int n = strtol(scanCount, (char**)nullptr, 10); michael@0: if (errno == 0 && n >= 0 && n <= 100) { michael@0: sUnwindStackScan = n; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool is_native_unwinding_avail() { michael@0: # if defined(HAVE_NATIVE_UNWIND) michael@0: return true; michael@0: #else michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: // Read env vars at startup, so as to set sUnwindMode and sInterval. michael@0: void read_profiler_env_vars() michael@0: { michael@0: bool nativeAvail = is_native_unwinding_avail(); michael@0: michael@0: /* Set defaults */ michael@0: sUnwindMode = nativeAvail ? UnwCOMBINED : UnwPSEUDO; michael@0: sUnwindInterval = 0; /* We'll have to look elsewhere */ michael@0: sProfileEntries = 0; michael@0: michael@0: const char* stackMode = PR_GetEnv(PROFILER_MODE); michael@0: const char* interval = PR_GetEnv(PROFILER_INTERVAL); michael@0: const char* entries = PR_GetEnv(PROFILER_ENTRIES); michael@0: const char* scanCount = PR_GetEnv(PROFILER_STACK); michael@0: michael@0: if (!set_profiler_mode(stackMode) || michael@0: !set_profiler_interval(interval) || michael@0: !set_profiler_entries(entries) || michael@0: !set_profiler_scan(scanCount)) { michael@0: profiler_usage(); michael@0: } else { michael@0: LOG( "SPS:"); michael@0: LOGF("SPS: Unwind mode = %s", name_UnwMode(sUnwindMode)); michael@0: LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")", michael@0: (int)sUnwindInterval); michael@0: LOGF("SPS: Entry store size = %d (zero means \"platform default\")", michael@0: (int)sProfileEntries); michael@0: LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).", michael@0: (int)sUnwindStackScan); michael@0: LOG( "SPS: Use env var MOZ_PROFILER_MODE=help for further information."); michael@0: LOG( "SPS: Note that MOZ_PROFILER_MODE=help sets all values to defaults."); michael@0: LOG( "SPS:"); michael@0: } michael@0: } michael@0: michael@0: void profiler_usage() { michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: Environment variable usage:"); michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: MOZ_PROFILER_MODE=native for native unwind only"); michael@0: LOG( "SPS: MOZ_PROFILER_MODE=pseudo for pseudo unwind only"); michael@0: LOG( "SPS: MOZ_PROFILER_MODE=combined for combined native & pseudo unwind"); michael@0: LOG( "SPS: If unset, default is 'combined' on native-capable"); michael@0: LOG( "SPS: platforms, 'pseudo' on others."); michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: MOZ_PROFILER_INTERVAL= (milliseconds, 1 to 1000)"); michael@0: LOG( "SPS: If unset, platform default is used."); michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: MOZ_PROFILER_ENTRIES= (count, minimum of 1)"); michael@0: LOG( "SPS: If unset, platform default is used."); michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: MOZ_PROFILER_VERBOSE"); michael@0: LOG( "SPS: If set to any value, increases verbosity (recommended)."); michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: MOZ_PROFILER_STACK_SCAN= (default is zero)"); michael@0: LOG( "SPS: The number of dubious (stack-scanned) frames allowed"); michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: MOZ_PROFILER_NEW"); michael@0: LOG( "SPS: Needs to be set to use LUL-based unwinding."); michael@0: LOG( "SPS: "); michael@0: LOG( "SPS: MOZ_PROFILER_LUL_TEST"); michael@0: LOG( "SPS: If set to any value, runs LUL unit tests at startup of"); michael@0: LOG( "SPS: the unwinder thread, and prints a short summary of results."); michael@0: LOG( "SPS: "); michael@0: LOGF("SPS: This platform %s native unwinding.", michael@0: is_native_unwinding_avail() ? "supports" : "does not support"); michael@0: LOG( "SPS: "); michael@0: michael@0: /* Re-set defaults */ michael@0: sUnwindMode = is_native_unwinding_avail() ? UnwCOMBINED : UnwPSEUDO; michael@0: sUnwindInterval = 0; /* We'll have to look elsewhere */ michael@0: sProfileEntries = 0; michael@0: sUnwindStackScan = 0; michael@0: michael@0: LOG( "SPS:"); michael@0: LOGF("SPS: Unwind mode = %s", name_UnwMode(sUnwindMode)); michael@0: LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")", michael@0: (int)sUnwindInterval); michael@0: LOGF("SPS: Entry store size = %d (zero means \"platform default\")", michael@0: (int)sProfileEntries); michael@0: LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).", michael@0: (int)sUnwindStackScan); michael@0: LOG( "SPS: Use env var MOZ_PROFILER_MODE=help for further information."); michael@0: LOG( "SPS: Note that MOZ_PROFILER_MODE=help sets all values to defaults."); michael@0: LOG( "SPS:"); michael@0: michael@0: return; michael@0: } michael@0: michael@0: void set_tls_stack_top(void* stackTop) michael@0: { michael@0: // Round |stackTop| up to the end of the containing page. We may michael@0: // as well do this -- there's no danger of a fault, and we might michael@0: // get a few more base-of-the-stack frames as a result. This michael@0: // assumes that no target has a page size smaller than 4096. michael@0: uintptr_t stackTopR = (uintptr_t)stackTop; michael@0: if (stackTop) { michael@0: stackTopR = (stackTopR & ~(uintptr_t)4095) + (uintptr_t)4095; michael@0: } michael@0: tlsStackTop.set((void*)stackTopR); michael@0: } michael@0: michael@0: bool is_main_thread_name(const char* aName) { michael@0: if (!aName) { michael@0: return false; michael@0: } michael@0: return strcmp(aName, gGeckoThreadName) == 0; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // BEGIN externally visible functions michael@0: michael@0: void mozilla_sampler_init(void* stackTop) michael@0: { michael@0: sInitCount++; michael@0: michael@0: if (stack_key_initialized) michael@0: return; michael@0: michael@0: LOG("BEGIN mozilla_sampler_init"); michael@0: if (!tlsPseudoStack.init() || !tlsTicker.init() || !tlsStackTop.init()) { michael@0: LOG("Failed to init."); michael@0: return; michael@0: } michael@0: stack_key_initialized = true; michael@0: michael@0: Sampler::Startup(); michael@0: michael@0: PseudoStack *stack = new PseudoStack(); michael@0: tlsPseudoStack.set(stack); michael@0: michael@0: bool isMainThread = true; michael@0: #ifdef XP_WIN michael@0: // For metrofx, we'll register the main thread once it's created. michael@0: isMainThread = !(XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro); michael@0: #endif michael@0: Sampler::RegisterCurrentThread(isMainThread ? michael@0: gGeckoThreadName : "Application Thread", michael@0: stack, isMainThread, stackTop); michael@0: michael@0: // Read mode settings from MOZ_PROFILER_MODE and interval michael@0: // settings from MOZ_PROFILER_INTERVAL and stack-scan threshhold michael@0: // from MOZ_PROFILER_STACK_SCAN. michael@0: read_profiler_env_vars(); michael@0: michael@0: // platform specific initialization michael@0: OS::Startup(); michael@0: michael@0: // We can't open pref so we use an environment variable michael@0: // to know if we should trigger the profiler on startup michael@0: // NOTE: Default michael@0: const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP"); michael@0: if (!val || !*val) { michael@0: return; michael@0: } michael@0: michael@0: const char* features[] = {"js" michael@0: , "leaf" michael@0: #if defined(XP_WIN) || defined(XP_MACOSX) || (defined(SPS_ARCH_arm) && defined(linux)) michael@0: , "stackwalk" michael@0: #endif michael@0: #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) michael@0: , "java" michael@0: #endif michael@0: }; michael@0: profiler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL, michael@0: features, sizeof(features)/sizeof(const char*), michael@0: // TODO Add env variable to select threads michael@0: nullptr, 0); michael@0: LOG("END mozilla_sampler_init"); michael@0: } michael@0: michael@0: void mozilla_sampler_shutdown() michael@0: { michael@0: sInitCount--; michael@0: michael@0: if (sInitCount > 0) michael@0: return; michael@0: michael@0: // Save the profile on shutdown if requested. michael@0: TableTicker *t = tlsTicker.get(); michael@0: if (t) { michael@0: const char *val = PR_GetEnv("MOZ_PROFILER_SHUTDOWN"); michael@0: if (val) { michael@0: std::ofstream stream; michael@0: stream.open(val); michael@0: if (stream.is_open()) { michael@0: t->ToStreamAsJSON(stream); michael@0: stream.close(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: profiler_stop(); michael@0: michael@0: Sampler::Shutdown(); michael@0: michael@0: // We can't delete the Stack because we can be between a michael@0: // sampler call_enter/call_exit point. michael@0: // TODO Need to find a safe time to delete Stack michael@0: } michael@0: michael@0: void mozilla_sampler_save() michael@0: { michael@0: TableTicker *t = tlsTicker.get(); michael@0: if (!t) { michael@0: return; michael@0: } michael@0: michael@0: t->RequestSave(); michael@0: // We're on the main thread already so we don't michael@0: // have to wait to handle the save request. michael@0: t->HandleSaveRequest(); michael@0: } michael@0: michael@0: char* mozilla_sampler_get_profile() michael@0: { michael@0: TableTicker *t = tlsTicker.get(); michael@0: if (!t) { michael@0: return nullptr; michael@0: } michael@0: michael@0: std::stringstream stream; michael@0: t->ToStreamAsJSON(stream); michael@0: char* profile = strdup(stream.str().c_str()); michael@0: return profile; michael@0: } michael@0: michael@0: JSObject *mozilla_sampler_get_profile_data(JSContext *aCx) michael@0: { michael@0: TableTicker *t = tlsTicker.get(); michael@0: if (!t) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return t->ToJSObject(aCx); michael@0: } michael@0: michael@0: void mozilla_sampler_save_profile_to_file(const char* aFilename) michael@0: { michael@0: TableTicker *t = tlsTicker.get(); michael@0: if (!t) { michael@0: return; michael@0: } michael@0: michael@0: std::ofstream stream; michael@0: stream.open(aFilename); michael@0: if (stream.is_open()) { michael@0: t->ToStreamAsJSON(stream); michael@0: stream.close(); michael@0: LOGF("Saved to %s", aFilename); michael@0: } else { michael@0: LOG("Fail to open profile log file."); michael@0: } michael@0: } michael@0: michael@0: michael@0: const char** mozilla_sampler_get_features() michael@0: { michael@0: static const char* features[] = { michael@0: #if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND) michael@0: // Walk the C++ stack. michael@0: "stackwalk", michael@0: #endif michael@0: #if defined(ENABLE_SPS_LEAF_DATA) michael@0: // Include the C++ leaf node if not stackwalking. DevTools michael@0: // profiler doesn't want the native addresses. michael@0: "leaf", michael@0: #endif michael@0: #if !defined(SPS_OS_windows) michael@0: // Use a seperate thread of walking the stack. michael@0: "unwinder", michael@0: #endif michael@0: "java", michael@0: // Only record samples during periods of bad responsiveness michael@0: "jank", michael@0: // Tell the JS engine to emmit pseudostack entries in the michael@0: // pro/epilogue. michael@0: "js", michael@0: // Profile the registered secondary threads. michael@0: "threads", michael@0: // Do not include user-identifiable information michael@0: "privacy", michael@0: // Add main thread I/O to the profile michael@0: "mainthreadio", michael@0: #if defined(XP_WIN) michael@0: // Add power collection michael@0: "power", michael@0: #endif michael@0: nullptr michael@0: }; michael@0: michael@0: return features; michael@0: } michael@0: michael@0: // Values are only honored on the first start michael@0: void mozilla_sampler_start(int aProfileEntries, double aInterval, michael@0: const char** aFeatures, uint32_t aFeatureCount, michael@0: const char** aThreadNameFilters, uint32_t aFilterCount) michael@0: michael@0: { michael@0: LOG("BEGIN mozilla_sampler_start"); michael@0: michael@0: if (!stack_key_initialized) michael@0: profiler_init(nullptr); michael@0: michael@0: /* If the sampling interval was set using env vars, use that michael@0: in preference to anything else. */ michael@0: if (sUnwindInterval > 0) michael@0: aInterval = sUnwindInterval; michael@0: michael@0: /* If the entry count was set using env vars, use that, too: */ michael@0: if (sProfileEntries > 0) michael@0: aProfileEntries = sProfileEntries; michael@0: michael@0: // Reset the current state if the profiler is running michael@0: profiler_stop(); michael@0: michael@0: TableTicker* t; michael@0: t = new TableTicker(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL, michael@0: aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY, michael@0: aFeatures, aFeatureCount, michael@0: aThreadNameFilters, aFilterCount); michael@0: if (t->HasUnwinderThread()) { michael@0: // Create the unwinder thread. ATM there is only one. michael@0: uwt__init(); michael@0: } michael@0: michael@0: tlsTicker.set(t); michael@0: t->Start(); michael@0: if (t->ProfileJS() || t->InPrivacyMode()) { michael@0: mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); michael@0: std::vector threads = t->GetRegisteredThreads(); michael@0: michael@0: for (uint32_t i = 0; i < threads.size(); i++) { michael@0: ThreadInfo* info = threads[i]; michael@0: ThreadProfile* thread_profile = info->Profile(); michael@0: if (!thread_profile) { michael@0: continue; michael@0: } michael@0: thread_profile->GetPseudoStack()->reinitializeOnResume(); michael@0: if (t->ProfileJS()) { michael@0: thread_profile->GetPseudoStack()->enableJSSampling(); michael@0: } michael@0: if (t->InPrivacyMode()) { michael@0: thread_profile->GetPseudoStack()->mPrivacyMode = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) michael@0: if (t->ProfileJava()) { michael@0: int javaInterval = aInterval; michael@0: // Java sampling doesn't accuratly keep up with 1ms sampling michael@0: if (javaInterval < 10) { michael@0: aInterval = 10; michael@0: } michael@0: mozilla::widget::android::GeckoJavaSampler::StartJavaProfiling(javaInterval, 1000); michael@0: } michael@0: #endif michael@0: michael@0: if (t->AddMainThreadIO()) { michael@0: if (!sInterposeObserver) { michael@0: // Lazily create IO interposer observer michael@0: sInterposeObserver = new mozilla::ProfilerIOInterposeObserver(); michael@0: } michael@0: mozilla::IOInterposer::Register(mozilla::IOInterposeObserver::OpAll, michael@0: sInterposeObserver); michael@0: } michael@0: michael@0: sIsProfiling = true; michael@0: michael@0: if (Sampler::CanNotifyObservers()) { michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->NotifyObservers(nullptr, "profiler-started", nullptr); michael@0: } michael@0: michael@0: LOG("END mozilla_sampler_start"); michael@0: } michael@0: michael@0: void mozilla_sampler_stop() michael@0: { michael@0: LOG("BEGIN mozilla_sampler_stop"); michael@0: michael@0: if (!stack_key_initialized) michael@0: profiler_init(nullptr); michael@0: michael@0: TableTicker *t = tlsTicker.get(); michael@0: if (!t) { michael@0: LOG("END mozilla_sampler_stop-early"); michael@0: return; michael@0: } michael@0: michael@0: bool disableJS = t->ProfileJS(); michael@0: bool unwinderThreader = t->HasUnwinderThread(); michael@0: michael@0: // Shut down and reap the unwinder thread. We have to do this michael@0: // before stopping the sampler, so as to guarantee that the unwinder michael@0: // thread doesn't try to access memory that the subsequent call to michael@0: // mozilla_sampler_stop causes to be freed. michael@0: if (unwinderThreader) { michael@0: uwt__stop(); michael@0: } michael@0: michael@0: t->Stop(); michael@0: delete t; michael@0: tlsTicker.set(nullptr); michael@0: michael@0: if (disableJS) { michael@0: PseudoStack *stack = tlsPseudoStack.get(); michael@0: ASSERT(stack != nullptr); michael@0: stack->disableJSSampling(); michael@0: } michael@0: michael@0: if (unwinderThreader) { michael@0: uwt__deinit(); michael@0: } michael@0: michael@0: mozilla::IOInterposer::Unregister(mozilla::IOInterposeObserver::OpAll, michael@0: sInterposeObserver); michael@0: sInterposeObserver = nullptr; michael@0: michael@0: sIsProfiling = false; michael@0: michael@0: if (Sampler::CanNotifyObservers()) { michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->NotifyObservers(nullptr, "profiler-stopped", nullptr); michael@0: } michael@0: michael@0: LOG("END mozilla_sampler_stop"); michael@0: } michael@0: michael@0: bool mozilla_sampler_is_paused() { michael@0: if (Sampler::GetActiveSampler()) { michael@0: return Sampler::GetActiveSampler()->IsPaused(); michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: void mozilla_sampler_pause() { michael@0: if (Sampler::GetActiveSampler()) { michael@0: Sampler::GetActiveSampler()->SetPaused(true); michael@0: } michael@0: } michael@0: michael@0: void mozilla_sampler_resume() { michael@0: if (Sampler::GetActiveSampler()) { michael@0: Sampler::GetActiveSampler()->SetPaused(false); michael@0: } michael@0: } michael@0: michael@0: bool mozilla_sampler_is_active() michael@0: { michael@0: return sIsProfiling; michael@0: } michael@0: michael@0: static double sResponsivenessTimes[100]; michael@0: static unsigned int sResponsivenessLoc = 0; michael@0: void mozilla_sampler_responsiveness(const TimeStamp& aTime) michael@0: { michael@0: if (!sLastTracerEvent.IsNull()) { michael@0: if (sResponsivenessLoc == 100) { michael@0: for(size_t i = 0; i < 100-1; i++) { michael@0: sResponsivenessTimes[i] = sResponsivenessTimes[i+1]; michael@0: } michael@0: sResponsivenessLoc--; michael@0: } michael@0: TimeDuration delta = aTime - sLastTracerEvent; michael@0: sResponsivenessTimes[sResponsivenessLoc++] = delta.ToMilliseconds(); michael@0: } michael@0: sCurrentEventGeneration++; michael@0: michael@0: sLastTracerEvent = aTime; michael@0: } michael@0: michael@0: const double* mozilla_sampler_get_responsiveness() michael@0: { michael@0: return sResponsivenessTimes; michael@0: } michael@0: michael@0: void mozilla_sampler_frame_number(int frameNumber) michael@0: { michael@0: sFrameNumber = frameNumber; michael@0: } michael@0: michael@0: void mozilla_sampler_print_location2() michael@0: { michael@0: // FIXME michael@0: } michael@0: michael@0: void mozilla_sampler_lock() michael@0: { michael@0: profiler_stop(); michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->NotifyObservers(nullptr, "profiler-locked", nullptr); michael@0: } michael@0: michael@0: void mozilla_sampler_unlock() michael@0: { michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->NotifyObservers(nullptr, "profiler-unlocked", nullptr); michael@0: } michael@0: michael@0: bool mozilla_sampler_register_thread(const char* aName, void* stackTop) michael@0: { michael@0: #if defined(MOZ_WIDGET_GONK) && !defined(MOZ_PROFILING) michael@0: // The only way to profile secondary threads on b2g michael@0: // is to build with profiling OR have the profiler michael@0: // running on startup. michael@0: if (!profiler_is_active()) { michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: PseudoStack* stack = new PseudoStack(); michael@0: tlsPseudoStack.set(stack); michael@0: bool isMainThread = is_main_thread_name(aName); michael@0: return Sampler::RegisterCurrentThread(aName, stack, isMainThread, stackTop); michael@0: } michael@0: michael@0: void mozilla_sampler_unregister_thread() michael@0: { michael@0: Sampler::UnregisterCurrentThread(); michael@0: michael@0: PseudoStack *stack = tlsPseudoStack.get(); michael@0: if (!stack) { michael@0: return; michael@0: } michael@0: delete stack; michael@0: tlsPseudoStack.set(nullptr); michael@0: } michael@0: michael@0: void mozilla_sampler_sleep_start() { michael@0: PseudoStack *stack = tlsPseudoStack.get(); michael@0: if (stack == nullptr) { michael@0: return; michael@0: } michael@0: stack->setSleeping(1); michael@0: } michael@0: michael@0: void mozilla_sampler_sleep_end() { michael@0: PseudoStack *stack = tlsPseudoStack.get(); michael@0: if (stack == nullptr) { michael@0: return; michael@0: } michael@0: stack->setSleeping(0); michael@0: } michael@0: michael@0: double mozilla_sampler_time(const TimeStamp& aTime) michael@0: { michael@0: if (!mozilla_sampler_is_active()) { michael@0: return 0.0; michael@0: } michael@0: TimeDuration delta = aTime - sStartTime; michael@0: return delta.ToMilliseconds(); michael@0: } michael@0: michael@0: double mozilla_sampler_time() michael@0: { michael@0: return mozilla_sampler_time(TimeStamp::Now()); michael@0: } michael@0: michael@0: ProfilerBacktrace* mozilla_sampler_get_backtrace() michael@0: { michael@0: if (!stack_key_initialized) michael@0: return nullptr; michael@0: michael@0: // Don't capture a stack if we're not profiling michael@0: if (!profiler_is_active()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Don't capture a stack if we don't want to include personal information michael@0: if (profiler_in_privacy_mode()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: TableTicker* t = tlsTicker.get(); michael@0: if (!t) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return new ProfilerBacktrace(t->GetBacktrace()); michael@0: } michael@0: michael@0: void mozilla_sampler_free_backtrace(ProfilerBacktrace* aBacktrace) michael@0: { michael@0: delete aBacktrace; michael@0: } michael@0: michael@0: void mozilla_sampler_tracing(const char* aCategory, const char* aInfo, michael@0: TracingMetadata aMetaData) michael@0: { michael@0: mozilla_sampler_add_marker(aInfo, new ProfilerMarkerTracing(aCategory, aMetaData)); michael@0: } michael@0: michael@0: void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload) michael@0: { michael@0: // Note that aPayload may be allocated by the caller, so we need to make sure michael@0: // that we free it at some point. michael@0: nsAutoPtr payload(aPayload); michael@0: michael@0: if (!stack_key_initialized) michael@0: return; michael@0: michael@0: // Don't insert a marker if we're not profiling to avoid michael@0: // the heap copy (malloc). michael@0: if (!profiler_is_active()) { michael@0: return; michael@0: } michael@0: michael@0: // Don't add a marker if we don't want to include personal information michael@0: if (profiler_in_privacy_mode()) { michael@0: return; michael@0: } michael@0: michael@0: PseudoStack *stack = tlsPseudoStack.get(); michael@0: if (!stack) { michael@0: return; michael@0: } michael@0: TimeDuration delta = TimeStamp::Now() - sStartTime; michael@0: stack->addMarker(aMarker, payload.forget(), static_cast(delta.ToMilliseconds())); michael@0: } michael@0: michael@0: // END externally visible functions michael@0: //////////////////////////////////////////////////////////////////////// michael@0: michael@0: