/* -*- 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 BaseProfiler_h #define BaseProfiler_h // This file is safe to include unconditionally, and only defines // empty macros if MOZ_GECKO_PROFILER is not set. // These headers are also safe to include unconditionally, with empty macros if // MOZ_GECKO_PROFILER is not set. // If your file only uses particular APIs (e.g., only markers), please consider // including only the needed headers instead of this one, to reduce compilation // dependencies. #include "mozilla/BaseProfilerCounts.h" #include "mozilla/BaseProfilerLabels.h" #include "mozilla/BaseProfilerMarkers.h" #include "mozilla/BaseProfilerState.h" #ifndef MOZ_GECKO_PROFILER # 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_BASE_PROFILER_INIT \ ::mozilla::baseprofiler::profiler_init_main_thread_id() # define BASE_PROFILER_REGISTER_THREAD(name) # define BASE_PROFILER_UNREGISTER_THREAD() # define AUTO_BASE_PROFILER_REGISTER_THREAD(name) # define AUTO_BASE_PROFILER_THREAD_SLEEP # define AUTO_BASE_PROFILER_THREAD_WAKE // Function stubs for when MOZ_GECKO_PROFILER is not defined. namespace mozilla { namespace baseprofiler { // This won't be used, it's just there to allow the empty definition of // `profiler_get_backtrace`. struct ProfilerBacktrace {}; using UniqueProfilerBacktrace = UniquePtr<ProfilerBacktrace>; // 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( ProfileChunkedBuffer& aChunkedBuffer, StackCaptureOptions aCaptureOptions) { return false; } static inline UniquePtr<ProfileChunkedBuffer> profiler_capture_backtrace() { return nullptr; } static inline void profiler_init(void* stackTop) {} static inline void profiler_shutdown() {} } // namespace baseprofiler } // namespace mozilla #else // !MOZ_GECKO_PROFILER # include "BaseProfilingStack.h" # include "mozilla/Assertions.h" # include "mozilla/Atomics.h" # include "mozilla/Attributes.h" # include "mozilla/BaseProfilerRAIIMacro.h" # include "mozilla/Maybe.h" # include "mozilla/PowerOfTwo.h" # include "mozilla/TimeStamp.h" # include "mozilla/UniquePtr.h" # include <functional> # include <stdint.h> # include <string> namespace mozilla { class MallocAllocPolicy; class ProfileChunkedBuffer; enum class StackCaptureOptions; template <class T, size_t MinInlineCapacity, class AllocPolicy> class Vector; namespace baseprofiler { class ProfilerBacktrace; class SpliceableJSONWriter; //--------------------------------------------------------------------------- // Start and stop the profiler //--------------------------------------------------------------------------- static constexpr PowerOfTwo32 BASE_PROFILER_DEFAULT_ENTRIES = # if !defined(GP_PLAT_arm_android) MakePowerOfTwo32<16 * 1024 * 1024>(); // 16M entries = 128MiB # else MakePowerOfTwo32<4 * 1024 * 1024>(); // 4M entries = 32MiB # 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 PowerOfTwo32 BASE_PROFILER_DEFAULT_STARTUP_ENTRIES = # if !defined(GP_PLAT_arm_android) mozilla::MakePowerOfTwo32<64 * 1024 * 1024>(); // 64M entries = 512MiB # else mozilla::MakePowerOfTwo32<16 * 1024 * 1024>(); // 16M entries = 128MiB # endif // 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 BASE_PROFILER_DEFAULT_INTERVAL 1 /* millisecond */ # define BASE_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). MFBT_API void profiler_init(void* stackTop); # define AUTO_BASE_PROFILER_INIT \ ::mozilla::baseprofiler::AutoProfilerInit PROFILER_RAII // 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. MFBT_API void profiler_shutdown(); // 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-byte 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:<n>" where n is the process // id of the process that the thread is running in. // "aDuration" is the duration of entries in the profiler's circular buffer. MFBT_API void profiler_start(PowerOfTwo32 aCapacity, double aInterval, uint32_t aFeatures, const char** aFilters, uint32_t aFilterCount, const Maybe<double>& aDuration = 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". MFBT_API 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. MFBT_API void profiler_ensure_started( PowerOfTwo32 aCapacity, double aInterval, uint32_t aFeatures, const char** aFilters, uint32_t aFilterCount, const Maybe<double>& aDuration = Nothing()); //--------------------------------------------------------------------------- // Control the profiler //--------------------------------------------------------------------------- // Register/unregister threads with the profiler. Both functions operate the // same whether the profiler is active or inactive. # define BASE_PROFILER_REGISTER_THREAD(name) \ do { \ char stackTop; \ ::mozilla::baseprofiler::profiler_register_thread(name, &stackTop); \ } while (0) # define BASE_PROFILER_UNREGISTER_THREAD() \ ::mozilla::baseprofiler::profiler_unregister_thread() MFBT_API ProfilingStack* profiler_register_thread(const char* name, void* guessStackTop); MFBT_API 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 // browsing context. Both the Window Id and Browser Id 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. // // "aTabID" is the BrowserId of 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. MFBT_API void profiler_register_page(uint64_t aTabD, uint64_t aInnerWindowID, const std::string& aUrl, uint64_t aEmbedderInnerWindowID); // Unregister page with the profiler. // // Take a Inner Window ID and unregister the page entry that has the same ID. MFBT_API void profiler_unregister_page(uint64_t aRegisteredInnerWindowID); // Remove all registered and unregistered pages in the profiler. void profiler_clear_all_pages(); class BaseProfilerCount; MFBT_API void profiler_add_sampled_counter(BaseProfilerCount* aCounter); MFBT_API void profiler_remove_sampled_counter(BaseProfilerCount* aCounter); // Register and unregister a thread within a scope. # define AUTO_BASE_PROFILER_REGISTER_THREAD(name) \ ::mozilla::baseprofiler::AutoProfilerRegisterThread PROFILER_RAII(name) // 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. // This feature will keep JavaScript profiling enabled, thus allowing toggling // the profiler without invalidating the JIT. MFBT_API void profiler_pause(); MFBT_API void profiler_resume(); // Only pause and resume the periodic sampling loop, including stack sampling, // counters, and profiling overheads. MFBT_API void profiler_pause_sampling(); MFBT_API 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. MFBT_API void profiler_thread_sleep(); MFBT_API void profiler_thread_wake(); // Mark a thread as asleep/awake within a scope. # define AUTO_BASE_PROFILER_THREAD_SLEEP \ ::mozilla::baseprofiler::AutoProfilerThreadSleep PROFILER_RAII # define AUTO_BASE_PROFILER_THREAD_WAKE \ ::mozilla::baseprofiler::AutoProfilerThreadWake PROFILER_RAII //--------------------------------------------------------------------------- // Get information from the profiler //--------------------------------------------------------------------------- // 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. MFBT_API void profiler_get_start_params( int* aEntrySize, Maybe<double>* aDuration, double* aInterval, uint32_t* aFeatures, Vector<const char*, 0, MallocAllocPolicy>* aFilters); // The number of milliseconds since the process started. Operates the same // whether the profiler is active or inactive. MFBT_API double profiler_time(); // 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 Maybe<uint64_t> SamplePositionInBuffer() { return Nothing(); } virtual Maybe<uint64_t> BufferRangeStart() { return 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 CollectProfilingStackFrame( const 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. // Use `aThreadId`=0 to sample the current thread. MFBT_API void profiler_suspend_and_sample_thread( int aThreadId, uint32_t aFeatures, ProfilerStackCollector& aCollector, bool aSampleNative = true); struct ProfilerBacktraceDestructor { MFBT_API void operator()(ProfilerBacktrace*); }; using UniqueProfilerBacktrace = UniquePtr<ProfilerBacktrace, ProfilerBacktraceDestructor>; // 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, if the profiler is inactive, or if // aCaptureOptions is NoStack. MFBT_API bool profiler_capture_backtrace_into( ProfileChunkedBuffer& aChunkedBuffer, StackCaptureOptions aCaptureOptions); // 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. MFBT_API UniquePtr<ProfileChunkedBuffer> 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. MFBT_API UniqueProfilerBacktrace profiler_get_backtrace(); struct ProfilerStats { unsigned n = 0; double sum = 0; double min = std::numeric_limits<double>::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 (us) between successive samplings. ProfilerStats mIntervalsUs; // Sampling stats: Total duration (us) of each sampling. (Split detail below.) ProfilerStats mOverheadsUs; // Sampling stats: Time (us) to acquire the lock before sampling. ProfilerStats mLockingsUs; // Sampling stats: Time (us) to discard expired data. ProfilerStats mCleaningsUs; // Sampling stats: Time (us) to collect counter data. ProfilerStats mCountersUs; // Sampling stats: Time (us) 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. MFBT_API Maybe<ProfilerBufferInfo> profiler_get_buffer_info(); } // namespace baseprofiler } // namespace mozilla namespace mozilla { namespace baseprofiler { //--------------------------------------------------------------------------- // Put profiling data into the profiler (markers) //--------------------------------------------------------------------------- MFBT_API void profiler_add_js_marker(const char* aMarkerName, const char* aMarkerText); //--------------------------------------------------------------------------- // Output profiles //--------------------------------------------------------------------------- // Set a user-friendly process name, used in JSON stream. MFBT_API void profiler_set_process_name(const std::string& aProcessName, const std::string* aETLDplus1); // 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. MFBT_API UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0, bool aIsShuttingDown = false, bool aOnlyThreads = false); // Write the profile for this process (excluding subprocesses) into aWriter. // Returns false if the profiler is inactive. MFBT_API bool profiler_stream_json_for_this_process( SpliceableJSONWriter& aWriter, double aSinceTime = 0, bool aIsShuttingDown = false, bool aOnlyThreads = false); // Get the profile and write it into a file. A no-op if the profile is // inactive. // Prefixed with "base" to avoid clashing with Gecko Profiler's extern "C" // profiler_save_profile_to_file when called from debugger. MFBT_API void baseprofiler_save_profile_to_file(const char* aFilename); //--------------------------------------------------------------------------- // RAII classes //--------------------------------------------------------------------------- class MOZ_RAII AutoProfilerInit { public: explicit AutoProfilerInit() { profiler_init(this); } ~AutoProfilerInit() { profiler_shutdown(); } 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; }; // 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. MFBT_API void GetProfilerEnvVarsForChildProcess( std::function<void(const char* key, const char* value)>&& aSetEnv); } // namespace baseprofiler } // namespace mozilla #endif // !MOZ_GECKO_PROFILER #endif // BaseProfiler_h