/* -*- 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. // 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 "BaseProfiler.h" #include "ProfileAdditionalInformation.h" #include "mozilla/ProfilerCounts.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/ProfilerState.h" #include "mozilla/ProfilerThreadSleep.h" #include "mozilla/ProfilerThreadState.h" #include "mozilla/ProgressLogger.h" #include "mozilla/Result.h" #include "mozilla/ResultVariant.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 PROFILER_REGISTER_THREAD(name) # define PROFILER_UNREGISTER_THREAD() # define AUTO_PROFILER_REGISTER_THREAD(name) # define PROFILER_JS_INTERRUPT_CALLBACK() # define PROFILER_SET_JS_CONTEXT(cx) # define PROFILER_CLEAR_JS_CONTEXT() // 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; } // This won't be used, it's just there to allow the empty definitions of // `profiler_capture_backtrace_into` and `profiler_capture_backtrace`. struct ProfileChunkedBuffer {}; static inline bool profiler_capture_backtrace_into( mozilla::ProfileChunkedBuffer& aChunkedBuffer, mozilla::StackCaptureOptions aCaptureOptions) { return false; } static inline mozilla::UniquePtr profiler_capture_backtrace() { return nullptr; } static inline void profiler_set_process_name( const nsACString& aProcessName, const nsACString* aETLDplus1 = nullptr) {} static inline void profiler_received_exit_profile( const nsACString& aExitProfile) {} static inline void profiler_register_page(uint64_t aTabID, uint64_t aInnerWindowID, const nsCString& aUrl, uint64_t aEmbedderInnerWindowID, bool aIsPrivateBrowsing) {} static inline void profiler_unregister_page(uint64_t aRegisteredInnerWindowID) { } static inline void GetProfilerEnvVarsForChildProcess( std::function&& aSetEnv) {} static inline void profiler_record_wakeup_count( const nsACString& aProcessType) {} #else // !MOZ_GECKO_PROFILER # include "js/ProfilingStack.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/ThreadLocal.h" # include "mozilla/TimeStamp.h" # include "mozilla/UniquePtr.h" # include "nscore.h" # include "nsINamed.h" # include "nsString.h" # include "nsThreadUtils.h" # include # include class ProfilerBacktrace; class ProfilerCodeAddressService; struct JSContext; namespace mozilla { class ProfileBufferControlledChunkManager; class ProfileChunkedBuffer; namespace baseprofiler { class SpliceableJSONWriter; } // namespace baseprofiler } // namespace mozilla class nsIURI; enum class ProfilerError { IsInactive, JsonGenerationFailed, }; template using ProfilerResult = mozilla::Result; //--------------------------------------------------------------------------- // Give information to 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 // 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. // "aIsPrivateBrowsing" is true if this browsing context happens in a // private browsing context. void profiler_register_page(uint64_t aTabID, uint64_t aInnerWindowID, const nsCString& aUrl, uint64_t aEmbedderInnerWindowID, bool aIsPrivateBrowsing); // 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); // 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 //--------------------------------------------------------------------------- // 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(); // 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. // Use `ProfilerThreadId{}` (unspecified) to sample the current thread. void profiler_suspend_and_sample_thread(ProfilerThreadId 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, mozilla::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. 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(); // Record through glean how many times profiler_thread_wake has been // called. void profiler_record_wakeup_count(const nsACString& aProcessType); //--------------------------------------------------------------------------- // 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); // Record an exit profile from a child process. void profiler_received_exit_profile(const nsACString& aExitProfile); // 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 a failed result if the profiler is inactive. ProfilerResult profiler_stream_json_for_this_process( mozilla::baseprofiler::SpliceableJSONWriter& aWriter, double aSinceTime = 0, bool aIsShuttingDown = false, ProfilerCodeAddressService* aService = nullptr, mozilla::ProgressLogger aProgressLogger = {}); // 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 //--------------------------------------------------------------------------- namespace mozilla { // 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; }; // 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