diff options
Diffstat (limited to '')
-rw-r--r-- | tools/profiler/public/ChildProfilerController.h | 51 | ||||
-rw-r--r-- | tools/profiler/public/GeckoProfiler.h | 1172 | ||||
-rw-r--r-- | tools/profiler/public/GeckoProfilerReporter.h | 26 | ||||
-rw-r--r-- | tools/profiler/public/ProfileBufferEntrySerializationGeckoExtensions.h | 160 | ||||
-rw-r--r-- | tools/profiler/public/ProfileJSONWriter.h | 19 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerChild.h | 91 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerCodeAddressService.h | 52 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerCounts.h | 274 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerMarkerTypes.h | 41 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerMarkers.h | 294 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerMarkersDetail.h | 42 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerMarkersPrerequisites.h | 30 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerParent.h | 95 | ||||
-rw-r--r-- | tools/profiler/public/shared-libraries.h | 155 |
14 files changed, 2502 insertions, 0 deletions
diff --git a/tools/profiler/public/ChildProfilerController.h b/tools/profiler/public/ChildProfilerController.h new file mode 100644 index 0000000000..8be428b18f --- /dev/null +++ b/tools/profiler/public/ChildProfilerController.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef ChildProfilerController_h +#define ChildProfilerController_h + +#include "base/process.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.h" +#include "nsStringFwd.h" + +namespace mozilla { + +class ProfilerChild; +class PProfilerChild; +class PProfilerParent; + +// ChildProfilerController manages the setup and teardown of ProfilerChild. +// It's used on the main thread. +// It manages a background thread that ProfilerChild runs on. +class ChildProfilerController final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChildProfilerController) + + static already_AddRefed<ChildProfilerController> Create( + mozilla::ipc::Endpoint<PProfilerChild>&& aEndpoint); + + [[nodiscard]] nsCString GrabShutdownProfileAndShutdown(); + void Shutdown(); + + private: + ChildProfilerController(); + ~ChildProfilerController(); + void Init(mozilla::ipc::Endpoint<PProfilerChild>&& aEndpoint); + void ShutdownAndMaybeGrabShutdownProfileFirst(nsCString* aOutShutdownProfile); + + // Called on mThread: + void SetupProfilerChild(mozilla::ipc::Endpoint<PProfilerChild>&& aEndpoint); + void ShutdownProfilerChild(nsCString* aOutShutdownProfile); + + RefPtr<ProfilerChild> mProfilerChild; // only accessed on mThread + RefPtr<nsIThread> mThread; +}; + +} // namespace mozilla + +#endif // ChildProfilerController_h diff --git a/tools/profiler/public/GeckoProfiler.h b/tools/profiler/public/GeckoProfiler.h new file mode 100644 index 0000000000..7433a3d2ea --- /dev/null +++ b/tools/profiler/public/GeckoProfiler.h @@ -0,0 +1,1172 @@ +/* -*- 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<int>; + +// 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<mozilla::ProfileChunkedBuffer> +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 <functional> +# include <stdint.h> + +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 T, size_t MinInlineCapacity, class AllocPolicy> +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<uint32_t> 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<uint32_t> 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<uint32_t, mozilla::MemoryOrdering::Relaxed> + 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:<n>" 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<double>& 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<double>& 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<void(SamplingState)>; + +// 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<uint32_t> 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<uint32_t> 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<double>* aDuration, double* aInterval, + uint32_t* aFeatures, + mozilla::Vector<const char*, 0, mozilla::MallocAllocPolicy>* 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<uint64_t> SamplePositionInBuffer() { + return mozilla::Nothing(); + } + virtual mozilla::Maybe<uint64_t> 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<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, 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<mozilla::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. +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 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<ProfilerBufferInfo> 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<nsAutoCString> autoCStr; \ + mozilla::Maybe<mozilla::AutoProfilerLabel> 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<nsAutoCString> autoCStr; \ + mozilla::Maybe<mozilla::AutoProfilerLabel> 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<NS_LossyConvertUTF16toASCII> asciiStr; \ + mozilla::Maybe<mozilla::AutoProfilerLabel> 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<mozilla::ProfileChunkedBuffer> aSource = nullptr, + const mozilla::Maybe<nsDependentCString>& 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<uint64_t> profiler_get_inner_window_id_from_docshell( + nsIDocShell* aDocshell); + +inline mozilla::MarkerInnerWindowId MarkerInnerWindowIdFromDocShell( + nsIDocShell* aDocshell) { + mozilla::Maybe<uint64_t> 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<char[]> 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<int32_t, MemoryOrdering::ReleaseAcquire> 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<void(const char* key, const char* value)>&& aSetEnv); + +} // namespace mozilla + +#endif // !MOZ_GECKO_PROFILER + +#endif // GeckoProfiler_h diff --git a/tools/profiler/public/GeckoProfilerReporter.h b/tools/profiler/public/GeckoProfilerReporter.h new file mode 100644 index 0000000000..f5bf41f223 --- /dev/null +++ b/tools/profiler/public/GeckoProfilerReporter.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef GeckoProfilerReporter_h +#define GeckoProfilerReporter_h + +#include "nsIMemoryReporter.h" + +class GeckoProfilerReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + GeckoProfilerReporter() {} + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override; + + private: + ~GeckoProfilerReporter() {} +}; + +#endif diff --git a/tools/profiler/public/ProfileBufferEntrySerializationGeckoExtensions.h b/tools/profiler/public/ProfileBufferEntrySerializationGeckoExtensions.h new file mode 100644 index 0000000000..1578bd2ddc --- /dev/null +++ b/tools/profiler/public/ProfileBufferEntrySerializationGeckoExtensions.h @@ -0,0 +1,160 @@ +/* -*- 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/. */ + +#ifndef ProfileBufferEntrySerializationGeckoExtensions_h +#define ProfileBufferEntrySerializationGeckoExtensions_h + +#include "mozilla/ProfileBufferEntrySerialization.h" + +#include "js/AllocPolicy.h" +#include "js/Utility.h" +#include "nsString.h" + +namespace mozilla { + +// ---------------------------------------------------------------------------- +// ns[C]String + +// nsString or nsCString contents are serialized as the number of bytes (encoded +// as ULEB128) and all the characters in the string. The terminal '\0' is +// omitted. +// Make sure you write and read with the same character type! +// +// Usage: `nsCString s = ...; aEW.WriteObject(s);` +template <typename CHAR> +struct ProfileBufferEntryWriter::Serializer<nsTString<CHAR>> { + static Length Bytes(const nsTString<CHAR>& aS) { + const auto length = aS.Length(); + return ProfileBufferEntryWriter::ULEB128Size(length) + + static_cast<Length>(length * sizeof(CHAR)); + } + + static void Write(ProfileBufferEntryWriter& aEW, const nsTString<CHAR>& aS) { + const auto length = aS.Length(); + aEW.WriteULEB128(length); + // Copy the bytes from the string's buffer. + aEW.WriteBytes(aS.Data(), length * sizeof(CHAR)); + } +}; + +template <typename CHAR> +struct ProfileBufferEntryReader::Deserializer<nsTString<CHAR>> { + static void ReadInto(ProfileBufferEntryReader& aER, nsTString<CHAR>& aS) { + aS = Read(aER); + } + + static nsTString<CHAR> Read(ProfileBufferEntryReader& aER) { + const Length length = aER.ReadULEB128<Length>(); + nsTString<CHAR> s; + // BulkWrite is the most efficient way to copy bytes into the target string. + auto writerOrErr = s.BulkWrite(length, 0, true); + MOZ_RELEASE_ASSERT(!writerOrErr.isErr()); + + auto writer = writerOrErr.unwrap(); + + aER.ReadBytes(writer.Elements(), length * sizeof(CHAR)); + writer.Finish(length, true); + return s; + } +}; + +// ---------------------------------------------------------------------------- +// nsAuto[C]String + +// nsAuto[C]String contents are serialized as the number of bytes (encoded as +// ULEB128) and all the characters in the string. The terminal '\0' is omitted. +// Make sure you write and read with the same character type! +// +// Usage: `nsAutoCString s = ...; aEW.WriteObject(s);` +template <typename CHAR, size_t N> +struct ProfileBufferEntryWriter::Serializer<nsTAutoStringN<CHAR, N>> { + static Length Bytes(const nsTAutoStringN<CHAR, N>& aS) { + const auto length = aS.Length(); + return ProfileBufferEntryWriter::ULEB128Size(length) + + static_cast<Length>(length * sizeof(CHAR)); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const nsTAutoStringN<CHAR, N>& aS) { + const auto length = aS.Length(); + aEW.WriteULEB128(length); + // Copy the bytes from the string's buffer. + aEW.WriteBytes(aS.BeginReading(), length * sizeof(CHAR)); + } +}; + +template <typename CHAR, size_t N> +struct ProfileBufferEntryReader::Deserializer<nsTAutoStringN<CHAR, N>> { + static void ReadInto(ProfileBufferEntryReader& aER, + nsTAutoStringN<CHAR, N>& aS) { + aS = Read(aER); + } + + static nsTAutoStringN<CHAR, N> Read(ProfileBufferEntryReader& aER) { + const auto length = aER.ReadULEB128<Length>(); + nsTAutoStringN<CHAR, N> s; + // BulkWrite is the most efficient way to copy bytes into the target string. + auto writerOrErr = s.BulkWrite(length, 0, true); + MOZ_RELEASE_ASSERT(!writerOrErr.isErr()); + + auto writer = writerOrErr.unwrap(); + aER.ReadBytes(writer.Elements(), length * sizeof(CHAR)); + writer.Finish(length, true); + return s; + } +}; + +// ---------------------------------------------------------------------------- +// JS::UniqueChars + +// JS::UniqueChars contents are serialized as the number of bytes (encoded as +// ULEB128) and all the characters in the string. The terminal '\0' is omitted. +// Note: A nullptr pointer will be serialized like an empty string, so when +// deserializing it will result in an allocated buffer only containing a +// single null terminator. +// +// Usage: `JS::UniqueChars s = ...; aEW.WriteObject(s);` +template <> +struct ProfileBufferEntryWriter::Serializer<JS::UniqueChars> { + static Length Bytes(const JS::UniqueChars& aS) { + if (!aS) { + return ProfileBufferEntryWriter::ULEB128Size<Length>(0); + } + const auto len = static_cast<Length>(strlen(aS.get())); + return ProfileBufferEntryWriter::ULEB128Size(len) + len; + } + + static void Write(ProfileBufferEntryWriter& aEW, const JS::UniqueChars& aS) { + if (!aS) { + aEW.WriteULEB128<Length>(0); + return; + } + const auto len = static_cast<Length>(strlen(aS.get())); + aEW.WriteULEB128(len); + aEW.WriteBytes(aS.get(), len); + } +}; + +template <> +struct ProfileBufferEntryReader::Deserializer<JS::UniqueChars> { + static void ReadInto(ProfileBufferEntryReader& aER, JS::UniqueChars& aS) { + aS = Read(aER); + } + + static JS::UniqueChars Read(ProfileBufferEntryReader& aER) { + const auto len = aER.ReadULEB128<Length>(); + // Use the same allocation policy as JS_smprintf. + char* buffer = + static_cast<char*>(js::SystemAllocPolicy{}.pod_malloc<char>(len + 1)); + aER.ReadBytes(buffer, len); + buffer[len] = '\0'; + return JS::UniqueChars(buffer); + } +}; + +} // namespace mozilla + +#endif // ProfileBufferEntrySerializationGeckoExtensions_h diff --git a/tools/profiler/public/ProfileJSONWriter.h b/tools/profiler/public/ProfileJSONWriter.h new file mode 100644 index 0000000000..8d23d7a890 --- /dev/null +++ b/tools/profiler/public/ProfileJSONWriter.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef PROFILEJSONWRITER_H +#define PROFILEJSONWRITER_H + +#include "mozilla/BaseProfileJSONWriter.h" + +using ChunkedJSONWriteFunc = mozilla::baseprofiler::ChunkedJSONWriteFunc; +using JSONSchemaWriter = mozilla::baseprofiler::JSONSchemaWriter; +using OStreamJSONWriteFunc = mozilla::baseprofiler::OStreamJSONWriteFunc; +using SpliceableChunkedJSONWriter = + mozilla::baseprofiler::SpliceableChunkedJSONWriter; +using SpliceableJSONWriter = mozilla::baseprofiler::SpliceableJSONWriter; +using UniqueJSONStrings = mozilla::baseprofiler::UniqueJSONStrings; + +#endif // PROFILEJSONWRITER_H diff --git a/tools/profiler/public/ProfilerChild.h b/tools/profiler/public/ProfilerChild.h new file mode 100644 index 0000000000..4e4875f909 --- /dev/null +++ b/tools/profiler/public/ProfilerChild.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef ProfilerChild_h +#define ProfilerChild_h + +#include "mozilla/BaseProfilerDetail.h" +#include "mozilla/DataMutex.h" +#include "mozilla/PProfilerChild.h" +#include "mozilla/ProfileBufferControlledChunkManager.h" +#include "mozilla/RefPtr.h" + +class nsIThread; + +namespace mozilla { + +// The ProfilerChild actor is created in all processes except for the main +// process. The corresponding ProfilerParent actor is created in the main +// process, and it will notify us about profiler state changes and request +// profiles from us. +class ProfilerChild final : public PProfilerChild, + public mozilla::ipc::IShmemAllocator { + NS_INLINE_DECL_REFCOUNTING(ProfilerChild) + + ProfilerChild(); + + // Collects and returns a profile. + // This method can be used to grab a profile just before PProfiler is torn + // down. The collected profile should then be sent through a different + // message channel that is guaranteed to stay open long enough. + nsCString GrabShutdownProfile(); + + void Destroy(); + + // This should be called regularly from outside of the profiler lock. + static void ProcessPendingUpdate(); + + static bool IsLockedOnCurrentThread(); + + private: + virtual ~ProfilerChild(); + + mozilla::ipc::IPCResult RecvStart(const ProfilerInitParams& params) override; + mozilla::ipc::IPCResult RecvEnsureStarted( + const ProfilerInitParams& params) override; + mozilla::ipc::IPCResult RecvStop() override; + mozilla::ipc::IPCResult RecvPause() override; + mozilla::ipc::IPCResult RecvResume() override; + mozilla::ipc::IPCResult RecvPauseSampling() override; + mozilla::ipc::IPCResult RecvResumeSampling() override; + mozilla::ipc::IPCResult RecvAwaitNextChunkManagerUpdate( + AwaitNextChunkManagerUpdateResolver&& aResolve) override; + mozilla::ipc::IPCResult RecvDestroyReleasedChunksAtOrBefore( + const TimeStamp& aTimeStamp) override; + mozilla::ipc::IPCResult RecvGatherProfile( + GatherProfileResolver&& aResolve) override; + mozilla::ipc::IPCResult RecvClearAllPages() override; + + void ActorDestroy(ActorDestroyReason aActorDestroyReason) override; + + FORWARD_SHMEM_ALLOCATOR_TO(PProfilerChild) + + void SetupChunkManager(); + void ResetChunkManager(); + void ResolveChunkUpdate( + PProfilerChild::AwaitNextChunkManagerUpdateResolver& aResolve); + void ProcessChunkManagerUpdate( + ProfileBufferControlledChunkManager::Update&& aUpdate); + + nsCOMPtr<nsIThread> mThread; + bool mDestroyed; + + ProfileBufferControlledChunkManager* mChunkManager = nullptr; + AwaitNextChunkManagerUpdateResolver mAwaitNextChunkManagerUpdateResolver; + ProfileBufferControlledChunkManager::Update mChunkManagerUpdate; + + struct ProfilerChildAndUpdate { + RefPtr<ProfilerChild> mProfilerChild; + ProfileBufferControlledChunkManager::Update mUpdate; + }; + static DataMutexBase<ProfilerChildAndUpdate, + baseprofiler::detail::BaseProfilerMutex> + sPendingChunkManagerUpdate; +}; + +} // namespace mozilla + +#endif // ProfilerChild_h diff --git a/tools/profiler/public/ProfilerCodeAddressService.h b/tools/profiler/public/ProfilerCodeAddressService.h new file mode 100644 index 0000000000..9d75c363b3 --- /dev/null +++ b/tools/profiler/public/ProfilerCodeAddressService.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef ProfilerCodeAddressService_h +#define ProfilerCodeAddressService_h + +#include "CodeAddressService.h" +#include "nsTArray.h" + +namespace mozilla { + +// This SymbolTable struct, and the CompactSymbolTable struct in the +// profiler rust module, have the exact same memory layout. +// nsTArray and ThinVec are FFI-compatible, because the thin-vec crate is +// being compiled with the "gecko-ffi" feature enabled. +struct SymbolTable { + SymbolTable() = default; + SymbolTable(SymbolTable&& aOther) = default; + + nsTArray<uint32_t> mAddrs; + nsTArray<uint32_t> mIndex; + nsTArray<uint8_t> mBuffer; +}; + +} // namespace mozilla + +/** + * Cache and look up function symbol names. + * + * We don't template this on AllocPolicy since we need to use nsTArray in + * SymbolTable above, which doesn't work with AllocPolicy. (We can't switch + * to Vector, as we would lose FFI compatibility with ThinVec.) + */ +class ProfilerCodeAddressService : public mozilla::CodeAddressService<> { + public: + // Like GetLocation, but only returns the symbol name. + bool GetFunction(const void* aPc, nsACString& aResult); + + private: +#if defined(XP_LINUX) || defined(XP_FREEBSD) + // Map of library names (owned by mLibraryStrings) to SymbolTables filled + // in by profiler_get_symbol_table. + mozilla::HashMap<const char*, mozilla::SymbolTable, + mozilla::DefaultHasher<const char*>, AllocPolicy> + mSymbolTables; +#endif +}; + +#endif // ProfilerCodeAddressService_h diff --git a/tools/profiler/public/ProfilerCounts.h b/tools/profiler/public/ProfilerCounts.h new file mode 100644 index 0000000000..a86cf19709 --- /dev/null +++ b/tools/profiler/public/ProfilerCounts.h @@ -0,0 +1,274 @@ +/* -*- 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/. */ + +#ifndef ProfilerCounts_h +#define ProfilerCounts_h + +#ifndef MOZ_GECKO_PROFILER + +# define PROFILER_DEFINE_COUNT_TOTAL(label, category, description) +# define PROFILER_DEFINE_COUNT(label, category, description) +# define PROFILER_DEFINE_STATIC_COUNT_TOTAL(label, category, description) +# define AUTO_PROFILER_TOTAL(label, count) +# define AUTO_PROFILER_COUNT(label) +# define AUTO_PROFILER_STATIC_COUNT(label, count) + +#else + +# include "mozilla/Atomics.h" + +class BaseProfilerCount; +void profiler_add_sampled_counter(BaseProfilerCount* aCounter); +void profiler_remove_sampled_counter(BaseProfilerCount* aCounter); + +typedef mozilla::Atomic<int64_t, mozilla::MemoryOrdering::Relaxed> + ProfilerAtomicSigned; +typedef mozilla::Atomic<uint64_t, mozilla::MemoryOrdering::Relaxed> + ProfilerAtomicUnsigned; + +// Counter support +// There are two types of counters: +// 1) a simple counter which can be added to or subtracted from. This could +// track the number of objects of a type, the number of calls to something +// (reflow, JIT, etc). +// 2) a combined counter which has the above, plus a number-of-calls counter +// that is incremented by 1 for each call to modify the count. This provides +// an optional source for a 'heatmap' of access. This can be used (for +// example) to track the amount of memory allocated, and provide a heatmap of +// memory operations (allocs/frees). +// +// Counters are sampled by the profiler once per sample-period. At this time, +// all counters are global to the process. In the future, there might be more +// versions with per-thread or other discriminators. +// +// Typical usage: +// There are two ways to use counters: With heap-created counter objects, +// or using macros. Note: the macros use statics, and will be slightly +// faster/smaller, and you need to care about creating them before using +// them. They're similar to the use-pattern for the other AUTO_PROFILER* +// macros, but they do need the PROFILER_DEFINE* to be use to instantiate +// the statics. +// +// PROFILER_DEFINE_COUNT(mything, "JIT", "Some JIT byte count") +// ... +// void foo() { ... AUTO_PROFILER_COUNT(mything, number_of_bytes_used); ... } +// +// or (to also get a heatmap) +// +// PROFILER_DEFINE_COUNT_TOTAL(mything, "JIT", "Some JIT byte count") +// ... +// void foo() { +// ... +// AUTO_PROFILER_COUNT_TOTAL(mything, number_of_bytes_generated); +// ... +// } +// +// To use without statics/macros: +// +// UniquePtr<ProfilerCounter> myCounter; +// ... +// myCounter = +// MakeUnique<ProfilerCounter>("mything", "JIT", "Some JIT byte count")); +// ... +// void foo() { ... myCounter->Add(number_of_bytes_generated0; ... } + +class BaseProfilerCount { + public: + BaseProfilerCount(const char* aLabel, ProfilerAtomicSigned* aCounter, + ProfilerAtomicUnsigned* aNumber, const char* aCategory, + const char* aDescription) + : mLabel(aLabel), + mCategory(aCategory), + mDescription(aDescription), + mCounter(aCounter), + mNumber(aNumber) { +# define COUNTER_CANARY 0xDEADBEEF +# ifdef DEBUG + mCanary = COUNTER_CANARY; + mPrevNumber = 0; +# endif + // Can't call profiler_* here since this may be non-xul-library + } + + virtual ~BaseProfilerCount() { +# ifdef DEBUG + mCanary = 0; +# endif + } + + virtual void Sample(int64_t& aCounter, uint64_t& aNumber) { + MOZ_ASSERT(mCanary == COUNTER_CANARY); + + aCounter = *mCounter; + aNumber = mNumber ? *mNumber : 0; +# ifdef DEBUG + MOZ_ASSERT(aNumber >= mPrevNumber); + mPrevNumber = aNumber; +# endif + } + + // We don't define ++ and Add() here, since the static defines directly + // increment the atomic counters, and the subclasses implement ++ and + // Add() directly. + + // These typically are static strings (for example if you use the macros + // below) + const char* mLabel; + const char* mCategory; + const char* mDescription; + // We're ok with these being un-ordered in race conditions. These are + // pointers because we want to be able to use statics and increment them + // directly. Otherwise we could just have them inline, and not need the + // constructor args. + // These can be static globals (using the macros below), though they + // don't have to be - their lifetime must be longer than the use of them + // by the profiler (see profiler_add/remove_sampled_counter()). If you're + // using a lot of these, they probably should be allocated at runtime (see + // class ProfilerCountOnly below). + ProfilerAtomicSigned* mCounter; + ProfilerAtomicUnsigned* mNumber; // may be null + +# ifdef DEBUG + uint32_t mCanary; + uint64_t mPrevNumber; // value of number from the last Sample() +# endif +}; + +// Designed to be allocated dynamically, and simply incremented with obj++ +// or obj->Add(n) +class ProfilerCounter final : public BaseProfilerCount { + public: + ProfilerCounter(const char* aLabel, const char* aCategory, + const char* aDescription) + : BaseProfilerCount(aLabel, &mCounter, nullptr, aCategory, aDescription) { + // Assume we're in libxul + profiler_add_sampled_counter(this); + } + + virtual ~ProfilerCounter() { profiler_remove_sampled_counter(this); } + + BaseProfilerCount& operator++() { + Add(1); + return *this; + } + + void Add(int64_t aNumber) { mCounter += aNumber; } + + ProfilerAtomicSigned mCounter; +}; + +// Also keeps a heatmap (number of calls to ++/Add()) +class ProfilerCounterTotal final : public BaseProfilerCount { + public: + ProfilerCounterTotal(const char* aLabel, const char* aCategory, + const char* aDescription) + : BaseProfilerCount(aLabel, &mCounter, &mNumber, aCategory, + aDescription) { + // Assume we're in libxul + profiler_add_sampled_counter(this); + } + + virtual ~ProfilerCounterTotal() { profiler_remove_sampled_counter(this); } + + BaseProfilerCount& operator++() { + Add(1); + return *this; + } + + void Add(int64_t aNumber) { + mCounter += aNumber; + mNumber++; + } + + ProfilerAtomicSigned mCounter; + ProfilerAtomicUnsigned mNumber; +}; + +// Defines a counter that is sampled on each profiler tick, with a running +// count (signed), and number-of-instances. Note that because these are two +// independent Atomics, there is a possiblity that count will not include +// the last call, but number of uses will. I think this is not worth +// worrying about +# define PROFILER_DEFINE_COUNT_TOTAL(label, category, description) \ + ProfilerAtomicSigned profiler_count_##label(0); \ + ProfilerAtomicUnsigned profiler_number_##label(0); \ + const char profiler_category_##label[] = category; \ + const char profiler_description_##label[] = description; \ + mozilla::UniquePtr<BaseProfilerCount> AutoCount_##label; + +// This counts, but doesn't keep track of the number of calls to +// AUTO_PROFILER_COUNT() +# define PROFILER_DEFINE_COUNT(label, category, description) \ + ProfilerAtomicSigned profiler_count_##label(0); \ + const char profiler_category_##label[] = category; \ + const char profiler_description_##label[] = description; \ + mozilla::UniquePtr<BaseProfilerCount> AutoCount_##label; + +// This will create a static initializer if used, but avoids a possible +// allocation. +# define PROFILER_DEFINE_STATIC_COUNT_TOTAL(label, category, description) \ + ProfilerAtomicSigned profiler_count_##label(0); \ + ProfilerAtomicUnsigned profiler_number_##label(0); \ + BaseProfilerCount AutoCount_##label(#label, &profiler_count_##label, \ + &profiler_number_##label, category, \ + description); + +// If we didn't care about static initializers, we could avoid the need for +// a ptr to the BaseProfilerCount object. + +// XXX It would be better to do this without the if() and without the +// theoretical race to set the UniquePtr (i.e. possible leak). +# define AUTO_PROFILER_COUNT_TOTAL(label, count) \ + do { \ + profiler_number_##label++; /* do this first*/ \ + profiler_count_##label += count; \ + if (!AutoCount_##label) { \ + /* Ignore that we could call this twice in theory, and that we leak \ + * them \ + */ \ + AutoCount_##label.reset(new BaseProfilerCount( \ + #label, &profiler_count_##label, &profiler_number_##label, \ + profiler_category_##label, profiler_description_##label)); \ + profiler_add_sampled_counter(AutoCount_##label.get()); \ + } \ + } while (0) + +# define AUTO_PROFILER_COUNT(label, count) \ + do { \ + profiler_count_##label += count; /* do this first*/ \ + if (!AutoCount_##label) { \ + /* Ignore that we could call this twice in theory, and that we leak \ + * them \ + */ \ + AutoCount_##label.reset(new BaseProfilerCount( \ + #label, nullptr, &profiler_number_##label, \ + profiler_category_##label, profiler_description_##label)); \ + profiler_add_sampled_counter(AutoCount_##label.get()); \ + } \ + } while (0) + +# define AUTO_PROFILER_STATIC_COUNT(label, count) \ + do { \ + profiler_number_##label++; /* do this first*/ \ + profiler_count_##label += count; \ + } while (0) + +// if we need to force the allocation +# define AUTO_PROFILER_FORCE_ALLOCATION(label) \ + do { \ + if (!AutoCount_##label) { \ + /* Ignore that we could call this twice in theory, and that we leak \ + * them \ + */ \ + AutoCount_##label.reset(new BaseProfilerCount( \ + #label, &profiler_count_##label, &profiler_number_##label, \ + profiler_category_##label, profiler_description_##label)); \ + } \ + } while (0) + +#endif // !MOZ_GECKO_PROFILER + +#endif // ProfilerCounts_h diff --git a/tools/profiler/public/ProfilerMarkerTypes.h b/tools/profiler/public/ProfilerMarkerTypes.h new file mode 100644 index 0000000000..347f58839c --- /dev/null +++ b/tools/profiler/public/ProfilerMarkerTypes.h @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +#ifndef ProfilerMarkerTypes_h +#define ProfilerMarkerTypes_h + +// This header contains common marker type definitions that rely on xpcom. +// +// It #include's "mozilla/BaseProfilerMarkerTypess.h" and "ProfilerMarkers.h", +// see these files for more marker types, how to define other marker types, and +// how to add markers to the profiler buffers. + +// !!! /!\ WORK IN PROGRESS /!\ !!! +// This file contains draft marker definitions, but most are not used yet. +// Further work is needed to complete these definitions, and use them to convert +// existing PROFILER_ADD_MARKER calls. See meta bug 1661394. + +#include "mozilla/BaseProfilerMarkerTypes.h" +#include "mozilla/ProfilerMarkers.h" + +#ifdef MOZ_GECKO_PROFILER + +# include "js/ProfilingFrameIterator.h" +# include "js/Utility.h" +# include "mozilla/Preferences.h" +# include "mozilla/ServoTraversalStatistics.h" + +namespace geckoprofiler::markers { + +// Import some common markers from mozilla::baseprofiler::markers. +using MediaSampleMarker = mozilla::baseprofiler::markers::MediaSampleMarker; +using ContentBuildMarker = mozilla::baseprofiler::markers::ContentBuildMarker; + +} // namespace geckoprofiler::markers + +#endif // MOZ_GECKO_PROFILER + +#endif // ProfilerMarkerTypes_h diff --git a/tools/profiler/public/ProfilerMarkers.h b/tools/profiler/public/ProfilerMarkers.h new file mode 100644 index 0000000000..de2e6f881d --- /dev/null +++ b/tools/profiler/public/ProfilerMarkers.h @@ -0,0 +1,294 @@ +/* -*- 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/. */ + +// Markers are useful to delimit something important happening such as the first +// paint. Unlike labels, which are only recorded in the profile buffer if a +// sample is collected while the label is on the label stack, markers will +// always be recorded in the profile buffer. +// +// This header contains definitions necessary to add markers to the Gecko +// Profiler buffer. +// +// It #include's "mozilla/BaseProfilerMarkers.h", see that header for base +// definitions necessary to create marker types. +// +// If common marker types are needed, #include "ProfilerMarkerTypes.h" instead. +// +// But if you want to create your own marker type locally, you can #include this +// header only; look at ProfilerMarkerTypes.h for examples of how to define +// types. +// +// To then record markers: +// - Use `baseprofiler::AddMarker(...)` from mozglue or other libraries that are +// outside of xul, especially if they may happen outside of xpcom's lifetime +// (typically startup, shutdown, or tests). +// - Otherwise #include "ProfilerMarkers.h" instead, and use +// `profiler_add_marker(...)`. +// See these functions for more details. + +#ifndef ProfilerMarkers_h +#define ProfilerMarkers_h + +#include "mozilla/BaseProfilerMarkers.h" +#include "mozilla/ProfilerMarkersDetail.h" + +#ifndef MOZ_GECKO_PROFILER + +# define PROFILER_MARKER_UNTYPED(markerName, categoryName, ...) +# define PROFILER_MARKER(markerName, categoryName, options, MarkerType, ...) +# define PROFILER_MARKER_TEXT(markerName, categoryName, options, text) +# define AUTO_PROFILER_MARKER_TEXT(markerName, categoryName, options, text) +# define AUTO_PROFILER_TRACING_MARKER(categoryString, markerName, categoryPair) +# define AUTO_PROFILER_TRACING_MARKER_DOCSHELL(categoryString, markerName, \ + categoryPair, docShell) + +#else // ndef MOZ_GECKO_PROFILER + +namespace mozilla { +class ProfileChunkedBuffer; +} + +bool profiler_can_accept_markers(); +bool profiler_capture_backtrace_into( + mozilla::ProfileChunkedBuffer& aChunkedBuffer); + +// Bring category names from Base Profiler into the geckoprofiler::category +// namespace, for consistency with other Gecko Profiler identifiers. +namespace geckoprofiler::category { +using namespace ::mozilla::baseprofiler::category; +} + +// Add a marker to a given buffer. `AddMarker()` and related macros should be +// used in most cases, see below for more information about them and the +// paramters; This function may be useful when markers need to be recorded in a +// local buffer outside of the main profiler buffer. +template <typename MarkerType, typename... PayloadArguments> +mozilla::ProfileBufferBlockIndex AddMarkerToBuffer( + mozilla::ProfileChunkedBuffer& aBuffer, + const mozilla::ProfilerString8View& aName, + const mozilla::MarkerCategory& aCategory, mozilla::MarkerOptions&& aOptions, + MarkerType aMarkerType, const PayloadArguments&... aPayloadArguments) { + mozilla::Unused << aMarkerType; // Only the empty object type is useful. + return mozilla::base_profiler_markers_detail::AddMarkerToBuffer<MarkerType>( + aBuffer, aName, aCategory, std::move(aOptions), + ::profiler_capture_backtrace_into, aPayloadArguments...); +} + +// Add a marker (without payload) to a given buffer. +inline mozilla::ProfileBufferBlockIndex AddMarkerToBuffer( + mozilla::ProfileChunkedBuffer& aBuffer, + const mozilla::ProfilerString8View& aName, + const mozilla::MarkerCategory& aCategory, + mozilla::MarkerOptions&& aOptions = {}) { + return AddMarkerToBuffer(aBuffer, aName, aCategory, std::move(aOptions), + mozilla::baseprofiler::markers::NoPayload{}); +} + +// Add a marker to the Gecko Profiler buffer. +// - aName: Main name of this marker. +// - aCategory: Category for this marker. +// - aOptions: Optional settings (such as timing, inner window id, +// backtrace...), see `MarkerOptions` for details. +// - aMarkerType: Empty object that specifies the type of marker. +// - aPayloadArguments: Arguments expected by this marker type's +// ` StreamJSONMarkerData` function. +template <typename MarkerType, typename... PayloadArguments> +mozilla::ProfileBufferBlockIndex profiler_add_marker( + const mozilla::ProfilerString8View& aName, + const mozilla::MarkerCategory& aCategory, mozilla::MarkerOptions&& aOptions, + MarkerType aMarkerType, const PayloadArguments&... aPayloadArguments) { + if (!profiler_can_accept_markers()) { + return {}; + } + return ::AddMarkerToBuffer(profiler_markers_detail::CachedCoreBuffer(), aName, + aCategory, std::move(aOptions), aMarkerType, + aPayloadArguments...); +} + +// Add a marker (without payload) to the Gecko Profiler buffer. +inline mozilla::ProfileBufferBlockIndex profiler_add_marker( + const mozilla::ProfilerString8View& aName, + const mozilla::MarkerCategory& aCategory, + mozilla::MarkerOptions&& aOptions = {}) { + return profiler_add_marker(aName, aCategory, std::move(aOptions), + mozilla::baseprofiler::markers::NoPayload{}); +} + +// Same as `profiler_add_marker()` (without payload). This macro is safe to use +// even if MOZ_GECKO_PROFILER is not #defined. +# define PROFILER_MARKER_UNTYPED(markerName, categoryName, ...) \ + do { \ + AUTO_PROFILER_STATS(PROFILER_MARKER_UNTYPED); \ + ::profiler_add_marker( \ + markerName, ::geckoprofiler::category::categoryName, ##__VA_ARGS__); \ + } while (false) + +// Same as `profiler_add_marker()` (with payload). This macro is safe to use +// even if MOZ_GECKO_PROFILER is not #defined. +# define PROFILER_MARKER(markerName, categoryName, options, MarkerType, ...) \ + do { \ + AUTO_PROFILER_STATS(PROFILER_MARKER_with_##MarkerType); \ + ::profiler_add_marker( \ + markerName, ::geckoprofiler::category::categoryName, options, \ + ::geckoprofiler::markers::MarkerType{}, ##__VA_ARGS__); \ + } while (false) + +namespace geckoprofiler::markers { +// Most common marker types. Others are in ProfilerMarkerTypes.h. +using TextMarker = ::mozilla::baseprofiler::markers::TextMarker; +using Tracing = mozilla::baseprofiler::markers::Tracing; +} // namespace geckoprofiler::markers + +// Add a text marker. This macro is safe to use even if MOZ_GECKO_PROFILER is +// not #defined. +# define PROFILER_MARKER_TEXT(markerName, categoryName, options, text) \ + do { \ + AUTO_PROFILER_STATS(PROFILER_MARKER_TEXT); \ + ::profiler_add_marker(markerName, \ + ::geckoprofiler::category::categoryName, options, \ + ::geckoprofiler::markers::TextMarker{}, text); \ + } while (false) + +// RAII object that adds a PROFILER_MARKER_TEXT when destroyed; the marker's +// timing will be the interval from construction (unless an instant or start +// time is already specified in the provided options) until destruction. +class MOZ_RAII AutoProfilerTextMarker { + public: + AutoProfilerTextMarker(const char* aMarkerName, + const mozilla::MarkerCategory& aCategory, + mozilla::MarkerOptions&& aOptions, + const nsACString& aText) + : mMarkerName(aMarkerName), + mCategory(aCategory), + mOptions(std::move(aOptions)), + mText(aText) { + MOZ_ASSERT(mOptions.Timing().EndTime().IsNull(), + "AutoProfilerTextMarker options shouldn't have an end time"); + if (mOptions.Timing().StartTime().IsNull()) { + mOptions.Set(mozilla::MarkerTiming::InstantNow()); + } + } + + ~AutoProfilerTextMarker() { + mOptions.TimingRef().SetIntervalEnd(); + AUTO_PROFILER_STATS(AUTO_PROFILER_MARKER_TEXT); + profiler_add_marker( + mozilla::ProfilerString8View::WrapNullTerminatedString(mMarkerName), + mCategory, std::move(mOptions), geckoprofiler::markers::TextMarker{}, + mText); + } + + protected: + const char* mMarkerName; + mozilla::MarkerCategory mCategory; + mozilla::MarkerOptions mOptions; + nsCString mText; +}; + +// Creates an AutoProfilerTextMarker RAII object. This macro is safe to use +// even if MOZ_GECKO_PROFILER is not #defined. +# define AUTO_PROFILER_MARKER_TEXT(markerName, categoryName, options, text) \ + AutoProfilerTextMarker PROFILER_RAII( \ + markerName, ::mozilla::baseprofiler::category::categoryName, options, \ + text) + +class MOZ_RAII AutoProfilerTracing { + public: + AutoProfilerTracing(const char* aCategoryString, const char* aMarkerName, + mozilla::MarkerCategory aCategoryPair, + const mozilla::Maybe<uint64_t>& aInnerWindowID) + : mCategoryString(aCategoryString), + mMarkerName(aMarkerName), + mCategoryPair(aCategoryPair), + mInnerWindowID(aInnerWindowID) { + profiler_add_marker( + mozilla::ProfilerString8View::WrapNullTerminatedString(mMarkerName), + mCategoryPair, + {mozilla::MarkerTiming::IntervalStart(), + mozilla::MarkerInnerWindowId(mInnerWindowID)}, + geckoprofiler::markers::Tracing{}, + mozilla::ProfilerString8View::WrapNullTerminatedString( + mCategoryString)); + } + + AutoProfilerTracing( + const char* aCategoryString, const char* aMarkerName, + mozilla::MarkerCategory aCategoryPair, + mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aBacktrace, + const mozilla::Maybe<uint64_t>& aInnerWindowID) + : mCategoryString(aCategoryString), + mMarkerName(aMarkerName), + mCategoryPair(aCategoryPair), + mInnerWindowID(aInnerWindowID) { + profiler_add_marker( + mozilla::ProfilerString8View::WrapNullTerminatedString(mMarkerName), + mCategoryPair, + {mozilla::MarkerTiming::IntervalStart(), + mozilla::MarkerInnerWindowId(mInnerWindowID), + mozilla::MarkerStack::TakeBacktrace(std::move(aBacktrace))}, + geckoprofiler::markers::Tracing{}, + mozilla::ProfilerString8View::WrapNullTerminatedString( + mCategoryString)); + } + + ~AutoProfilerTracing() { + profiler_add_marker( + mozilla::ProfilerString8View::WrapNullTerminatedString(mMarkerName), + mCategoryPair, + {mozilla::MarkerTiming::IntervalEnd(), + mozilla::MarkerInnerWindowId(mInnerWindowID)}, + geckoprofiler::markers::Tracing{}, + mozilla::ProfilerString8View::WrapNullTerminatedString( + mCategoryString)); + } + + protected: + const char* mCategoryString; + const char* mMarkerName; + const mozilla::MarkerCategory mCategoryPair; + const mozilla::Maybe<uint64_t> mInnerWindowID; +}; + +// Adds a START/END pair of tracing markers. +# define AUTO_PROFILER_TRACING_MARKER(categoryString, markerName, \ + categoryPair) \ + AutoProfilerTracing PROFILER_RAII(categoryString, markerName, \ + geckoprofiler::category::categoryPair, \ + mozilla::Nothing()) +# define AUTO_PROFILER_TRACING_MARKER_DOCSHELL(categoryString, markerName, \ + categoryPair, docShell) \ + AutoProfilerTracing PROFILER_RAII( \ + categoryString, markerName, geckoprofiler::category::categoryPair, \ + profiler_get_inner_window_id_from_docshell(docShell)) + +extern template mozilla::ProfileBufferBlockIndex AddMarkerToBuffer( + mozilla::ProfileChunkedBuffer&, const mozilla::ProfilerString8View&, + const mozilla::MarkerCategory&, mozilla::MarkerOptions&&, + mozilla::baseprofiler::markers::NoPayload); + +extern template mozilla::ProfileBufferBlockIndex AddMarkerToBuffer( + mozilla::ProfileChunkedBuffer&, const mozilla::ProfilerString8View&, + const mozilla::MarkerCategory&, mozilla::MarkerOptions&&, + mozilla::baseprofiler::markers::TextMarker, const std::string&); + +extern template mozilla::ProfileBufferBlockIndex profiler_add_marker( + const mozilla::ProfilerString8View&, const mozilla::MarkerCategory&, + mozilla::MarkerOptions&&, mozilla::baseprofiler::markers::TextMarker, + const std::string&); + +extern template mozilla::ProfileBufferBlockIndex profiler_add_marker( + const mozilla::ProfilerString8View&, const mozilla::MarkerCategory&, + mozilla::MarkerOptions&&, mozilla::baseprofiler::markers::TextMarker, + const nsCString&); + +extern template mozilla::ProfileBufferBlockIndex profiler_add_marker( + const mozilla::ProfilerString8View&, const mozilla::MarkerCategory&, + mozilla::MarkerOptions&&, mozilla::baseprofiler::markers::Tracing, + const mozilla::ProfilerString8View&); + +#endif // nfed MOZ_GECKO_PROFILER else + +#endif // ProfilerMarkers_h diff --git a/tools/profiler/public/ProfilerMarkersDetail.h b/tools/profiler/public/ProfilerMarkersDetail.h new file mode 100644 index 0000000000..22396b5d2f --- /dev/null +++ b/tools/profiler/public/ProfilerMarkersDetail.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +#ifndef ProfilerMarkersDetail_h +#define ProfilerMarkersDetail_h + +#ifndef ProfilerMarkers_h +# error "This header should only be #included by ProfilerMarkers.h" +#endif + +#include "mozilla/ProfilerMarkersPrerequisites.h" + +#ifdef MOZ_GECKO_PROFILER + +// ~~ HERE BE DRAGONS ~~ +// +// Everything below is internal implementation detail, you shouldn't need to +// look at it unless working on the profiler code. + +// Header that specializes the (de)serializers for xpcom types. +# include "mozilla/ProfileBufferEntrySerializationGeckoExtensions.h" + +// Implemented in platform.cpp +mozilla::ProfileChunkedBuffer& profiler_get_core_buffer(); + +namespace profiler_markers_detail { + +// Get the core buffer from the profiler, and cache it in a +// non-templated-function static reference. +inline mozilla::ProfileChunkedBuffer& CachedCoreBuffer() { + static mozilla::ProfileChunkedBuffer& coreBuffer = profiler_get_core_buffer(); + return coreBuffer; +} + +} // namespace profiler_markers_detail + +#endif // MOZ_GECKO_PROFILER + +#endif // ProfilerMarkersDetail_h diff --git a/tools/profiler/public/ProfilerMarkersPrerequisites.h b/tools/profiler/public/ProfilerMarkersPrerequisites.h new file mode 100644 index 0000000000..93868b0524 --- /dev/null +++ b/tools/profiler/public/ProfilerMarkersPrerequisites.h @@ -0,0 +1,30 @@ +/* -*- 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/. */ + +// This header contains basic definitions required to create marker types, and +// to add markers to the profiler buffers. +// +// In most cases, #include "mozilla/ProfilerMarkers.h" instead, or +// #include "mozilla/ProfilerMarkerTypes.h" for common marker types. + +#ifndef ProfilerMarkersPrerequisites_h +#define ProfilerMarkersPrerequisites_h + +#include "mozilla/BaseProfilerMarkersPrerequisites.h" + +#ifdef MOZ_GECKO_PROFILER + +namespace geckoprofiler::markers { + +// Default marker payload types, with no extra information, not even a marker +// type and payload. This is intended for label-only markers. +using NoPayload = ::mozilla::baseprofiler::markers::NoPayload; + +} // namespace geckoprofiler::markers + +#endif // MOZ_GECKO_PROFILER + +#endif // ProfilerMarkersPrerequisites_h diff --git a/tools/profiler/public/ProfilerParent.h b/tools/profiler/public/ProfilerParent.h new file mode 100644 index 0000000000..fe9457e678 --- /dev/null +++ b/tools/profiler/public/ProfilerParent.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef ProfilerParent_h +#define ProfilerParent_h + +#include "mozilla/PProfilerParent.h" +#include "mozilla/RefPtr.h" + +class nsIProfilerStartParams; + +namespace mozilla { + +class ProfileBufferGlobalController; +class ProfilerParentTracker; + +// This is the main process side of the PProfiler protocol. +// ProfilerParent instances only exist on the main thread of the main process. +// The other side (ProfilerChild) lives on a background thread in the other +// process. +// The creation of PProfiler actors is initiated from the main process, after +// the other process has been launched. +// ProfilerParent instances are destroyed once the message channel closes, +// which can be triggered by either process, depending on which one shuts down +// first. +// All ProfilerParent instances are registered with a manager class called +// ProfilerParentTracker, which has the list of living ProfilerParent instances +// and handles shutdown. +class ProfilerParent final : public PProfilerParent { + public: + NS_INLINE_DECL_REFCOUNTING(ProfilerParent) + + static mozilla::ipc::Endpoint<PProfilerChild> CreateForProcess( + base::ProcessId aOtherPid); + + typedef MozPromise<Shmem, ResponseRejectReason, true> + SingleProcessProfilePromise; + + // The following static methods can be called on any thread, but they are + // no-ops on anything other than the main thread. + // If called on the main thread, the call will be broadcast to all + // registered processes (all processes for which we have a ProfilerParent + // object). + // At the moment, the main process always calls these methods on the main + // thread, and that's the only process in which we need to forward these + // calls to other processes. The other processes will call these methods on + // the ProfilerChild background thread, but those processes don't need to + // forward these calls any further. + + // Returns the number of profiles to expect. The gathered profiles will be + // provided asynchronously with a call to + // ProfileGatherer::ReceiveGatheredProfile. + static nsTArray<RefPtr<SingleProcessProfilePromise>> GatherProfiles(); + + static void ProfilerStarted(nsIProfilerStartParams* aParams); + static void ProfilerWillStopIfStarted(); + static void ProfilerStopped(); + static void ProfilerPaused(); + static void ProfilerResumed(); + static void ProfilerPausedSampling(); + static void ProfilerResumedSampling(); + static void ClearAllPages(); + + // Create a "Final" update that the Child can return to its Parent. + static ProfileBufferChunkManagerUpdate MakeFinalUpdate(); + + // True if the ProfilerParent holds a lock on this thread. + static bool IsLockedOnCurrentThread(); + + private: + friend class ProfileBufferGlobalController; + friend class ProfilerParentTracker; + + explicit ProfilerParent(base::ProcessId aChildPid); + virtual ~ProfilerParent(); + + void Init(); + void ActorDestroy(ActorDestroyReason aActorDestroyReason) override; + void ActorDealloc() override; + + void RequestChunkManagerUpdate(); + + RefPtr<ProfilerParent> mSelfRef; + base::ProcessId mChildPid; + nsTArray<MozPromiseHolder<SingleProcessProfilePromise>> + mPendingRequestedProfiles; + bool mDestroyed; +}; + +} // namespace mozilla + +#endif // ProfilerParent_h diff --git a/tools/profiler/public/shared-libraries.h b/tools/profiler/public/shared-libraries.h new file mode 100644 index 0000000000..2397fbd84f --- /dev/null +++ b/tools/profiler/public/shared-libraries.h @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef SHARED_LIBRARIES_H_ +#define SHARED_LIBRARIES_H_ + +#ifndef MOZ_GECKO_PROFILER +# error This header does not have a useful implementation on your platform! +#endif + +#include "nsNativeCharsetUtils.h" +#include "nsString.h" +#include <nsID.h> + +#include <algorithm> +#include <stdint.h> +#include <stdlib.h> +#include <string> +#include <vector> + +class SharedLibrary { + public: + SharedLibrary(uintptr_t aStart, uintptr_t aEnd, uintptr_t aOffset, + const nsCString& aBreakpadId, const nsString& aModuleName, + const nsString& aModulePath, const nsString& aDebugName, + const nsString& aDebugPath, const nsCString& aVersion, + const char* aArch) + : mStart(aStart), + mEnd(aEnd), + mOffset(aOffset), + mBreakpadId(aBreakpadId), + mModuleName(aModuleName), + mModulePath(aModulePath), + mDebugName(aDebugName), + mDebugPath(aDebugPath), + mVersion(aVersion), + mArch(aArch) {} + + SharedLibrary(const SharedLibrary& aEntry) + : mStart(aEntry.mStart), + mEnd(aEntry.mEnd), + mOffset(aEntry.mOffset), + mBreakpadId(aEntry.mBreakpadId), + mModuleName(aEntry.mModuleName), + mModulePath(aEntry.mModulePath), + mDebugName(aEntry.mDebugName), + mDebugPath(aEntry.mDebugPath), + mVersion(aEntry.mVersion), + mArch(aEntry.mArch) {} + + SharedLibrary& operator=(const SharedLibrary& aEntry) { + // Gracefully handle self assignment + if (this == &aEntry) return *this; + + mStart = aEntry.mStart; + mEnd = aEntry.mEnd; + mOffset = aEntry.mOffset; + mBreakpadId = aEntry.mBreakpadId; + mModuleName = aEntry.mModuleName; + mModulePath = aEntry.mModulePath; + mDebugName = aEntry.mDebugName; + mDebugPath = aEntry.mDebugPath; + mVersion = aEntry.mVersion; + mArch = aEntry.mArch; + return *this; + } + + bool operator==(const SharedLibrary& other) const { + return (mStart == other.mStart) && (mEnd == other.mEnd) && + (mOffset == other.mOffset) && (mModuleName == other.mModuleName) && + (mModulePath == other.mModulePath) && + (mDebugName == other.mDebugName) && + (mDebugPath == other.mDebugPath) && + (mBreakpadId == other.mBreakpadId) && (mVersion == other.mVersion) && + (mArch == other.mArch); + } + + uintptr_t GetStart() const { return mStart; } + uintptr_t GetEnd() const { return mEnd; } + uintptr_t GetOffset() const { return mOffset; } + const nsCString& GetBreakpadId() const { return mBreakpadId; } + const nsString& GetModuleName() const { return mModuleName; } + const nsString& GetModulePath() const { return mModulePath; } + const std::string GetNativeDebugPath() const { + nsAutoCString debugPathStr; + + NS_CopyUnicodeToNative(mDebugPath, debugPathStr); + + return debugPathStr.get(); + } + const nsString& GetDebugName() const { return mDebugName; } + const nsString& GetDebugPath() const { return mDebugPath; } + const nsCString& GetVersion() const { return mVersion; } + const std::string& GetArch() const { return mArch; } + + private: + SharedLibrary() : mStart{0}, mEnd{0}, mOffset{0} {} + + uintptr_t mStart; + uintptr_t mEnd; + uintptr_t mOffset; + nsCString mBreakpadId; + nsString mModuleName; + nsString mModulePath; + nsString mDebugName; + nsString mDebugPath; + nsCString mVersion; + std::string mArch; +}; + +static bool CompareAddresses(const SharedLibrary& first, + const SharedLibrary& second) { + return first.GetStart() < second.GetStart(); +} + +class SharedLibraryInfo { + public: + static SharedLibraryInfo GetInfoForSelf(); + static void Initialize(); + + SharedLibraryInfo() {} + + void AddSharedLibrary(SharedLibrary entry) { mEntries.push_back(entry); } + + const SharedLibrary& GetEntry(size_t i) const { return mEntries[i]; } + + SharedLibrary& GetMutableEntry(size_t i) { return mEntries[i]; } + + // Removes items in the range [first, last) + // i.e. element at the "last" index is not removed + void RemoveEntries(size_t first, size_t last) { + mEntries.erase(mEntries.begin() + first, mEntries.begin() + last); + } + + bool Contains(const SharedLibrary& searchItem) const { + return (mEntries.end() != + std::find(mEntries.begin(), mEntries.end(), searchItem)); + } + + size_t GetSize() const { return mEntries.size(); } + + void SortByAddress() { + std::sort(mEntries.begin(), mEntries.end(), CompareAddresses); + } + + void Clear() { mEntries.clear(); } + + private: + std::vector<SharedLibrary> mEntries; +}; + +#endif |