436 lines
17 KiB
C++
436 lines
17 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// The Gecko Profiler is an always-on profiler that takes fast and low overhead
|
|
// samples of the program execution using only userspace functionality for
|
|
// portability. The goal of this module is to provide performance data in a
|
|
// generic cross-platform way without requiring custom tools or kernel support.
|
|
//
|
|
// Samples are collected to form a timeline with optional timeline event
|
|
// (markers) used for filtering. The samples include both native stacks and
|
|
// platform-independent "label stack" frames.
|
|
|
|
#ifndef GeckoProfiler_h
|
|
#define GeckoProfiler_h
|
|
|
|
// Everything in here is also safe to include unconditionally, and only defines
|
|
// empty macros if MOZ_GECKO_PROFILER is unset.
|
|
// If your file only uses particular APIs (e.g., only markers), please consider
|
|
// including only the needed headers instead of this one, to reduce compilation
|
|
// dependencies.
|
|
#include "BaseProfiler.h"
|
|
#include "ProfileAdditionalInformation.h"
|
|
#include "mozilla/ProfilerCounts.h"
|
|
#include "mozilla/ProfilerLabels.h"
|
|
#include "mozilla/ProfilerMarkers.h"
|
|
#include "mozilla/ProfilerState.h"
|
|
#include "mozilla/ProfilerThreadSleep.h"
|
|
#include "mozilla/ProfilerThreadState.h"
|
|
#include "mozilla/ProgressLogger.h"
|
|
#include "mozilla/Result.h"
|
|
#include "mozilla/ResultVariant.h"
|
|
|
|
#ifndef MOZ_GECKO_PROFILER
|
|
|
|
# include "mozilla/UniquePtr.h"
|
|
|
|
// This file can be #included unconditionally. However, everything within this
|
|
// file must be guarded by a #ifdef MOZ_GECKO_PROFILER, *except* for the
|
|
// following macros and functions, which encapsulate the most common operations
|
|
// and thus avoid the need for many #ifdefs.
|
|
|
|
# define PROFILER_REGISTER_THREAD(name)
|
|
# define PROFILER_UNREGISTER_THREAD()
|
|
# define AUTO_PROFILER_REGISTER_THREAD(name)
|
|
|
|
# define PROFILER_JS_INTERRUPT_CALLBACK()
|
|
|
|
# define PROFILER_SET_JS_CONTEXT(cx)
|
|
# define PROFILER_CLEAR_JS_CONTEXT()
|
|
|
|
// Function stubs for when MOZ_GECKO_PROFILER is not defined.
|
|
|
|
// This won't be used, it's just there to allow the empty definition of
|
|
// `profiler_get_backtrace`.
|
|
struct ProfilerBacktrace {};
|
|
using UniqueProfilerBacktrace = mozilla::UniquePtr<ProfilerBacktrace>;
|
|
|
|
// Get/Capture-backtrace functions can return nullptr or false, the result
|
|
// should be fed to another empty macro or stub anyway.
|
|
|
|
static inline UniqueProfilerBacktrace profiler_get_backtrace() {
|
|
return nullptr;
|
|
}
|
|
|
|
// This won't be used, it's just there to allow the empty definitions of
|
|
// `profiler_capture_backtrace_into` and `profiler_capture_backtrace`.
|
|
struct ProfileChunkedBuffer {};
|
|
|
|
static inline bool profiler_capture_backtrace_into(
|
|
mozilla::ProfileChunkedBuffer& aChunkedBuffer,
|
|
mozilla::StackCaptureOptions aCaptureOptions) {
|
|
return false;
|
|
}
|
|
static inline mozilla::UniquePtr<mozilla::ProfileChunkedBuffer>
|
|
profiler_capture_backtrace() {
|
|
return nullptr;
|
|
}
|
|
|
|
static inline void profiler_set_process_name(
|
|
const nsACString& aProcessName, const nsACString* aETLDplus1 = nullptr) {}
|
|
|
|
static inline void profiler_received_exit_profile(
|
|
const nsACString& aExitProfile) {}
|
|
|
|
static inline void profiler_register_page(uint64_t aTabID,
|
|
uint64_t aInnerWindowID,
|
|
const nsCString& aUrl,
|
|
uint64_t aEmbedderInnerWindowID,
|
|
bool aIsPrivateBrowsing) {}
|
|
static inline void profiler_unregister_page(uint64_t aRegisteredInnerWindowID) {
|
|
}
|
|
|
|
static inline void GetProfilerEnvVarsForChildProcess(
|
|
std::function<void(const char* key, const char* value)>&& aSetEnv) {}
|
|
|
|
static inline void profiler_record_wakeup_count(
|
|
const nsACString& aProcessType) {}
|
|
|
|
#else // !MOZ_GECKO_PROFILER
|
|
|
|
# include "js/ProfilingStack.h"
|
|
# include "mozilla/Assertions.h"
|
|
# include "mozilla/Atomics.h"
|
|
# include "mozilla/Attributes.h"
|
|
# include "mozilla/BaseProfilerRAIIMacro.h"
|
|
# include "mozilla/Maybe.h"
|
|
# include "mozilla/PowerOfTwo.h"
|
|
# include "mozilla/ThreadLocal.h"
|
|
# include "mozilla/TimeStamp.h"
|
|
# include "mozilla/UniquePtr.h"
|
|
# include "nscore.h"
|
|
# include "nsINamed.h"
|
|
# include "nsString.h"
|
|
# include "nsThreadUtils.h"
|
|
|
|
# include <functional>
|
|
# include <stdint.h>
|
|
|
|
class ProfilerBacktrace;
|
|
class ProfilerCodeAddressService;
|
|
struct JSContext;
|
|
|
|
namespace mozilla {
|
|
class ProfileBufferControlledChunkManager;
|
|
class ProfileChunkedBuffer;
|
|
namespace baseprofiler {
|
|
class SpliceableJSONWriter;
|
|
} // namespace baseprofiler
|
|
} // namespace mozilla
|
|
class nsIURI;
|
|
|
|
enum class ProfilerError {
|
|
IsInactive,
|
|
JsonGenerationFailed,
|
|
};
|
|
|
|
template <typename T>
|
|
using ProfilerResult = mozilla::Result<T, ProfilerError>;
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Give information to the profiler
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Register/unregister threads with the profiler. Both functions operate the
|
|
// same whether the profiler is active or inactive.
|
|
# define PROFILER_REGISTER_THREAD(name) \
|
|
do { \
|
|
char stackTop; \
|
|
profiler_register_thread(name, &stackTop); \
|
|
} while (0)
|
|
# define PROFILER_UNREGISTER_THREAD() profiler_unregister_thread()
|
|
ProfilingStack* profiler_register_thread(const char* name, void* guessStackTop);
|
|
void profiler_unregister_thread();
|
|
|
|
// Registers a DOM Window (the JS global `window`) with the profiler. Each
|
|
// Window _roughly_ corresponds to a single document loaded within a
|
|
// browsing context. Both the Window Id and Browser Id are recorded to allow
|
|
// correlating different Windows loaded within the same tab or frame element.
|
|
//
|
|
// We register pages for each navigations but we do not register
|
|
// history.pushState or history.replaceState since they correspond to the same
|
|
// Inner Window ID. When a browsing context is first loaded, the first url
|
|
// loaded in it will be about:blank. Because of that, this call keeps the first
|
|
// non-about:blank registration of window and discards the previous one.
|
|
//
|
|
// "aTabID" is the BrowserId of that document belongs to.
|
|
// That's used to determine the tab of that page.
|
|
// "aInnerWindowID" is the ID of the `window` global object of that
|
|
// document.
|
|
// "aUrl" is the URL of the page.
|
|
// "aEmbedderInnerWindowID" is the inner window id of embedder. It's used to
|
|
// determine sub documents of a page.
|
|
// "aIsPrivateBrowsing" is true if this browsing context happens in a
|
|
// private browsing context.
|
|
void profiler_register_page(uint64_t aTabID, uint64_t aInnerWindowID,
|
|
const nsCString& aUrl,
|
|
uint64_t aEmbedderInnerWindowID,
|
|
bool aIsPrivateBrowsing);
|
|
// Unregister page with the profiler.
|
|
//
|
|
// Take a Inner Window ID and unregister the page entry that has the same ID.
|
|
void profiler_unregister_page(uint64_t aRegisteredInnerWindowID);
|
|
|
|
// Remove all registered and unregistered pages in the profiler.
|
|
void profiler_clear_all_pages();
|
|
|
|
class BaseProfilerCount;
|
|
void profiler_add_sampled_counter(BaseProfilerCount* aCounter);
|
|
void profiler_remove_sampled_counter(BaseProfilerCount* aCounter);
|
|
|
|
// Register and unregister a thread within a scope.
|
|
# define AUTO_PROFILER_REGISTER_THREAD(name) \
|
|
mozilla::AutoProfilerRegisterThread PROFILER_RAII(name)
|
|
|
|
enum class SamplingState {
|
|
JustStopped, // Sampling loop has just stopped without sampling, between the
|
|
// callback registration and now.
|
|
SamplingPaused, // Profiler is active but sampling loop has gone through a
|
|
// pause.
|
|
NoStackSamplingCompleted, // A full sampling loop has completed in
|
|
// no-stack-sampling mode.
|
|
SamplingCompleted // A full sampling loop has completed.
|
|
};
|
|
|
|
using PostSamplingCallback = std::function<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);
|
|
|
|
// Called by the JSRuntime's operation callback. This is used to start profiling
|
|
// on auxiliary threads. Operates the same whether the profiler is active or
|
|
// not.
|
|
# define PROFILER_JS_INTERRUPT_CALLBACK() profiler_js_interrupt_callback()
|
|
void profiler_js_interrupt_callback();
|
|
|
|
// Set and clear the current thread's JSContext.
|
|
# define PROFILER_SET_JS_CONTEXT(cx) profiler_set_js_context(cx)
|
|
# define PROFILER_CLEAR_JS_CONTEXT() profiler_clear_js_context()
|
|
void profiler_set_js_context(JSContext* aCx);
|
|
void profiler_clear_js_context();
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Get information from the profiler
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Get the chunk manager used in the current profiling session, or null.
|
|
mozilla::ProfileBufferControlledChunkManager*
|
|
profiler_get_controlled_chunk_manager();
|
|
|
|
// The number of milliseconds since the process started. Operates the same
|
|
// whether the profiler is active or inactive.
|
|
double profiler_time();
|
|
|
|
// An object of this class is passed to profiler_suspend_and_sample_thread().
|
|
// For each stack frame, one of the Collect methods will be called.
|
|
class ProfilerStackCollector {
|
|
public:
|
|
// Some collectors need to worry about possibly overwriting previous
|
|
// generations of data. If that's not an issue, this can return Nothing,
|
|
// which is the default behaviour.
|
|
virtual mozilla::Maybe<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(JS::ProfilingCategoryPair aCategory,
|
|
const char* aLabel) = 0;
|
|
|
|
virtual void CollectProfilingStackFrame(
|
|
const js::ProfilingStackFrame& aFrame) = 0;
|
|
};
|
|
|
|
// This method suspends the thread identified by aThreadId, samples its
|
|
// profiling stack, JS stack, and (optionally) native stack, passing the
|
|
// collected frames into aCollector. aFeatures dictates which compiler features
|
|
// are used. |Leaf| is the only relevant one.
|
|
// Use `ProfilerThreadId{}` (unspecified) to sample the current thread.
|
|
void profiler_suspend_and_sample_thread(ProfilerThreadId aThreadId,
|
|
uint32_t aFeatures,
|
|
ProfilerStackCollector& aCollector,
|
|
bool aSampleNative = true);
|
|
|
|
struct ProfilerBacktraceDestructor {
|
|
void operator()(ProfilerBacktrace*);
|
|
};
|
|
|
|
using UniqueProfilerBacktrace =
|
|
mozilla::UniquePtr<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,
|
|
mozilla::StackCaptureOptions aCaptureOptions);
|
|
|
|
// Immediately capture the current thread's call stack, and return it in a
|
|
// ProfileChunkedBuffer (usually for later use in MarkerStack::TakeBacktrace()).
|
|
// May be null if unsuccessful, or if the profiler is inactive.
|
|
mozilla::UniquePtr<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();
|
|
|
|
// Record through glean how many times profiler_thread_wake has been
|
|
// called.
|
|
void profiler_record_wakeup_count(const nsACString& aProcessType);
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Output profiles
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Set a user-friendly process name, used in JSON stream. Allows an optional
|
|
// detailed name which may include private info (eTLD+1 in fission)
|
|
void profiler_set_process_name(const nsACString& aProcessName,
|
|
const nsACString* aETLDplus1 = nullptr);
|
|
|
|
// Record an exit profile from a child process.
|
|
void profiler_received_exit_profile(const nsACString& aExitProfile);
|
|
|
|
// Get the profile encoded as a JSON string. A no-op (returning nullptr) if the
|
|
// profiler is inactive.
|
|
// If aIsShuttingDown is true, the current time is included as the process
|
|
// shutdown time in the JSON's "meta" object.
|
|
mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0,
|
|
bool aIsShuttingDown = false);
|
|
|
|
// Write the profile for this process (excluding subprocesses) into aWriter.
|
|
// Returns a failed result if the profiler is inactive.
|
|
ProfilerResult<mozilla::ProfileGenerationAdditionalInformation>
|
|
profiler_stream_json_for_this_process(
|
|
mozilla::baseprofiler::SpliceableJSONWriter& aWriter, double aSinceTime = 0,
|
|
bool aIsShuttingDown = false,
|
|
ProfilerCodeAddressService* aService = nullptr,
|
|
mozilla::ProgressLogger aProgressLogger = {});
|
|
|
|
// Get the profile and write it into a file. A no-op if the profile is
|
|
// inactive.
|
|
//
|
|
// This function is 'extern "C"' so that it is easily callable from a debugger
|
|
// in a build without debugging information (a workaround for
|
|
// http://llvm.org/bugs/show_bug.cgi?id=22211).
|
|
extern "C" {
|
|
void profiler_save_profile_to_file(const char* aFilename);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// RAII classes
|
|
//---------------------------------------------------------------------------
|
|
|
|
namespace mozilla {
|
|
|
|
// Convenience class to register and unregister a thread with the profiler.
|
|
// Needs to be the first object on the stack of the thread.
|
|
class MOZ_RAII AutoProfilerRegisterThread final {
|
|
public:
|
|
explicit AutoProfilerRegisterThread(const char* aName) {
|
|
profiler_register_thread(aName, this);
|
|
}
|
|
|
|
~AutoProfilerRegisterThread() { profiler_unregister_thread(); }
|
|
|
|
private:
|
|
AutoProfilerRegisterThread(const AutoProfilerRegisterThread&) = delete;
|
|
AutoProfilerRegisterThread& operator=(const AutoProfilerRegisterThread&) =
|
|
delete;
|
|
};
|
|
|
|
// Get the MOZ_PROFILER_STARTUP* environment variables that should be
|
|
// supplied to a child process that is about to be launched, in order
|
|
// to make that child process start with the same profiler settings as
|
|
// in the current process. The given function is invoked once for
|
|
// each variable to be set.
|
|
void GetProfilerEnvVarsForChildProcess(
|
|
std::function<void(const char* key, const char* value)>&& aSetEnv);
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // !MOZ_GECKO_PROFILER
|
|
|
|
#endif // GeckoProfiler_h
|