/* -*- Mode: C++; tab-width: 2; 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/. */ // The Gecko Profiler is an always-on profiler that takes fast and low overhead // samples of the program execution using only userspace functionality for // portability. The goal of this module is to provide performance data in a // generic cross-platform way without requiring custom tools or kernel support. // // Samples are collected to form a timeline with optional timeline event // (markers) used for filtering. The samples include both native stacks and // platform-independent "label stack" frames. #ifndef GeckoProfiler_h #define GeckoProfiler_h // everything in here is also safe to include unconditionally, and only defines // empty macros if MOZ_GECKO_PROFILER is unset #include "BaseProfiler.h" #include "mozilla/ProfilerCounts.h" // ProfilerMarkers.h is #included in the middle of this header! // #include "mozilla/ProfilerMarkers.h" #ifndef MOZ_GECKO_PROFILER # include "mozilla/ProfilerMarkers.h" # include "mozilla/UniquePtr.h" // This file can be #included unconditionally. However, everything within this // file must be guarded by a #ifdef MOZ_GECKO_PROFILER, *except* for the // following macros and functions, which encapsulate the most common operations // and thus avoid the need for many #ifdefs. # define AUTO_PROFILER_INIT # define AUTO_PROFILER_INIT2 # define PROFILER_REGISTER_THREAD(name) # define PROFILER_UNREGISTER_THREAD() # define AUTO_PROFILER_REGISTER_THREAD(name) # define AUTO_PROFILER_THREAD_SLEEP # define AUTO_PROFILER_THREAD_WAKE # define PROFILER_JS_INTERRUPT_CALLBACK() # define PROFILER_SET_JS_CONTEXT(cx) # define PROFILER_CLEAR_JS_CONTEXT() # define AUTO_PROFILER_LABEL(label, categoryPair) # define AUTO_PROFILER_LABEL_CATEGORY_PAIR(categoryPair) # define AUTO_PROFILER_LABEL_DYNAMIC_CSTR(label, categoryPair, cStr) # define AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(label, categoryPair, \ cStr) # define AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(label, categoryPair, nsCStr) # define AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( \ label, categoryPair, nsCStr) # define AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(label, categoryPair, nsStr) # define AUTO_PROFILER_LABEL_FAST(label, categoryPair, ctx) # define AUTO_PROFILER_LABEL_DYNAMIC_FAST(label, dynamicString, categoryPair, \ ctx, flags) // Function stubs for when MOZ_GECKO_PROFILER is not defined. // This won't be used, it's just there to allow the empty definition of // `profiler_get_backtrace`. struct ProfilerBacktrace {}; using UniqueProfilerBacktrace = mozilla::UniquePtr; // Get/Capture-backtrace functions can return nullptr or false, the result // should be fed to another empty macro or stub anyway. static inline UniqueProfilerBacktrace profiler_get_backtrace() { return nullptr; } static inline bool profiler_capture_backtrace_into( mozilla::ProfileChunkedBuffer& aChunkedBuffer) { return false; } static inline mozilla::UniquePtr profiler_capture_backtrace() { return nullptr; } #else // !MOZ_GECKO_PROFILER # include "js/ProfilingCategory.h" # include "js/ProfilingStack.h" # include "js/RootingAPI.h" # include "mozilla/Assertions.h" # include "mozilla/Atomics.h" # include "mozilla/Attributes.h" # include "mozilla/Maybe.h" # include "mozilla/PowerOfTwo.h" # include "mozilla/Sprintf.h" # include "mozilla/ThreadLocal.h" # include "mozilla/TimeStamp.h" # include "mozilla/UniquePtr.h" # include "nscore.h" # include "nsString.h" # include # include class ProfilerBacktrace; class ProfilerCodeAddressService; struct JSContext; namespace JS { struct RecordAllocationInfo; } namespace mozilla { class ProfileBufferControlledChunkManager; class ProfileChunkedBuffer; namespace baseprofiler { class SpliceableJSONWriter; } // namespace baseprofiler namespace net { struct TimingStruct; enum CacheDisposition : uint8_t; } // namespace net } // namespace mozilla class nsIURI; class nsIDocShell; namespace mozilla { class MallocAllocPolicy; template class Vector; } // namespace mozilla // Macros used by the AUTO_PROFILER_* macros below. # define PROFILER_RAII_PASTE(id, line) id##line # define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line) # define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__) //--------------------------------------------------------------------------- // Profiler features //--------------------------------------------------------------------------- // Higher-order macro containing all the feature info in one place. Define // |MACRO| appropriately to extract the relevant parts. Note that the number // values are used internally only and so can be changed without consequence. // Any changes to this list should also be applied to the feature list in // toolkit/components/extensions/schemas/geckoProfiler.json. # define PROFILER_FOR_EACH_FEATURE(MACRO) \ MACRO(0, "java", Java, "Profile Java code, Android only") \ \ MACRO(1, "js", JS, \ "Get the JS engine to expose the JS stack to the profiler") \ \ /* The DevTools profiler doesn't want the native addresses. */ \ MACRO(2, "leaf", Leaf, "Include the C++ leaf node if not stackwalking") \ \ MACRO(3, "mainthreadio", MainThreadIO, "Add main thread file I/O") \ \ MACRO(4, "fileio", FileIO, \ "Add file I/O from all profiled threads, implies mainthreadio") \ \ MACRO(5, "fileioall", FileIOAll, \ "Add file I/O from all threads, implies fileio") \ \ MACRO(6, "noiostacks", NoIOStacks, \ "File I/O markers do not capture stacks, to reduce overhead") \ \ MACRO(7, "screenshots", Screenshots, \ "Take a snapshot of the window on every composition") \ \ MACRO(8, "seqstyle", SequentialStyle, \ "Disable parallel traversal in styling") \ \ MACRO(9, "stackwalk", StackWalk, \ "Walk the C++ stack, not available on all platforms") \ \ MACRO(10, "tasktracer", TaskTracer, \ "Start profiling with feature TaskTracer") \ \ MACRO(11, "threads", Threads, "Profile the registered secondary threads") \ \ MACRO(12, "jstracer", JSTracer, "Enable tracing of the JavaScript engine") \ \ MACRO(13, "jsallocations", JSAllocations, \ "Have the JavaScript engine track allocations") \ \ MACRO(14, "nostacksampling", NoStackSampling, \ "Disable all stack sampling: Cancels \"js\", \"leaf\", " \ "\"stackwalk\" and labels") \ \ MACRO(15, "preferencereads", PreferenceReads, \ "Track when preferences are read") \ \ MACRO(16, "nativeallocations", NativeAllocations, \ "Collect the stacks from a smaller subset of all native " \ "allocations, biasing towards collecting larger allocations") \ \ MACRO(17, "ipcmessages", IPCMessages, \ "Have the IPC layer track cross-process messages") \ \ MACRO(18, "audiocallbacktracing", AudioCallbackTracing, \ "Audio callback tracing") \ \ MACRO(19, "cpu", CPUUtilization, "CPU utilization") struct ProfilerFeature { # define DECLARE(n_, str_, Name_, desc_) \ static constexpr uint32_t Name_ = (1u << n_); \ static constexpr bool Has##Name_(uint32_t aFeatures) { \ return aFeatures & Name_; \ } \ static constexpr void Set##Name_(uint32_t& aFeatures) { \ aFeatures |= Name_; \ } \ static constexpr void Clear##Name_(uint32_t& aFeatures) { \ aFeatures &= ~Name_; \ } // Define a bitfield constant, a getter, and two setters for each feature. PROFILER_FOR_EACH_FEATURE(DECLARE) # undef DECLARE }; namespace mozilla { namespace profiler { namespace detail { // RacyFeatures is only defined in this header file so that its methods can // be inlined into profiler_is_active(). Please do not use anything from the // detail namespace outside the profiler. // Within the profiler's code, the preferred way to check profiler activeness // and features is via ActivePS(). However, that requires locking gPSMutex. // There are some hot operations where absolute precision isn't required, so we // duplicate the activeness/feature state in a lock-free manner in this class. class RacyFeatures { public: static void SetActive(uint32_t aFeatures) { sActiveAndFeatures = Active | aFeatures; } static void SetInactive() { sActiveAndFeatures = 0; } static void SetPaused() { sActiveAndFeatures |= Paused; } static void SetUnpaused() { sActiveAndFeatures &= ~Paused; } static void SetSamplingPaused() { sActiveAndFeatures |= SamplingPaused; } static void SetSamplingUnpaused() { sActiveAndFeatures &= ~SamplingPaused; } static mozilla::Maybe FeaturesIfActive() { if (uint32_t af = sActiveAndFeatures; af & Active) { // Active, remove the Active&Paused bits to get all features. return Some(af & ~(Active | Paused | SamplingPaused)); } return Nothing(); } static mozilla::Maybe FeaturesIfActiveAndUnpaused() { if (uint32_t af = sActiveAndFeatures; (af & (Active | Paused)) == Active) { // Active but not fully paused, remove the Active and sampling-paused bits // to get all features. return Some(af & ~(Active | SamplingPaused)); } return Nothing(); } static bool IsActive() { return uint32_t(sActiveAndFeatures) & Active; } static bool IsActiveWithFeature(uint32_t aFeature) { uint32_t af = sActiveAndFeatures; // copy it first return (af & Active) && (af & aFeature); } // True if profiler is active, and not fully paused. // Note that periodic sampling *could* be paused! static bool IsActiveAndUnpaused() { uint32_t af = sActiveAndFeatures; // copy it first return (af & Active) && !(af & Paused); } // True if profiler is active, and sampling is not paused (though generic // `SetPaused()` or specific `SetSamplingPaused()`). static bool IsActiveAndSamplingUnpaused() { uint32_t af = sActiveAndFeatures; // copy it first return (af & Active) && !(af & (Paused | SamplingPaused)); } private: static constexpr uint32_t Active = 1u << 31; static constexpr uint32_t Paused = 1u << 30; static constexpr uint32_t SamplingPaused = 1u << 29; // Ensure Active/Paused don't overlap with any of the feature bits. # define NO_OVERLAP(n_, str_, Name_, desc_) \ static_assert(ProfilerFeature::Name_ != SamplingPaused, \ "bad feature value"); PROFILER_FOR_EACH_FEATURE(NO_OVERLAP); # undef NO_OVERLAP // We combine the active bit with the feature bits so they can be read or // written in a single atomic operation. Accesses to this atomic are not // recorded by web replay as they may occur at non-deterministic points. static mozilla::Atomic sActiveAndFeatures; }; bool IsThreadBeingProfiled(); bool IsThreadRegistered(); } // namespace detail } // namespace profiler } // namespace mozilla //--------------------------------------------------------------------------- // Start and stop the profiler //--------------------------------------------------------------------------- static constexpr mozilla::PowerOfTwo32 PROFILER_DEFAULT_ENTRIES = # if !defined(GP_PLAT_arm_android) mozilla::MakePowerOfTwo32<8 * 1024 * 1024>(); // 8M entries = 64MB # else mozilla::MakePowerOfTwo32<2 * 1024 * 1024>(); // 2M entries = 16MB # endif // Startup profiling usually need to capture more data, especially on slow // systems. // Note: Keep in sync with GeckoThread.maybeStartGeckoProfiler: // https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java static constexpr mozilla::PowerOfTwo32 PROFILER_DEFAULT_STARTUP_ENTRIES = # if !defined(GP_PLAT_arm_android) mozilla::MakePowerOfTwo32<64 * 1024 * 1024>(); // 64M entries = 512MB # else mozilla::MakePowerOfTwo32<8 * 1024 * 1024>(); // 8M entries = 64MB # endif # define PROFILER_DEFAULT_DURATION 20 /* seconds, for tests only */ // Note: Keep in sync with GeckoThread.maybeStartGeckoProfiler: // https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java # define PROFILER_DEFAULT_INTERVAL 1 /* millisecond */ # define PROFILER_MAX_INTERVAL 5000 /* milliseconds */ // Initialize the profiler. If MOZ_PROFILER_STARTUP is set the profiler will // also be started. This call must happen before any other profiler calls // (except profiler_start(), which will call profiler_init() if it hasn't // already run). void profiler_init(void* stackTop); void profiler_init_threadmanager(); // Call this as early as possible # define AUTO_PROFILER_INIT mozilla::AutoProfilerInit PROFILER_RAII // Call this after the nsThreadManager is Init()ed # define AUTO_PROFILER_INIT2 mozilla::AutoProfilerInit2 PROFILER_RAII enum class IsFastShutdown { No, Yes, }; // Clean up the profiler module, stopping it if required. This function may // also save a shutdown profile if requested. No profiler calls should happen // after this point and all profiling stack labels should have been popped. void profiler_shutdown(IsFastShutdown aIsFastShutdown = IsFastShutdown::No); // Start the profiler -- initializing it first if necessary -- with the // selected options. Stops and restarts the profiler if it is already active. // After starting the profiler is "active". The samples will be recorded in a // circular buffer. // "aCapacity" is the maximum number of 8-bytes entries in the profiler's // circular buffer. // "aInterval" the sampling interval, measured in millseconds. // "aFeatures" is the feature set. Features unsupported by this // platform/configuration are ignored. // "aFilters" is the list of thread filters. Threads that do not match any // of the filters are not profiled. A filter matches a thread if // (a) the thread name contains the filter as a case-insensitive // substring, or // (b) the filter is of the form "pid:" where n is the process // id of the process that the thread is running in. // "aActiveBrowsingContextID" Browsing Context of the active browser screen's // active tab. It's being used to determine the profiled tab. // It's "0" if we failed to get the ID. // "aDuration" is the duration of entries in the profiler's circular buffer. void profiler_start( mozilla::PowerOfTwo32 aCapacity, double aInterval, uint32_t aFeatures, const char** aFilters, uint32_t aFilterCount, uint64_t aActiveBrowsingContextID, const mozilla::Maybe& aDuration = mozilla::Nothing()); // Stop the profiler and discard the profile without saving it. A no-op if the // profiler is inactive. After stopping the profiler is "inactive". void profiler_stop(); // If the profiler is inactive, start it. If it's already active, restart it if // the requested settings differ from the current settings. Both the check and // the state change are performed while the profiler state is locked. // The only difference to profiler_start is that the current buffer contents are // not discarded if the profiler is already running with the requested settings. void profiler_ensure_started( mozilla::PowerOfTwo32 aCapacity, double aInterval, uint32_t aFeatures, const char** aFilters, uint32_t aFilterCount, uint64_t aActiveBrowsingContextID, const mozilla::Maybe& aDuration = mozilla::Nothing()); //--------------------------------------------------------------------------- // Control the profiler //--------------------------------------------------------------------------- // Register/unregister threads with the profiler. Both functions operate the // same whether the profiler is active or inactive. # define PROFILER_REGISTER_THREAD(name) \ do { \ char stackTop; \ profiler_register_thread(name, &stackTop); \ } while (0) # define PROFILER_UNREGISTER_THREAD() profiler_unregister_thread() ProfilingStack* profiler_register_thread(const char* name, void* guessStackTop); void profiler_unregister_thread(); // Registers a DOM Window (the JS global `window`) with the profiler. Each // Window _roughly_ corresponds to a single document loaded within a // BrowsingContext. The unique IDs for both the Window and BrowsingContext are // recorded to allow correlating different Windows loaded within the same tab or // frame element. // // We register pages for each navigations but we do not register // history.pushState or history.replaceState since they correspond to the same // Inner Window ID. When a Browsing context is first loaded, the first url // loaded in it will be about:blank. Because of that, this call keeps the first // non-about:blank registration of window and discards the previous one. // // "aBrowsingContextID" is the ID of the browsing context that document // belongs to. That's used to determine the tab of // that page. // "aInnerWindowID" is the ID of the `window` global object of that // document. // "aUrl" is the URL of the page. // "aEmbedderInnerWindowID" is the inner window id of embedder. It's used to // determine sub documents of a page. void profiler_register_page(uint64_t aBrowsingContextID, uint64_t aInnerWindowID, const nsCString& aUrl, uint64_t aEmbedderInnerWindowID); // Unregister page with the profiler. // // Take a Inner Window ID and unregister the page entry that has the same ID. void profiler_unregister_page(uint64_t aRegisteredInnerWindowID); // Remove all registered and unregistered pages in the profiler. void profiler_clear_all_pages(); class BaseProfilerCount; void profiler_add_sampled_counter(BaseProfilerCount* aCounter); void profiler_remove_sampled_counter(BaseProfilerCount* aCounter); // Register and unregister a thread within a scope. # define AUTO_PROFILER_REGISTER_THREAD(name) \ mozilla::AutoProfilerRegisterThread PROFILER_RAII(name) enum class SamplingState { JustStopped, // Sampling loop has just stopped without sampling, between the // callback registration and now. SamplingPaused, // Profiler is active but sampling loop has gone through a // pause. NoStackSamplingCompleted, // A full sampling loop has completed in // no-stack-sampling mode. SamplingCompleted // A full sampling loop has completed. }; using PostSamplingCallback = std::function; // Install a callback to be invoked at the end of the next sampling loop. // - `false` if profiler is not active, `aCallback` will stay untouched. // - `true` if `aCallback` was successfully moved-from into internal storage, // and *will* be invoked at the end of the next sampling cycle. Note that this // will happen on the Sampler thread, and will block further sampling, so // please be mindful not to block for a long time (e.g., just dispatch a // runnable to another thread.) Calling profiler functions from the callback // is allowed. [[nodiscard]] bool profiler_callback_after_sampling( PostSamplingCallback&& aCallback); // Pause and resume the profiler. No-ops if the profiler is inactive. While // paused the profile will not take any samples and will not record any data // into its buffers. The profiler remains fully initialized in this state. // Timeline markers will still be stored. This feature will keep JavaScript // profiling enabled, thus allowing toggling the profiler without invalidating // the JIT. void profiler_pause(); void profiler_resume(); // Only pause and resume the periodic sampling loop, including stack sampling, // counters, and profiling overheads. void profiler_pause_sampling(); void profiler_resume_sampling(); // These functions tell the profiler that a thread went to sleep so that we can // avoid sampling it while it's sleeping. Calling profiler_thread_sleep() // twice without an intervening profiler_thread_wake() is an error. All three // functions operate the same whether the profiler is active or inactive. void profiler_thread_sleep(); void profiler_thread_wake(); // Mark a thread as asleep/awake within a scope. # define AUTO_PROFILER_THREAD_SLEEP \ mozilla::AutoProfilerThreadSleep PROFILER_RAII # define AUTO_PROFILER_THREAD_WAKE \ mozilla::AutoProfilerThreadWake PROFILER_RAII // Called by the JSRuntime's operation callback. This is used to start profiling // on auxiliary threads. Operates the same whether the profiler is active or // not. # define PROFILER_JS_INTERRUPT_CALLBACK() profiler_js_interrupt_callback() void profiler_js_interrupt_callback(); // Set and clear the current thread's JSContext. # define PROFILER_SET_JS_CONTEXT(cx) profiler_set_js_context(cx) # define PROFILER_CLEAR_JS_CONTEXT() profiler_clear_js_context() void profiler_set_js_context(JSContext* aCx); void profiler_clear_js_context(); //--------------------------------------------------------------------------- // Get information from the profiler //--------------------------------------------------------------------------- // Is the profiler active? Note: the return value of this function can become // immediately out-of-date. E.g. the profile might be active but then // profiler_stop() is called immediately afterward. One common and reasonable // pattern of usage is the following: // // if (profiler_is_active()) { // ExpensiveData expensiveData = CreateExpensiveData(); // PROFILER_OPERATION(expensiveData); // } // // where PROFILER_OPERATION is a no-op if the profiler is inactive. In this // case the profiler_is_active() check is just an optimization -- it prevents // us calling CreateExpensiveData() unnecessarily in most cases, but the // expensive data will end up being created but not used if another thread // stops the profiler between the CreateExpensiveData() and PROFILER_OPERATION // calls. inline bool profiler_is_active() { return mozilla::profiler::detail::RacyFeatures::IsActive(); } // Same as profiler_is_active(), but with the same extra checks that determine // if the profiler would currently store markers. So this should be used before // doing some potentially-expensive work that's used in a marker. E.g.: // // if (profiler_can_accept_markers()) { // ExpensiveMarkerPayload expensivePayload = CreateExpensivePayload(); // BASE_PROFILER_ADD_MARKER_WITH_PAYLOAD(name, OTHER, expensivePayload); // } inline bool profiler_can_accept_markers() { return mozilla::profiler::detail::RacyFeatures::IsActiveAndUnpaused(); } // Is the profiler active, and is the current thread being profiled? // (Same caveats and recommented usage as profiler_is_active().) inline bool profiler_thread_is_being_profiled() { return profiler_is_active() && mozilla::profiler::detail::IsThreadBeingProfiled(); } // During profiling, if the current thread is registered, return true // (regardless of whether it is actively being profiled). // (Same caveats and recommented usage as profiler_is_active().) inline bool profiler_is_active_and_thread_is_registered() { return profiler_is_active() && mozilla::profiler::detail::IsThreadRegistered(); } // Is the profiler active and paused? Returns false if the profiler is inactive. bool profiler_is_paused(); // Is the profiler active and sampling is paused? Returns false if the profiler // is inactive. bool profiler_is_sampling_paused(); // Is the current thread sleeping? bool profiler_thread_is_sleeping(); // Get all the features supported by the profiler that are accepted by // profiler_start(). The result is the same whether the profiler is active or // not. uint32_t profiler_get_available_features(); // Returns the full feature set if the profiler is active. // Note: the return value can become immediately out-of-date, much like the // return value of profiler_is_active(). inline mozilla::Maybe profiler_features_if_active() { return mozilla::profiler::detail::RacyFeatures::FeaturesIfActive(); } // Returns the full feature set if the profiler is active and unpaused. // Note: the return value can become immediately out-of-date, much like the // return value of profiler_is_active(). inline mozilla::Maybe profiler_features_if_active_and_unpaused() { return mozilla::profiler::detail::RacyFeatures::FeaturesIfActiveAndUnpaused(); } // Check if a profiler feature (specified via the ProfilerFeature type) is // active. Returns false if the profiler is inactive. Note: the return value // can become immediately out-of-date, much like the return value of // profiler_is_active(). bool profiler_feature_active(uint32_t aFeature); // Get the params used to start the profiler. Returns 0 and an empty vector // (via outparams) if the profile is inactive. It's possible that the features // returned may be slightly different to those requested due to required // adjustments. void profiler_get_start_params( int* aEntrySize, mozilla::Maybe* aDuration, double* aInterval, uint32_t* aFeatures, mozilla::Vector* aFilters, uint64_t* aActiveBrowsingContextID); // Get the chunk manager used in the current profiling session, or null. mozilla::ProfileBufferControlledChunkManager* profiler_get_controlled_chunk_manager(); // The number of milliseconds since the process started. Operates the same // whether the profiler is active or inactive. double profiler_time(); // Get the current process's ID. int profiler_current_process_id(); // Get the current thread's ID. int profiler_current_thread_id(); // Statically initialized to 0, then set once from profiler_init(), which should // be called from the main thread before any other use of the profiler. extern int scProfilerMainThreadId; inline int profiler_main_thread_id() { return scProfilerMainThreadId; } inline bool profiler_is_main_thread() { return profiler_current_thread_id() == profiler_main_thread_id(); } // An object of this class is passed to profiler_suspend_and_sample_thread(). // For each stack frame, one of the Collect methods will be called. class ProfilerStackCollector { public: // Some collectors need to worry about possibly overwriting previous // generations of data. If that's not an issue, this can return Nothing, // which is the default behaviour. virtual mozilla::Maybe SamplePositionInBuffer() { return mozilla::Nothing(); } virtual mozilla::Maybe BufferRangeStart() { return mozilla::Nothing(); } // This method will be called once if the thread being suspended is the main // thread. Default behaviour is to do nothing. virtual void SetIsMainThread() {} // WARNING: The target thread is suspended when the Collect methods are // called. Do not try to allocate or acquire any locks, or you could // deadlock. The target thread will have resumed by the time this function // returns. virtual void CollectNativeLeafAddr(void* aAddr) = 0; virtual void CollectJitReturnAddr(void* aAddr) = 0; virtual void CollectWasmFrame(const char* aLabel) = 0; virtual void CollectProfilingStackFrame( const js::ProfilingStackFrame& aFrame) = 0; }; // This method suspends the thread identified by aThreadId, samples its // profiling stack, JS stack, and (optionally) native stack, passing the // collected frames into aCollector. aFeatures dictates which compiler features // are used. |Leaf| is the only relevant one. void profiler_suspend_and_sample_thread(int aThreadId, uint32_t aFeatures, ProfilerStackCollector& aCollector, bool aSampleNative = true); struct ProfilerBacktraceDestructor { void operator()(ProfilerBacktrace*); }; using UniqueProfilerBacktrace = mozilla::UniquePtr; // Immediately capture the current thread's call stack, store it in the provided // buffer (usually to avoid allocations if you can construct the buffer on the // stack). Returns false if unsuccessful, or if the profiler is inactive. bool profiler_capture_backtrace_into( mozilla::ProfileChunkedBuffer& aChunkedBuffer); // Immediately capture the current thread's call stack, and return it in a // ProfileChunkedBuffer (usually for later use in MarkerStack::TakeBacktrace()). // May be null if unsuccessful, or if the profiler is inactive. mozilla::UniquePtr profiler_capture_backtrace(); // Immediately capture the current thread's call stack, and return it in a // ProfilerBacktrace (usually for later use in marker function that take a // ProfilerBacktrace). May be null if unsuccessful, or if the profiler is // inactive. UniqueProfilerBacktrace profiler_get_backtrace(); struct ProfilerStats { unsigned n = 0; double sum = 0; double min = std::numeric_limits::max(); double max = 0; void Count(double v) { ++n; sum += v; if (v < min) { min = v; } if (v > max) { max = v; } } }; struct ProfilerBufferInfo { // Index of the oldest entry. uint64_t mRangeStart; // Index of the newest entry. uint64_t mRangeEnd; // Buffer capacity in number of 8-byte entries. uint32_t mEntryCount; // Sampling stats: Interval between successive samplings. ProfilerStats mIntervalsUs; // Sampling stats: Total sampling duration. (Split detail below.) ProfilerStats mOverheadsUs; // Sampling stats: Time to acquire the lock before sampling. ProfilerStats mLockingsUs; // Sampling stats: Time to discard expired data. ProfilerStats mCleaningsUs; // Sampling stats: Time to collect counter data. ProfilerStats mCountersUs; // Sampling stats: Time to sample thread stacks. ProfilerStats mThreadsUs; }; // Get information about the current buffer status. // Returns Nothing() if the profiler is inactive. // // This information may be useful to a user-interface displaying the current // status of the profiler, allowing the user to get a sense for how fast the // buffer is being written to, and how much data is visible. mozilla::Maybe profiler_get_buffer_info(); // ProfilerMarkers.h requires some stuff from this header. // TODO: Move common stuff to shared header, and move this #include to the top. # include "mozilla/ProfilerMarkers.h" //--------------------------------------------------------------------------- // Put profiling data into the profiler (labels and markers) //--------------------------------------------------------------------------- // Insert an RAII object in this scope to enter a label stack frame. Any // samples collected in this scope will contain this label in their stack. // The label argument must be a static C string. It is usually of the // form "ClassName::FunctionName". (Ideally we'd use the compiler to provide // that for us, but __func__ gives us the function name without the class // name.) If the label applies to only part of a function, you can qualify it // like this: "ClassName::FunctionName:PartName". // // Use AUTO_PROFILER_LABEL_DYNAMIC_* if you want to add additional / dynamic // information to the label stack frame. # define AUTO_PROFILER_LABEL(label, categoryPair) \ mozilla::AutoProfilerLabel PROFILER_RAII( \ label, nullptr, JS::ProfilingCategoryPair::categoryPair) // Similar to AUTO_PROFILER_LABEL, but with only one argument: the category // pair. The label string is taken from the category pair. This is convenient // for labels like AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_LayerBuilding) // which would otherwise just repeat the string. # define AUTO_PROFILER_LABEL_CATEGORY_PAIR(categoryPair) \ mozilla::AutoProfilerLabel PROFILER_RAII( \ "", nullptr, JS::ProfilingCategoryPair::categoryPair, \ uint32_t(js::ProfilingStackFrame::Flags:: \ LABEL_DETERMINED_BY_CATEGORY_PAIR)) // Similar to AUTO_PROFILER_LABEL, but with an additional string. The inserted // RAII object stores the cStr pointer in a field; it does not copy the string. // // WARNING: This means that the string you pass to this macro needs to live at // least until the end of the current scope. Be careful using this macro with // ns[C]String; the other AUTO_PROFILER_LABEL_DYNAMIC_* macros below are // preferred because they avoid this problem. // // If the profiler samples the current thread and walks the label stack while // this RAII object is on the stack, it will copy the supplied string into the // profile buffer. So there's one string copy operation, and it happens at // sample time. // // Compare this to the plain AUTO_PROFILER_LABEL macro, which only accepts // literal strings: When the label stack frames generated by // AUTO_PROFILER_LABEL are sampled, no string copy needs to be made because the // profile buffer can just store the raw pointers to the literal strings. // Consequently, AUTO_PROFILER_LABEL frames take up considerably less space in // the profile buffer than AUTO_PROFILER_LABEL_DYNAMIC_* frames. # define AUTO_PROFILER_LABEL_DYNAMIC_CSTR(label, categoryPair, cStr) \ mozilla::AutoProfilerLabel PROFILER_RAII( \ label, cStr, JS::ProfilingCategoryPair::categoryPair) // Like AUTO_PROFILER_LABEL_DYNAMIC_CSTR, but with the NONSENSITIVE flag to // note that it does not contain sensitive information (so we can include it // in, for example, the BackgroundHangMonitor) # define AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(label, categoryPair, \ cStr) \ mozilla::AutoProfilerLabel PROFILER_RAII( \ label, cStr, JS::ProfilingCategoryPair::categoryPair, \ uint32_t(js::ProfilingStackFrame::Flags::NONSENSITIVE)) // Similar to AUTO_PROFILER_LABEL_DYNAMIC_CSTR, but takes an nsACString. // // Note: The use of the Maybe<>s ensures the scopes for the dynamic string and // the AutoProfilerLabel are appropriate, while also not incurring the runtime // cost of the string assignment unless the profiler is active. Therefore, // unlike AUTO_PROFILER_LABEL and AUTO_PROFILER_LABEL_DYNAMIC_CSTR, this macro // doesn't push/pop a label when the profiler is inactive. # define AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(label, categoryPair, nsCStr) \ mozilla::Maybe autoCStr; \ mozilla::Maybe raiiObjectNsCString; \ if (profiler_is_active()) { \ autoCStr.emplace(nsCStr); \ raiiObjectNsCString.emplace(label, autoCStr->get(), \ JS::ProfilingCategoryPair::categoryPair); \ } // See note above AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE # define AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( \ label, categoryPair, nsCStr) \ mozilla::Maybe autoCStr; \ mozilla::Maybe raiiObjectNsCString; \ if (profiler_is_active()) { \ autoCStr.emplace(nsCStr); \ raiiObjectNsCString.emplace( \ label, autoCStr->get(), JS::ProfilingCategoryPair::categoryPair, \ uint32_t(js::ProfilingStackFrame::Flags::NONSENSITIVE)); \ } // Similar to AUTO_PROFILER_LABEL_DYNAMIC_CSTR, but takes an nsString that is // is lossily converted to an ASCII string. // // Note: The use of the Maybe<>s ensures the scopes for the converted dynamic // string and the AutoProfilerLabel are appropriate, while also not incurring // the runtime cost of the string conversion unless the profiler is active. // Therefore, unlike AUTO_PROFILER_LABEL and AUTO_PROFILER_LABEL_DYNAMIC_CSTR, // this macro doesn't push/pop a label when the profiler is inactive. # define AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(label, categoryPair, \ nsStr) \ mozilla::Maybe asciiStr; \ mozilla::Maybe raiiObjectLossyNsString; \ if (profiler_is_active()) { \ asciiStr.emplace(nsStr); \ raiiObjectLossyNsString.emplace( \ label, asciiStr->get(), JS::ProfilingCategoryPair::categoryPair); \ } // Similar to AUTO_PROFILER_LABEL, but accepting a JSContext* parameter, and a // no-op if the profiler is disabled. // Used to annotate functions for which overhead in the range of nanoseconds is // noticeable. It avoids overhead from the TLS lookup because it can get the // ProfilingStack from the JS context, and avoids almost all overhead in the // case where the profiler is disabled. # define AUTO_PROFILER_LABEL_FAST(label, categoryPair, ctx) \ mozilla::AutoProfilerLabel PROFILER_RAII( \ ctx, label, nullptr, JS::ProfilingCategoryPair::categoryPair) // Similar to AUTO_PROFILER_LABEL_FAST, but also takes an extra string and an // additional set of flags. The flags parameter should carry values from the // js::ProfilingStackFrame::Flags enum. # define AUTO_PROFILER_LABEL_DYNAMIC_FAST(label, dynamicString, categoryPair, \ ctx, flags) \ mozilla::AutoProfilerLabel PROFILER_RAII( \ ctx, label, dynamicString, JS::ProfilingCategoryPair::categoryPair, \ flags) void profiler_add_js_marker(const char* aMarkerName, const char* aMarkerText); void profiler_add_js_allocation_marker(JS::RecordAllocationInfo&& info); // Returns true or or false depending on whether the marker was actually added // or not. bool profiler_add_native_allocation_marker(int64_t aSize, uintptr_t aMemoryAddress); // Returns true if any of the profiler mutexes are currently locked *on the // current thread*. This may be used by re-entrant code that may call profiler // functions while the same of a different profiler mutex is locked, which could // deadlock. bool profiler_is_locked_on_current_thread(); enum class NetworkLoadType { LOAD_START, LOAD_STOP, LOAD_REDIRECT }; void profiler_add_network_marker( nsIURI* aURI, const nsACString& aRequestMethod, int32_t aPriority, uint64_t aChannelId, NetworkLoadType aType, mozilla::TimeStamp aStart, mozilla::TimeStamp aEnd, int64_t aCount, mozilla::net::CacheDisposition aCacheDisposition, uint64_t aInnerWindowID, const mozilla::net::TimingStruct* aTimings = nullptr, nsIURI* aRedirectURI = nullptr, mozilla::UniquePtr aSource = nullptr, const mozilla::Maybe& aContentType = mozilla::Nothing()); enum TracingKind { TRACING_EVENT, TRACING_INTERVAL_START, TRACING_INTERVAL_END, }; // This is a helper function to get the Inner Window ID from DocShell but it's // not a recommended method to get it and it's not encouraged to use this // function. If there is a computed inner window ID, `window`, or `Document` // available in the call site, please use them. Use this function as a last // resort. mozilla::Maybe profiler_get_inner_window_id_from_docshell( nsIDocShell* aDocshell); inline mozilla::MarkerInnerWindowId MarkerInnerWindowIdFromDocShell( nsIDocShell* aDocshell) { mozilla::Maybe id = profiler_get_inner_window_id_from_docshell(aDocshell); if (!id) { return mozilla::MarkerInnerWindowId::NoId(); } return mozilla::MarkerInnerWindowId(*id); } //--------------------------------------------------------------------------- // Output profiles //--------------------------------------------------------------------------- // Set a user-friendly process name, used in JSON stream. Allows an optional // detailed name which may include private info (eTLD+1 in fission) void profiler_set_process_name(const nsACString& aProcessName, const nsACString* aETLDplus1 = nullptr); // Get the profile encoded as a JSON string. A no-op (returning nullptr) if the // profiler is inactive. // If aIsShuttingDown is true, the current time is included as the process // shutdown time in the JSON's "meta" object. mozilla::UniquePtr profiler_get_profile(double aSinceTime = 0, bool aIsShuttingDown = false); // Write the profile for this process (excluding subprocesses) into aWriter. // Returns false if the profiler is inactive. bool profiler_stream_json_for_this_process( mozilla::baseprofiler::SpliceableJSONWriter& aWriter, double aSinceTime = 0, bool aIsShuttingDown = false, ProfilerCodeAddressService* aService = nullptr); // Get the profile and write it into a file. A no-op if the profile is // inactive. // // This function is 'extern "C"' so that it is easily callable from a debugger // in a build without debugging information (a workaround for // http://llvm.org/bugs/show_bug.cgi?id=22211). extern "C" { void profiler_save_profile_to_file(const char* aFilename); } //--------------------------------------------------------------------------- // RAII classes //--------------------------------------------------------------------------- class TLSRegisteredThread; // Needed for friendship in ProfilingStackOwnerTLS. namespace mozilla { class MOZ_RAII AutoProfilerInit { public: explicit AutoProfilerInit() { profiler_init(this); } ~AutoProfilerInit() { profiler_shutdown(); } private: }; class MOZ_RAII AutoProfilerInit2 { public: explicit AutoProfilerInit2() { profiler_init_threadmanager(); } private: }; // Convenience class to register and unregister a thread with the profiler. // Needs to be the first object on the stack of the thread. class MOZ_RAII AutoProfilerRegisterThread final { public: explicit AutoProfilerRegisterThread(const char* aName) { profiler_register_thread(aName, this); } ~AutoProfilerRegisterThread() { profiler_unregister_thread(); } private: AutoProfilerRegisterThread(const AutoProfilerRegisterThread&) = delete; AutoProfilerRegisterThread& operator=(const AutoProfilerRegisterThread&) = delete; }; class MOZ_RAII AutoProfilerThreadSleep { public: explicit AutoProfilerThreadSleep() { profiler_thread_sleep(); } ~AutoProfilerThreadSleep() { profiler_thread_wake(); } private: }; // Temporarily wake up the profiling of a thread while servicing events such as // Asynchronous Procedure Calls (APCs). class MOZ_RAII AutoProfilerThreadWake { public: explicit AutoProfilerThreadWake() : mIssuedWake(profiler_thread_is_sleeping()) { if (mIssuedWake) { profiler_thread_wake(); } } ~AutoProfilerThreadWake() { if (mIssuedWake) { MOZ_ASSERT(!profiler_thread_is_sleeping()); profiler_thread_sleep(); } } private: bool mIssuedWake; }; // Ref-counted shell around a `ProfilingStack`, to be used by the owning // (Racy)RegisteredThread and AutoProfilerLabel. class ProfilingStackOwner { public: class ProfilingStack& ProfilingStack() { return mProfilingStack; } // Using hand-rolled ref-counting, to evade leak checking (emergency patch // for bug 1445822). // TODO: Eliminate all/most leaks if possible. void AddRef() const { ++mRefCnt; } void Release() const { MOZ_ASSERT(int32_t(mRefCnt) > 0); if (--mRefCnt == 0) { if (mProfilingStack.stackSize() > 0) { DumpStackAndCrash(); } delete this; } } private: ~ProfilingStackOwner() = default; MOZ_NORETURN void DumpStackAndCrash() const; class ProfilingStack mProfilingStack; mutable Atomic mRefCnt; }; // This class creates a non-owning ProfilingStack reference. Objects of this // class are stack-allocated, and so exist within a thread, and are thus bounded // by the lifetime of the thread, which ensures that the references held can't // be used after the ProfilingStack is destroyed. class MOZ_RAII AutoProfilerLabel { public: // This is the AUTO_PROFILER_LABEL and AUTO_PROFILER_LABEL_DYNAMIC variant. AutoProfilerLabel(const char* aLabel, const char* aDynamicString, JS::ProfilingCategoryPair aCategoryPair, uint32_t aFlags = 0) { // Get the ProfilingStack from TLS. ProfilingStackOwner* profilingStackOwner = ProfilingStackOwnerTLS::Get(); Push(profilingStackOwner ? &profilingStackOwner->ProfilingStack() : nullptr, aLabel, aDynamicString, aCategoryPair, aFlags); } // This is the AUTO_PROFILER_LABEL_FAST variant. It retrieves the // ProfilingStack from the JSContext and does nothing if the profiler is // inactive. AutoProfilerLabel(JSContext* aJSContext, const char* aLabel, const char* aDynamicString, JS::ProfilingCategoryPair aCategoryPair, uint32_t aFlags) { Push(js::GetContextProfilingStackIfEnabled(aJSContext), aLabel, aDynamicString, aCategoryPair, aFlags); } void Push(ProfilingStack* aProfilingStack, const char* aLabel, const char* aDynamicString, JS::ProfilingCategoryPair aCategoryPair, uint32_t aFlags = 0) { // This function runs both on and off the main thread. mProfilingStack = aProfilingStack; if (mProfilingStack) { mProfilingStack->pushLabelFrame(aLabel, aDynamicString, this, aCategoryPair, aFlags); } } ~AutoProfilerLabel() { // This function runs both on and off the main thread. if (mProfilingStack) { mProfilingStack->pop(); } } private: // We save a ProfilingStack pointer in the ctor so we don't have to redo the // TLS lookup in the dtor. ProfilingStack* mProfilingStack; public: // See the comment on the definition in platform.cpp for details about this. class ProfilingStackOwnerTLS { public: static ProfilingStackOwner* Get() { MOZ_ASSERT( sState != State::Uninitialized, "ProfilingStackOwnerTLS::Get() should only be called after Init()"); if (sState != State::Initialized) { return nullptr; } return sProfilingStackOwnerTLS.get(); } static void Set(ProfilingStackOwner* aProfilingStackOwner) { MOZ_ASSERT( sState != State::Uninitialized, "ProfilingStackOwnerTLS::Set() should only be called after Init()"); MOZ_DIAGNOSTIC_ASSERT(sState == State::Initialized, "ProfilingStackOwnerTLS::Set() should only be " "called after a successful Init()"); sProfilingStackOwnerTLS.set(aProfilingStackOwner); } private: friend TLSRegisteredThread; static void Init(); enum class State { Uninitialized = 0, Initialized, Unavailable }; static State sState; static MOZ_THREAD_LOCAL(ProfilingStackOwner*) sProfilingStackOwnerTLS; }; }; // Get the MOZ_PROFILER_STARTUP* environment variables that should be // supplied to a child process that is about to be launched, in order // to make that child process start with the same profiler settings as // in the current process. The given function is invoked once for // each variable to be set. void GetProfilerEnvVarsForChildProcess( std::function&& aSetEnv); } // namespace mozilla #endif // !MOZ_GECKO_PROFILER #endif // GeckoProfiler_h