diff options
Diffstat (limited to 'tools/profiler/public')
-rw-r--r-- | tools/profiler/public/GeckoProfiler.h | 300 | ||||
-rw-r--r-- | tools/profiler/public/GeckoProfilerFunc.h | 125 | ||||
-rw-r--r-- | tools/profiler/public/GeckoProfilerImpl.h | 522 | ||||
-rw-r--r-- | tools/profiler/public/ProfileGatherer.h | 42 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerBacktrace.h | 36 | ||||
-rw-r--r-- | tools/profiler/public/ProfilerMarkers.h | 193 | ||||
-rw-r--r-- | tools/profiler/public/PseudoStack.h | 469 | ||||
-rw-r--r-- | tools/profiler/public/shared-libraries.h | 137 |
8 files changed, 1824 insertions, 0 deletions
diff --git a/tools/profiler/public/GeckoProfiler.h b/tools/profiler/public/GeckoProfiler.h new file mode 100644 index 000000000..bef017d11 --- /dev/null +++ b/tools/profiler/public/GeckoProfiler.h @@ -0,0 +1,300 @@ +/* -*- 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/. */ + +/* *************** SPS Sampler Information **************** + * + * SPS is an always on profiler that takes fast and low overheads samples + * of the program execution using only userspace functionity 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. + * + * Non goals: Support features that are platform specific or replace + * platform specific profilers. + * + * Samples are collected to form a timeline with optional timeline event (markers) + * used for filtering. + * + * SPS collects samples in a platform independant way by using a speudo stack abstraction + * of the real program stack by using 'sample stack frames'. When a sample is collected + * all active sample stack frames and the program counter are recorded. + */ + +/* *************** SPS Sampler File Format **************** + * + * Simple new line seperated tag format: + * S -> BOF tags EOF + * tags -> tag tags + * tag -> CHAR - STRING + * + * Tags: + * 's' - Sample tag followed by the first stack frame followed by 0 or more 'c' tags. + * 'c' - Continue Sample tag gives remaining tag element. If a 'c' tag is seen without + * a preceding 's' tag it should be ignored. This is to support the behavior + * of circular buffers. + * If the 'stackwalk' feature is enabled this tag will have the format + * 'l-<library name>@<hex address>' and will expect an external tool to translate + * the tag into something readable through a symbolication processing step. + * 'm' - Timeline marker. Zero or more may appear before a 's' tag. + * 'l' - Information about the program counter library and address. Post processing + * can include function and source line. If built with leaf data enabled + * this tag will describe the last 'c' tag. + * 'r' - Responsiveness tag following an 's' tag. Gives an indication on how well the + * application is responding to the event loop. Lower is better. + * 't' - Elapse time since recording started. + * + */ + +#ifndef SAMPLER_H +#define SAMPLER_H + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#ifndef SPS_STANDALONE +#include "js/TypeDecls.h" +#endif +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" + +namespace mozilla { +class TimeStamp; + +namespace dom { +class Promise; +} // namespace dom + +} // namespace mozilla + +#ifndef SPS_STANDALONE +class nsIProfilerStartParams; +#endif + +enum TracingMetadata { + TRACING_DEFAULT, + TRACING_INTERVAL_START, + TRACING_INTERVAL_END, + TRACING_EVENT, + TRACING_EVENT_BACKTRACE, + TRACING_TIMESTAMP +}; + +#if !defined(MOZ_ENABLE_PROFILER_SPS) + +#include <stdint.h> +#include <stdarg.h> + +// Insert a RAII in this scope to active a pseudo label. Any samples collected +// in this scope will contain this annotation. For dynamic strings use +// PROFILER_LABEL_PRINTF. Arguments must be string literals. +#define PROFILER_LABEL(name_space, info, category) do {} while (0) + +// Similar to PROFILER_LABEL, PROFILER_LABEL_FUNC will push/pop the enclosing +// functon name as the pseudostack label. +#define PROFILER_LABEL_FUNC(category) do {} while (0) + +// Format a dynamic string as a pseudo label. These labels will a considerable +// storage size in the circular buffer compared to regular labels. This function +// can be used to annotate custom information such as URL for the resource being +// decoded or the size of the paint. +#define PROFILER_LABEL_PRINTF(name_space, info, category, format, ...) do {} while (0) + +// Insert a marker in the profile timeline. This is useful to delimit something +// important happening such as the first paint. Unlike profiler_label that are +// only recorded if a sample is collected while it is active, marker will always +// be collected. +#define PROFILER_MARKER(info) do {} while (0) +#define PROFILER_MARKER_PAYLOAD(info, payload) do { mozilla::UniquePtr<ProfilerMarkerPayload> payloadDeletor(payload); } while (0) + +// Main thread specilization to avoid TLS lookup for performance critical use. +#define PROFILER_MAIN_THREAD_LABEL(name_space, info, category) do {} while (0) +#define PROFILER_MAIN_THREAD_LABEL_PRINTF(name_space, info, category, format, ...) do {} while (0) + +static inline void profiler_tracing(const char* aCategory, const char* aInfo, + TracingMetadata metaData = TRACING_DEFAULT) {} +class ProfilerBacktrace; + +static inline void profiler_tracing(const char* aCategory, const char* aInfo, + ProfilerBacktrace* aCause, + TracingMetadata metaData = TRACING_DEFAULT) {} + +// Initilize the profiler TLS, signal handlers on linux. If MOZ_PROFILER_STARTUP +// is set the profiler will be started. This call must happen before any other +// sampler calls. Particularly sampler_label/sampler_marker. +static inline void profiler_init(void* stackTop) {}; + +// 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 pseudo labels should have been popped. +static inline void profiler_shutdown() {}; + +// Start the profiler with the selected options. The samples will be +// recorded in a circular buffer. +// "aProfileEntries" is an abstract size indication of how big +// the profile's circular buffer should be. Multiply by 4 +// words to get the cost. +// "aInterval" the sampling interval. The profiler will do its +// best to sample at this interval. The profiler visualization +// should represent the actual sampling accuracy. +static inline void profiler_start(int aProfileEntries, double aInterval, + const char** aFeatures, uint32_t aFeatureCount, + const char** aThreadNameFilters, uint32_t aFilterCount) {} + +// Stop the profiler and discard the profile. Call 'profiler_save' before this +// to retrieve the profile. +static inline void profiler_stop() {} + +// These functions pause and resume the profiler. 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. +static inline bool profiler_is_paused() { return false; } +static inline void profiler_pause() {} +static inline void profiler_resume() {} + + +// Immediately capture the current thread's call stack and return it +static inline ProfilerBacktrace* profiler_get_backtrace() { return nullptr; } +static inline void profiler_get_backtrace_noalloc(char *output, size_t outputSize) { return; } + +// Free a ProfilerBacktrace returned by profiler_get_backtrace() +static inline void profiler_free_backtrace(ProfilerBacktrace* aBacktrace) {} + +static inline bool profiler_is_active() { return false; } + +// Check if an external profiler feature is active. +// Supported: +// * gpu +static inline bool profiler_feature_active(const char*) { return false; } + +// Internal-only. Used by the event tracer. +static inline void profiler_responsiveness(const mozilla::TimeStamp& aTime) {} + +// Internal-only. +static inline void profiler_set_frame_number(int frameNumber) {} + +// Get the profile encoded as a JSON string. +static inline mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0) { + return nullptr; +} + +// Get the profile encoded as a JSON object. +static inline JSObject* profiler_get_profile_jsobject(JSContext* aCx, + double aSinceTime = 0) { + return nullptr; +} + +#ifndef SPS_STANDALONE +// Get the profile encoded as a JSON object. +static inline void profiler_get_profile_jsobject_async(double aSinceTime = 0, + mozilla::dom::Promise* = 0) {} +static inline void profiler_get_start_params(int* aEntrySize, + double* aInterval, + mozilla::Vector<const char*>* aFilters, + mozilla::Vector<const char*>* aFeatures) {} +#endif + +// Get the profile and write it into a file +static inline void profiler_save_profile_to_file(char* aFilename) { } + +// Get the features supported by the profiler that are accepted by profiler_init. +// Returns a null terminated char* array. +static inline char** profiler_get_features() { return nullptr; } + +// Get information about the current buffer status. +// Retursn (using outparams) the current write position in the buffer, +// the total size of the buffer, and the generation of the buffer. +// 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. +static inline void profiler_get_buffer_info(uint32_t *aCurrentPosition, + uint32_t *aTotalSize, + uint32_t *aGeneration) +{ + *aCurrentPosition = 0; + *aTotalSize = 0; + *aGeneration = 0; +} + +// Discard the profile, throw away the profile and notify 'profiler-locked'. +// This function is to be used when entering private browsing to prevent +// the profiler from collecting sensitive data. +static inline void profiler_lock() {} + +// Re-enable the profiler and notify 'profiler-unlocked'. +static inline void profiler_unlock() {} + +static inline void profiler_register_thread(const char* name, void* guessStackTop) {} +static inline void profiler_unregister_thread() {} + +// These functions tell the profiler that a thread went to sleep so that we can avoid +// sampling it while it's sleeping. Calling profiler_sleep_start() twice without +// profiler_sleep_end() is an error. +static inline void profiler_sleep_start() {} +static inline void profiler_sleep_end() {} +static inline bool profiler_is_sleeping() { return false; } + +// Call by the JSRuntime's operation callback. This is used to enable +// profiling on auxilerary threads. +static inline void profiler_js_operation_callback() {} + +static inline double profiler_time() { return 0; } +static inline double profiler_time(const mozilla::TimeStamp& aTime) { return 0; } + +static inline bool profiler_in_privacy_mode() { return false; } + +static inline void profiler_log(const char *str) {} +static inline void profiler_log(const char *fmt, va_list args) {} + +#else + +#include "GeckoProfilerImpl.h" + +#endif + +class MOZ_RAII GeckoProfilerInitRAII { +public: + explicit GeckoProfilerInitRAII(void* stackTop) { + profiler_init(stackTop); + } + ~GeckoProfilerInitRAII() { + profiler_shutdown(); + } +}; + +class MOZ_RAII GeckoProfilerSleepRAII { +public: + GeckoProfilerSleepRAII() { + profiler_sleep_start(); + } + ~GeckoProfilerSleepRAII() { + profiler_sleep_end(); + } +}; + +/** + * Temporarily wake up the profiler while servicing events such as + * Asynchronous Procedure Calls (APCs). + */ +class MOZ_RAII GeckoProfilerWakeRAII { +public: + GeckoProfilerWakeRAII() + : mIssuedWake(profiler_is_sleeping()) + { + if (mIssuedWake) { + profiler_sleep_end(); + } + } + ~GeckoProfilerWakeRAII() { + if (mIssuedWake) { + MOZ_ASSERT(!profiler_is_sleeping()); + profiler_sleep_start(); + } + } +private: + bool mIssuedWake; +}; + +#endif // ifndef SAMPLER_H diff --git a/tools/profiler/public/GeckoProfilerFunc.h b/tools/profiler/public/GeckoProfilerFunc.h new file mode 100644 index 000000000..e0d27f593 --- /dev/null +++ b/tools/profiler/public/GeckoProfilerFunc.h @@ -0,0 +1,125 @@ +/* -*- 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 PROFILER_FUNCS_H +#define PROFILER_FUNCS_H + +#ifndef SPS_STANDALONE +#include "js/TypeDecls.h" +#endif +#include "js/ProfilingStack.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include <stdint.h> + +class nsISupports; + +namespace mozilla { +class TimeStamp; + +namespace dom { +class Promise; +} // namespace dom + +} // namespace mozilla + +class ProfilerBacktrace; +class ProfilerMarkerPayload; + +// Returns a handle to pass on exit. This can check that we are popping the +// correct callstack. +inline void* mozilla_sampler_call_enter(const char *aInfo, js::ProfileEntry::Category aCategory, + void *aFrameAddress = nullptr, bool aCopy = false, + uint32_t line = 0); + +inline void mozilla_sampler_call_exit(void* handle); + +void mozilla_sampler_add_marker(const char *aInfo, + ProfilerMarkerPayload *aPayload = nullptr); + +void mozilla_sampler_start(int aEntries, double aInterval, + const char** aFeatures, uint32_t aFeatureCount, + const char** aThreadNameFilters, uint32_t aFilterCount); + +void mozilla_sampler_stop(); + +bool mozilla_sampler_is_paused(); +void mozilla_sampler_pause(); +void mozilla_sampler_resume(); + +ProfilerBacktrace* mozilla_sampler_get_backtrace(); +void mozilla_sampler_free_backtrace(ProfilerBacktrace* aBacktrace); +void mozilla_sampler_get_backtrace_noalloc(char *output, size_t outputSize); + +bool mozilla_sampler_is_active(); + +bool mozilla_sampler_feature_active(const char* aName); + +void mozilla_sampler_responsiveness(const mozilla::TimeStamp& time); + +void mozilla_sampler_frame_number(int frameNumber); + +const double* mozilla_sampler_get_responsiveness(); + +void mozilla_sampler_save(); + +mozilla::UniquePtr<char[]> mozilla_sampler_get_profile(double aSinceTime); + +#ifndef SPS_STANDALONE +JSObject *mozilla_sampler_get_profile_data(JSContext* aCx, double aSinceTime); +void mozilla_sampler_get_profile_data_async(double aSinceTime, + mozilla::dom::Promise* aPromise); +void mozilla_sampler_get_profiler_start_params(int* aEntrySize, + double* aInterval, + mozilla::Vector<const char*>* aFilters, + mozilla::Vector<const char*>* aFeatures); +void mozilla_sampler_get_gatherer(nsISupports** aRetVal); +#endif + +// Make this function easily callable from a debugger in a build without +// debugging information (work around http://llvm.org/bugs/show_bug.cgi?id=22211) +extern "C" { + void mozilla_sampler_save_profile_to_file(const char* aFilename); +} + +const char** mozilla_sampler_get_features(); + +void mozilla_sampler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize, + uint32_t *aGeneration); + +void mozilla_sampler_init(void* stackTop); + +void mozilla_sampler_shutdown(); + +// Lock the profiler. When locked the profiler is (1) stopped, +// (2) profile data is cleared, (3) profiler-locked is fired. +// This is used to lock down the profiler during private browsing +void mozilla_sampler_lock(); + +// Unlock the profiler, leaving it stopped and fires profiler-unlocked. +void mozilla_sampler_unlock(); + +// Register/unregister threads with the profiler +bool mozilla_sampler_register_thread(const char* name, void* stackTop); +void mozilla_sampler_unregister_thread(); + +void mozilla_sampler_sleep_start(); +void mozilla_sampler_sleep_end(); +bool mozilla_sampler_is_sleeping(); + +double mozilla_sampler_time(); +double mozilla_sampler_time(const mozilla::TimeStamp& aTime); + +void mozilla_sampler_tracing(const char* aCategory, const char* aInfo, + TracingMetadata aMetaData); + +void mozilla_sampler_tracing(const char* aCategory, const char* aInfo, + ProfilerBacktrace* aCause, + TracingMetadata aMetaData); + +void mozilla_sampler_log(const char *fmt, va_list args); + +#endif + diff --git a/tools/profiler/public/GeckoProfilerImpl.h b/tools/profiler/public/GeckoProfilerImpl.h new file mode 100644 index 000000000..a32096b94 --- /dev/null +++ b/tools/profiler/public/GeckoProfilerImpl.h @@ -0,0 +1,522 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "GeckoProfiler.h" + +#ifndef TOOLS_SPS_SAMPLER_H_ +#define TOOLS_SPS_SAMPLER_H_ + +#include <stdlib.h> +#include <signal.h> +#include <stdarg.h> +#include "mozilla/Assertions.h" +#include "mozilla/GuardObjects.h" +#include "mozilla/Sprintf.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/UniquePtr.h" +#ifndef SPS_STANDALONE +#include "nscore.h" +#include "nsISupports.h" +#endif +#include "GeckoProfilerFunc.h" +#include "PseudoStack.h" +#include "ProfilerBacktrace.h" + +// Make sure that we can use std::min here without the Windows headers messing with us. +#ifdef min +#undef min +#endif + +class GeckoSampler; + +namespace mozilla { +class TimeStamp; +} // namespace mozilla + +extern MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack; +extern MOZ_THREAD_LOCAL(GeckoSampler *) tlsTicker; +extern MOZ_THREAD_LOCAL(void *) tlsStackTop; +extern bool stack_key_initialized; + +#ifndef SAMPLE_FUNCTION_NAME +# ifdef __GNUC__ +# define SAMPLE_FUNCTION_NAME __FUNCTION__ +# elif defined(_MSC_VER) +# define SAMPLE_FUNCTION_NAME __FUNCTION__ +# else +# define SAMPLE_FUNCTION_NAME __func__ // defined in C99, supported in various C++ compilers. Just raw function name. +# endif +#endif + +static inline +void profiler_init(void* stackTop) +{ + mozilla_sampler_init(stackTop); +} + +static inline +void profiler_shutdown() +{ + mozilla_sampler_shutdown(); +} + +static inline +void profiler_start(int aProfileEntries, double aInterval, + const char** aFeatures, uint32_t aFeatureCount, + const char** aThreadNameFilters, uint32_t aFilterCount) +{ + mozilla_sampler_start(aProfileEntries, aInterval, aFeatures, aFeatureCount, aThreadNameFilters, aFilterCount); +} + +static inline +void profiler_stop() +{ + mozilla_sampler_stop(); +} + +static inline +bool profiler_is_paused() +{ + return mozilla_sampler_is_paused(); +} + +static inline +void profiler_pause() +{ + mozilla_sampler_pause(); +} + +static inline +void profiler_resume() +{ + mozilla_sampler_resume(); +} + +static inline +ProfilerBacktrace* profiler_get_backtrace() +{ + return mozilla_sampler_get_backtrace(); +} + +static inline +void profiler_free_backtrace(ProfilerBacktrace* aBacktrace) +{ + mozilla_sampler_free_backtrace(aBacktrace); +} + +static inline +void profiler_get_backtrace_noalloc(char *output, size_t outputSize) +{ + return mozilla_sampler_get_backtrace_noalloc(output, outputSize); +} + +static inline +bool profiler_is_active() +{ + return mozilla_sampler_is_active(); +} + +static inline +bool profiler_feature_active(const char* aName) +{ + return mozilla_sampler_feature_active(aName); +} + +static inline +void profiler_responsiveness(const mozilla::TimeStamp& aTime) +{ + mozilla_sampler_responsiveness(aTime); +} + +static inline +void profiler_set_frame_number(int frameNumber) +{ + return mozilla_sampler_frame_number(frameNumber); +} + +static inline +mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0) +{ + return mozilla_sampler_get_profile(aSinceTime); +} + +#ifndef SPS_STANDALONE +static inline +JSObject* profiler_get_profile_jsobject(JSContext* aCx, double aSinceTime = 0) +{ + return mozilla_sampler_get_profile_data(aCx, aSinceTime); +} + +static inline +void profiler_get_profile_jsobject_async(double aSinceTime = 0, + mozilla::dom::Promise* aPromise = 0) +{ + mozilla_sampler_get_profile_data_async(aSinceTime, aPromise); +} + +static inline +void profiler_get_start_params(int* aEntrySize, + double* aInterval, + mozilla::Vector<const char*>* aFilters, + mozilla::Vector<const char*>* aFeatures) +{ + mozilla_sampler_get_profiler_start_params(aEntrySize, aInterval, aFilters, aFeatures); +} + +static inline +void profiler_get_gatherer(nsISupports** aRetVal) +{ + mozilla_sampler_get_gatherer(aRetVal); +} + +#endif + +static inline +void profiler_save_profile_to_file(const char* aFilename) +{ + return mozilla_sampler_save_profile_to_file(aFilename); +} + +static inline +const char** profiler_get_features() +{ + return mozilla_sampler_get_features(); +} + +static inline +void profiler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize, + uint32_t *aGeneration) +{ + return mozilla_sampler_get_buffer_info(aCurrentPosition, aTotalSize, aGeneration); +} + +static inline +void profiler_lock() +{ + return mozilla_sampler_lock(); +} + +static inline +void profiler_unlock() +{ + return mozilla_sampler_unlock(); +} + +static inline +void profiler_register_thread(const char* name, void* guessStackTop) +{ + mozilla_sampler_register_thread(name, guessStackTop); +} + +static inline +void profiler_unregister_thread() +{ + mozilla_sampler_unregister_thread(); +} + +static inline +void profiler_sleep_start() +{ + mozilla_sampler_sleep_start(); +} + +static inline +void profiler_sleep_end() +{ + mozilla_sampler_sleep_end(); +} + +static inline +bool profiler_is_sleeping() +{ + return mozilla_sampler_is_sleeping(); +} + +#ifndef SPS_STANDALONE +static inline +void profiler_js_operation_callback() +{ + PseudoStack *stack = tlsPseudoStack.get(); + if (!stack) { + return; + } + + stack->jsOperationCallback(); +} +#endif + +static inline +double profiler_time() +{ + return mozilla_sampler_time(); +} + +static inline +double profiler_time(const mozilla::TimeStamp& aTime) +{ + return mozilla_sampler_time(aTime); +} + +static inline +bool profiler_in_privacy_mode() +{ + PseudoStack *stack = tlsPseudoStack.get(); + if (!stack) { + return false; + } + return stack->mPrivacyMode; +} + +static inline void profiler_tracing(const char* aCategory, const char* aInfo, + ProfilerBacktrace* aCause, + TracingMetadata aMetaData = TRACING_DEFAULT) +{ + // Don't insert a marker if we're not profiling to avoid + // the heap copy (malloc). + if (!stack_key_initialized || !profiler_is_active()) { + delete aCause; + return; + } + + mozilla_sampler_tracing(aCategory, aInfo, aCause, aMetaData); +} + +static inline void profiler_tracing(const char* aCategory, const char* aInfo, + TracingMetadata aMetaData = TRACING_DEFAULT) +{ + if (!stack_key_initialized) + return; + + // Don't insert a marker if we're not profiling to avoid + // the heap copy (malloc). + if (!profiler_is_active()) { + return; + } + + mozilla_sampler_tracing(aCategory, aInfo, aMetaData); +} + +#define SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line) id ## line +#define SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, line) SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line) +#define SAMPLER_APPEND_LINE_NUMBER(id) SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, __LINE__) + +// Uncomment this to turn on systrace or build with +// ac_add_options --enable-systace +//#define MOZ_USE_SYSTRACE +#ifdef MOZ_USE_SYSTRACE +#ifndef ATRACE_TAG +# define ATRACE_TAG ATRACE_TAG_ALWAYS +#endif +// We need HAVE_ANDROID_OS to be defined for Trace.h. +// If its not set we will set it temporary and remove it. +# ifndef HAVE_ANDROID_OS +# define HAVE_ANDROID_OS +# define REMOVE_HAVE_ANDROID_OS +# endif +// Android source code will include <cutils/trace.h> before this. There is no +// HAVE_ANDROID_OS defined in Firefox OS build at that time. Enabled it globally +// will cause other build break. So atrace_begin and atrace_end are not defined. +// It will cause a build-break when we include <utils/Trace.h>. Use undef +// _LIBS_CUTILS_TRACE_H will force <cutils/trace.h> to define atrace_begin and +// atrace_end with defined HAVE_ANDROID_OS again. Then there is no build-break. +# undef _LIBS_CUTILS_TRACE_H +# include <utils/Trace.h> +# define MOZ_PLATFORM_TRACING(name) android::ScopedTrace SAMPLER_APPEND_LINE_NUMBER(scopedTrace)(ATRACE_TAG, name); +# ifdef REMOVE_HAVE_ANDROID_OS +# undef HAVE_ANDROID_OS +# undef REMOVE_HAVE_ANDROID_OS +# endif +#else +# define MOZ_PLATFORM_TRACING(name) +#endif + +// we want the class and function name but can't easily get that using preprocessor macros +// __func__ doesn't have the class name and __PRETTY_FUNCTION__ has the parameters + +#define PROFILER_LABEL(name_space, info, category) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__) +#define PROFILER_LABEL_FUNC(category) MOZ_PLATFORM_TRACING(SAMPLE_FUNCTION_NAME) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(SAMPLE_FUNCTION_NAME, category, __LINE__) +#define PROFILER_LABEL_PRINTF(name_space, info, category, ...) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, __VA_ARGS__) + +#define PROFILER_MARKER(info) mozilla_sampler_add_marker(info) +#define PROFILER_MARKER_PAYLOAD(info, payload) mozilla_sampler_add_marker(info, payload) +#define PROFILER_MAIN_THREAD_MARKER(info) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla_sampler_add_marker(info) + +#define PROFILER_MAIN_THREAD_LABEL(name_space, info, category) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__) +#define PROFILER_MAIN_THREAD_LABEL_PRINTF(name_space, info, category, ...) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, __VA_ARGS__) + + +/* FIXME/bug 789667: memory constraints wouldn't much of a problem for + * this small a sample buffer size, except that serializing the + * profile data is extremely, unnecessarily memory intensive. */ +#ifdef MOZ_WIDGET_GONK +# define PLATFORM_LIKELY_MEMORY_CONSTRAINED +#endif + +#if !defined(PLATFORM_LIKELY_MEMORY_CONSTRAINED) && !defined(ARCH_ARMV6) +# define PROFILE_DEFAULT_ENTRY 1000000 +#else +# define PROFILE_DEFAULT_ENTRY 100000 +#endif + +// In the case of profiler_get_backtrace we know that we only need enough space +// for a single backtrace. +#define GET_BACKTRACE_DEFAULT_ENTRY 1000 + +#if defined(PLATFORM_LIKELY_MEMORY_CONSTRAINED) +/* A 1ms sampling interval has been shown to be a large perf hit + * (10fps) on memory-contrained (low-end) platforms, and additionally + * to yield different results from the profiler. Where this is the + * important case, b2g, there are also many gecko processes which + * magnify these effects. */ +# define PROFILE_DEFAULT_INTERVAL 10 +#elif defined(ANDROID) +// We use a lower frequency on Android, in order to make things work +// more smoothly on phones. This value can be adjusted later with +// some libunwind optimizations. +// In one sample measurement on Galaxy Nexus, out of about 700 backtraces, +// 60 of them took more than 25ms, and the average and standard deviation +// were 6.17ms and 9.71ms respectively. + +// For now since we don't support stackwalking let's use 1ms since it's fast +// enough. +#define PROFILE_DEFAULT_INTERVAL 1 +#else +#define PROFILE_DEFAULT_INTERVAL 1 +#endif +#define PROFILE_DEFAULT_FEATURES NULL +#define PROFILE_DEFAULT_FEATURE_COUNT 0 + +namespace mozilla { + +class MOZ_RAII GeckoProfilerTracingRAII { +public: + GeckoProfilerTracingRAII(const char* aCategory, const char* aInfo, + mozilla::UniquePtr<ProfilerBacktrace> aBacktrace + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mCategory(aCategory) + , mInfo(aInfo) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + profiler_tracing(mCategory, mInfo, aBacktrace.release(), TRACING_INTERVAL_START); + } + + ~GeckoProfilerTracingRAII() { + profiler_tracing(mCategory, mInfo, TRACING_INTERVAL_END); + } + +protected: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + const char* mCategory; + const char* mInfo; +}; + +class MOZ_RAII SamplerStackFrameRAII { +public: + // we only copy the strings at save time, so to take multiple parameters we'd need to copy them then. + SamplerStackFrameRAII(const char *aInfo, + js::ProfileEntry::Category aCategory, uint32_t line + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + mHandle = mozilla_sampler_call_enter(aInfo, aCategory, this, false, line); + } + ~SamplerStackFrameRAII() { + mozilla_sampler_call_exit(mHandle); + } +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + void* mHandle; +}; + +static const int SAMPLER_MAX_STRING = 128; +class MOZ_RAII SamplerStackFramePrintfRAII { +public: + // we only copy the strings at save time, so to take multiple parameters we'd need to copy them then. + SamplerStackFramePrintfRAII(const char *aInfo, + js::ProfileEntry::Category aCategory, uint32_t line, const char *aFormat, ...) + : mHandle(nullptr) + { + if (profiler_is_active() && !profiler_in_privacy_mode()) { + va_list args; + va_start(args, aFormat); + char buff[SAMPLER_MAX_STRING]; + + // We have to use seperate printf's because we're using + // the vargs. + VsprintfLiteral(buff, aFormat, args); + SprintfLiteral(mDest, "%s %s", aInfo, buff); + + mHandle = mozilla_sampler_call_enter(mDest, aCategory, this, true, line); + va_end(args); + } else { + mHandle = mozilla_sampler_call_enter(aInfo, aCategory, this, false, line); + } + } + ~SamplerStackFramePrintfRAII() { + mozilla_sampler_call_exit(mHandle); + } +private: + char mDest[SAMPLER_MAX_STRING]; + void* mHandle; +}; + +} // namespace mozilla + +inline PseudoStack* mozilla_get_pseudo_stack(void) +{ + if (!stack_key_initialized) + return nullptr; + return tlsPseudoStack.get(); +} + +inline void* mozilla_sampler_call_enter(const char *aInfo, + js::ProfileEntry::Category aCategory, void *aFrameAddress, bool aCopy, uint32_t line) +{ + // check if we've been initialized to avoid calling pthread_getspecific + // with a null tlsStack which will return undefined results. + if (!stack_key_initialized) + return nullptr; + + PseudoStack *stack = tlsPseudoStack.get(); + // we can't infer whether 'stack' has been initialized + // based on the value of stack_key_intiailized because + // 'stack' is only intialized when a thread is being + // profiled. + if (!stack) { + return stack; + } + stack->push(aInfo, aCategory, aFrameAddress, aCopy, line); + + // The handle is meant to support future changes + // but for now it is simply use to save a call to + // pthread_getspecific on exit. It also supports the + // case where the sampler is initialized between + // enter and exit. + return stack; +} + +inline void mozilla_sampler_call_exit(void *aHandle) +{ + if (!aHandle) + return; + + PseudoStack *stack = (PseudoStack*)aHandle; + stack->popAndMaybeDelete(); +} + +void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload); + +static inline +void profiler_log(const char *str) +{ + profiler_tracing("log", str, TRACING_EVENT); +} + +static inline +void profiler_log(const char *fmt, va_list args) +{ + mozilla_sampler_log(fmt, args); +} + +#endif /* ndef TOOLS_SPS_SAMPLER_H_ */ diff --git a/tools/profiler/public/ProfileGatherer.h b/tools/profiler/public/ProfileGatherer.h new file mode 100644 index 000000000..4e39a4f5c --- /dev/null +++ b/tools/profiler/public/ProfileGatherer.h @@ -0,0 +1,42 @@ +/* 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 MOZ_PROFILE_GATHERER_H +#define MOZ_PROFILE_GATHERER_H + +#include "mozilla/dom/Promise.h" + +class GeckoSampler; + +namespace mozilla { + +class ProfileGatherer final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit ProfileGatherer(GeckoSampler* aTicker); + void WillGatherOOPProfile(); + void GatheredOOPProfile(); + void Start(double aSinceTime, mozilla::dom::Promise* aPromise); + void Cancel(); + void OOPExitProfile(const nsCString& aProfile); + +private: + ~ProfileGatherer() {}; + void Finish(); + void Reset(); + + nsTArray<nsCString> mExitProfiles; + RefPtr<mozilla::dom::Promise> mPromise; + GeckoSampler* mTicker; + double mSinceTime; + uint32_t mPendingProfiles; + bool mGathering; +}; + +} // namespace mozilla + +#endif diff --git a/tools/profiler/public/ProfilerBacktrace.h b/tools/profiler/public/ProfilerBacktrace.h new file mode 100644 index 000000000..bcaab3563 --- /dev/null +++ b/tools/profiler/public/ProfilerBacktrace.h @@ -0,0 +1,36 @@ +/* -*- 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 __PROFILER_BACKTRACE_H +#define __PROFILER_BACKTRACE_H + +class SyncProfile; +class SpliceableJSONWriter; +class UniqueStacks; + +class ProfilerBacktrace +{ +public: + explicit ProfilerBacktrace(SyncProfile* aProfile); + ~ProfilerBacktrace(); + + // ProfilerBacktraces' stacks are deduplicated in the context of the + // profile that contains the backtrace as a marker payload. + // + // That is, markers that contain backtraces should not need their own stack, + // frame, and string tables. They should instead reuse their parent + // profile's tables. + void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks); + +private: + ProfilerBacktrace(const ProfilerBacktrace&); + ProfilerBacktrace& operator=(const ProfilerBacktrace&); + + SyncProfile* mProfile; +}; + +#endif // __PROFILER_BACKTRACE_H + diff --git a/tools/profiler/public/ProfilerMarkers.h b/tools/profiler/public/ProfilerMarkers.h new file mode 100644 index 000000000..29711f210 --- /dev/null +++ b/tools/profiler/public/ProfilerMarkers.h @@ -0,0 +1,193 @@ +/* -*- 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 PROFILER_MARKERS_H +#define PROFILER_MARKERS_H + +#include "mozilla/TimeStamp.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace layers { +class Layer; +} // namespace layers +} // namespace mozilla + +class SpliceableJSONWriter; +class UniqueStacks; + +/** + * This is an abstract object that can be implied to supply + * data to be attached with a profiler marker. Most data inserted + * into a profile is stored in a circular buffer. This buffer + * typically wraps around and overwrites most entries. Because + * of this, this structure is designed to defer the work of + * prepare the payload only when 'preparePayload' is called. + * + * Note when implementing that this object is typically constructed + * on a particular thread but 'preparePayload' and the destructor + * is called from the main thread. + */ +class ProfilerMarkerPayload +{ +public: + /** + * ProfilerMarkerPayload takes ownership of aStack + */ + explicit ProfilerMarkerPayload(ProfilerBacktrace* aStack = nullptr); + ProfilerMarkerPayload(const mozilla::TimeStamp& aStartTime, + const mozilla::TimeStamp& aEndTime, + ProfilerBacktrace* aStack = nullptr); + + /** + * Called from the main thread + */ + virtual ~ProfilerMarkerPayload(); + + /** + * Called from the main thread + */ + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) = 0; + + mozilla::TimeStamp GetStartTime() const { return mStartTime; } + +protected: + /** + * Called from the main thread + */ + void streamCommonProps(const char* aMarkerType, SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks); + + void SetStack(ProfilerBacktrace* aStack) { mStack = aStack; } + +private: + mozilla::TimeStamp mStartTime; + mozilla::TimeStamp mEndTime; + ProfilerBacktrace* mStack; +}; + +class ProfilerMarkerTracing : public ProfilerMarkerPayload +{ +public: + ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData); + ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData, ProfilerBacktrace* aCause); + + const char *GetCategory() const { return mCategory; } + TracingMetadata GetMetaData() const { return mMetaData; } + + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) override; + +private: + const char *mCategory; + TracingMetadata mMetaData; +}; + + +#ifndef SPS_STANDALONE +#include "gfxASurface.h" +class ProfilerMarkerImagePayload : public ProfilerMarkerPayload +{ +public: + explicit ProfilerMarkerImagePayload(gfxASurface *aImg); + + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) override; + +private: + RefPtr<gfxASurface> mImg; +}; + +class IOMarkerPayload : public ProfilerMarkerPayload +{ +public: + IOMarkerPayload(const char* aSource, const char* aFilename, const mozilla::TimeStamp& aStartTime, + const mozilla::TimeStamp& aEndTime, + ProfilerBacktrace* aStack); + ~IOMarkerPayload(); + + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) override; + +private: + const char* mSource; + char* mFilename; +}; + +/** + * Contains the translation applied to a 2d layer so we can + * track the layer position at each frame. + */ +class LayerTranslationPayload : public ProfilerMarkerPayload +{ +public: + LayerTranslationPayload(mozilla::layers::Layer* aLayer, + mozilla::gfx::Point aPoint); + + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) override; + +private: + mozilla::layers::Layer* mLayer; + mozilla::gfx::Point mPoint; +}; + +#include "Units.h" // For ScreenIntPoint + +/** + * Tracks when touch events are processed by gecko, not when + * the touch actually occured in gonk/android. + */ +class TouchDataPayload : public ProfilerMarkerPayload +{ +public: + explicit TouchDataPayload(const mozilla::ScreenIntPoint& aPoint); + virtual ~TouchDataPayload() {} + + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) override; + +private: + mozilla::ScreenIntPoint mPoint; +}; + +/** + * Tracks when a vsync occurs according to the HardwareComposer. + */ +class VsyncPayload : public ProfilerMarkerPayload +{ +public: + explicit VsyncPayload(mozilla::TimeStamp aVsyncTimestamp); + virtual ~VsyncPayload() {} + + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) override; + +private: + mozilla::TimeStamp mVsyncTimestamp; +}; + +class GPUMarkerPayload : public ProfilerMarkerPayload +{ +public: + GPUMarkerPayload(const mozilla::TimeStamp& aCpuTimeStart, + const mozilla::TimeStamp& aCpuTimeEnd, + uint64_t aGpuTimeStart, + uint64_t aGpuTimeEnd); + ~GPUMarkerPayload() {} + + virtual void StreamPayload(SpliceableJSONWriter& aWriter, + UniqueStacks& aUniqueStacks) override; + +private: + mozilla::TimeStamp mCpuTimeStart; + mozilla::TimeStamp mCpuTimeEnd; + uint64_t mGpuTimeStart; + uint64_t mGpuTimeEnd; +}; +#endif + +#endif // PROFILER_MARKERS_H diff --git a/tools/profiler/public/PseudoStack.h b/tools/profiler/public/PseudoStack.h new file mode 100644 index 000000000..f9e3836ea --- /dev/null +++ b/tools/profiler/public/PseudoStack.h @@ -0,0 +1,469 @@ +/* -*- 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 PROFILER_PSEUDO_STACK_H_ +#define PROFILER_PSEUDO_STACK_H_ + +#include "mozilla/ArrayUtils.h" +#include <stdint.h> +#include "js/ProfilingStack.h" +#include <stdlib.h> +#include "mozilla/Atomics.h" +#ifndef SPS_STANDALONE +#include "nsISupportsImpl.h" +#endif + +/* we duplicate this code here to avoid header dependencies + * which make it more difficult to include in other places */ +#if defined(_M_X64) || defined(__x86_64__) +#define V8_HOST_ARCH_X64 1 +#elif defined(_M_IX86) || defined(__i386__) || defined(__i386) +#define V8_HOST_ARCH_IA32 1 +#elif defined(__ARMEL__) +#define V8_HOST_ARCH_ARM 1 +#else +#warning Please add support for your architecture in chromium_types.h +#endif + +// STORE_SEQUENCER: Because signals can interrupt our profile modification +// we need to make stores are not re-ordered by the compiler +// or hardware to make sure the profile is consistent at +// every point the signal can fire. +#ifdef V8_HOST_ARCH_ARM +// TODO Is there something cheaper that will prevent +// memory stores from being reordered + +typedef void (*LinuxKernelMemoryBarrierFunc)(void); +LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) = + (LinuxKernelMemoryBarrierFunc) 0xffff0fa0; + +# define STORE_SEQUENCER() pLinuxKernelMemoryBarrier() +#elif defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_X64) +# if defined(_MSC_VER) +# include <intrin.h> +# define STORE_SEQUENCER() _ReadWriteBarrier(); +# elif defined(__INTEL_COMPILER) +# define STORE_SEQUENCER() __memory_barrier(); +# elif __GNUC__ +# define STORE_SEQUENCER() asm volatile("" ::: "memory"); +# else +# error "Memory clobber not supported for your compiler." +# endif +#else +# error "Memory clobber not supported for your platform." +#endif + +// We can't include <algorithm> because it causes issues on OS X, so we use +// our own min function. +static inline uint32_t sMin(uint32_t l, uint32_t r) { + return l < r ? l : r; +} + +// A stack entry exists to allow the JS engine to inform SPS of the current +// backtrace, but also to instrument particular points in C++ in case stack +// walking is not available on the platform we are running on. +// +// Each entry has a descriptive string, a relevant stack address, and some extra +// information the JS engine might want to inform SPS of. This class inherits +// from the JS engine's version of the entry to ensure that the size and layout +// of the two representations are consistent. +class StackEntry : public js::ProfileEntry +{ +}; + +class ProfilerMarkerPayload; +template<typename T> +class ProfilerLinkedList; +class SpliceableJSONWriter; +class UniqueStacks; + +class ProfilerMarker { + friend class ProfilerLinkedList<ProfilerMarker>; +public: + explicit ProfilerMarker(const char* aMarkerName, + ProfilerMarkerPayload* aPayload = nullptr, + double aTime = 0); + + ~ProfilerMarker(); + + const char* GetMarkerName() const { + return mMarkerName; + } + + void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks) const; + + void SetGeneration(uint32_t aGenID); + + bool HasExpired(uint32_t aGenID) const { + return mGenID + 2 <= aGenID; + } + + double GetTime() const; + +private: + char* mMarkerName; + ProfilerMarkerPayload* mPayload; + ProfilerMarker* mNext; + double mTime; + uint32_t mGenID; +}; + +template<typename T> +class ProfilerLinkedList { +public: + ProfilerLinkedList() + : mHead(nullptr) + , mTail(nullptr) + {} + + void insert(T* elem) + { + if (!mTail) { + mHead = elem; + mTail = elem; + } else { + mTail->mNext = elem; + mTail = elem; + } + elem->mNext = nullptr; + } + + T* popHead() + { + if (!mHead) { + MOZ_ASSERT(false); + return nullptr; + } + + T* head = mHead; + + mHead = head->mNext; + if (!mHead) { + mTail = nullptr; + } + + return head; + } + + const T* peek() { + return mHead; + } + +private: + T* mHead; + T* mTail; +}; + +typedef ProfilerLinkedList<ProfilerMarker> ProfilerMarkerLinkedList; + +template<typename T> +class ProfilerSignalSafeLinkedList { +public: + ProfilerSignalSafeLinkedList() + : mSignalLock(false) + {} + + ~ProfilerSignalSafeLinkedList() + { + if (mSignalLock) { + // Some thread is modifying the list. We should only be released on that + // thread. + abort(); + } + + while (mList.peek()) { + delete mList.popHead(); + } + } + + // Insert an item into the list. + // Must only be called from the owning thread. + // Must not be called while the list from accessList() is being accessed. + // In the profiler, we ensure that by interrupting the profiled thread + // (which is the one that owns this list and calls insert() on it) until + // we're done reading the list from the signal handler. + void insert(T* aElement) { + MOZ_ASSERT(aElement); + + mSignalLock = true; + STORE_SEQUENCER(); + + mList.insert(aElement); + + STORE_SEQUENCER(); + mSignalLock = false; + } + + // Called within signal, from any thread, possibly while insert() is in the + // middle of modifying the list (on the owning thread). Will return null if + // that is the case. + // Function must be reentrant. + ProfilerLinkedList<T>* accessList() + { + if (mSignalLock) { + return nullptr; + } + return &mList; + } + +private: + ProfilerLinkedList<T> mList; + + // If this is set, then it's not safe to read the list because its contents + // are being changed. + volatile bool mSignalLock; +}; + +// Stub eventMarker function for js-engine event generation. +void ProfilerJSEventMarker(const char *event); + +// the PseudoStack members are read by signal +// handlers, so the mutation of them needs to be signal-safe. +struct PseudoStack +{ +public: + // Create a new PseudoStack and acquire a reference to it. + static PseudoStack *create() + { + return new PseudoStack(); + } + + // This is called on every profiler restart. Put things that should happen at that time here. + void reinitializeOnResume() { + // This is needed to cause an initial sample to be taken from sleeping threads. Otherwise sleeping + // threads would not have any samples to copy forward while sleeping. + mSleepId++; + } + + void addMarker(const char* aMarkerStr, ProfilerMarkerPayload* aPayload, double aTime) + { + ProfilerMarker* marker = new ProfilerMarker(aMarkerStr, aPayload, aTime); + mPendingMarkers.insert(marker); + } + + // called within signal. Function must be reentrant + ProfilerMarkerLinkedList* getPendingMarkers() + { + // The profiled thread is interrupted, so we can access the list safely. + // Unless the profiled thread was in the middle of changing the list when + // we interrupted it - in that case, accessList() will return null. + return mPendingMarkers.accessList(); + } + + void push(const char *aName, js::ProfileEntry::Category aCategory, uint32_t line) + { + push(aName, aCategory, nullptr, false, line); + } + + void push(const char *aName, js::ProfileEntry::Category aCategory, + void *aStackAddress, bool aCopy, uint32_t line) + { + if (size_t(mStackPointer) >= mozilla::ArrayLength(mStack)) { + mStackPointer++; + return; + } + + // In order to ensure this object is kept alive while it is + // active, we acquire a reference at the outermost push. This is + // released by the corresponding pop. + if (mStackPointer == 0) { + ref(); + } + + volatile StackEntry &entry = mStack[mStackPointer]; + + // Make sure we increment the pointer after the name has + // been written such that mStack is always consistent. + entry.initCppFrame(aStackAddress, line); + entry.setLabel(aName); + MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY); + entry.setCategory(aCategory); + + // Track if mLabel needs a copy. + if (aCopy) + entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY); + else + entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY); + + // Prevent the optimizer from re-ordering these instructions + STORE_SEQUENCER(); + mStackPointer++; + } + + // Pop the stack. If the stack is empty and all other references to + // this PseudoStack have been dropped, then the PseudoStack is + // deleted and "false" is returned. Otherwise "true" is returned. + bool popAndMaybeDelete() + { + mStackPointer--; + if (mStackPointer == 0) { + // Release our self-owned reference count. See 'push'. + deref(); + return false; + } else { + return true; + } + } + bool isEmpty() + { + return mStackPointer == 0; + } + uint32_t stackSize() const + { + return sMin(mStackPointer, mozilla::sig_safe_t(mozilla::ArrayLength(mStack))); + } + + void sampleContext(JSContext* context) { +#ifndef SPS_STANDALONE + if (mContext && !context) { + // On JS shut down, flush the current buffer as stringifying JIT samples + // requires a live JSContext. + flushSamplerOnJSShutdown(); + } + + mContext = context; + + if (!context) { + return; + } + + static_assert(sizeof(mStack[0]) == sizeof(js::ProfileEntry), + "mStack must be binary compatible with js::ProfileEntry."); + js::SetContextProfilingStack(context, + (js::ProfileEntry*) mStack, + (uint32_t*) &mStackPointer, + (uint32_t) mozilla::ArrayLength(mStack)); + if (mStartJSSampling) + enableJSSampling(); +#endif + } +#ifndef SPS_STANDALONE + void enableJSSampling() { + if (mContext) { + js::EnableContextProfilingStack(mContext, true); + js::RegisterContextProfilingEventMarker(mContext, &ProfilerJSEventMarker); + mStartJSSampling = false; + } else { + mStartJSSampling = true; + } + } + void jsOperationCallback() { + if (mStartJSSampling) + enableJSSampling(); + } + void disableJSSampling() { + mStartJSSampling = false; + if (mContext) + js::EnableContextProfilingStack(mContext, false); + } +#endif + + // Keep a list of active checkpoints + StackEntry volatile mStack[1024]; + private: + + // A PseudoStack can only be created via the "create" method. + PseudoStack() + : mStackPointer(0) + , mSleepId(0) + , mSleepIdObserved(0) + , mSleeping(false) + , mRefCnt(1) +#ifndef SPS_STANDALONE + , mContext(nullptr) +#endif + , mStartJSSampling(false) + , mPrivacyMode(false) + { + MOZ_COUNT_CTOR(PseudoStack); + } + + // A PseudoStack can only be deleted via deref. + ~PseudoStack() { + MOZ_COUNT_DTOR(PseudoStack); + if (mStackPointer != 0) { + // We're releasing the pseudostack while it's still in use. + // The label macros keep a non ref counted reference to the + // stack to avoid a TLS. If these are not all cleared we will + // get a use-after-free so better to crash now. + abort(); + } + } + + // No copying. + PseudoStack(const PseudoStack&) = delete; + void operator=(const PseudoStack&) = delete; + + void flushSamplerOnJSShutdown(); + + // Keep a list of pending markers that must be moved + // to the circular buffer + ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers; + // This may exceed the length of mStack, so instead use the stackSize() method + // to determine the number of valid samples in mStack + mozilla::sig_safe_t mStackPointer; + // Incremented at every sleep/wake up of the thread + int mSleepId; + // Previous id observed. If this is not the same as mSleepId, this thread is not sleeping in the same place any more + mozilla::Atomic<int> mSleepIdObserved; + // Keeps tack of whether the thread is sleeping or not (1 when sleeping 0 when awake) + mozilla::Atomic<int> mSleeping; + // This class is reference counted because it must be kept alive by + // the ThreadInfo, by the reference from tlsPseudoStack, and by the + // current thread when callbacks are in progress. + mozilla::Atomic<int> mRefCnt; + + public: +#ifndef SPS_STANDALONE + // The context which is being sampled + JSContext *mContext; +#endif + // Start JS Profiling when possible + bool mStartJSSampling; + bool mPrivacyMode; + + enum SleepState {NOT_SLEEPING, SLEEPING_FIRST, SLEEPING_AGAIN}; + + // The first time this is called per sleep cycle we return SLEEPING_FIRST + // and any other subsequent call within the same sleep cycle we return SLEEPING_AGAIN + SleepState observeSleeping() { + if (mSleeping != 0) { + if (mSleepIdObserved == mSleepId) { + return SLEEPING_AGAIN; + } else { + mSleepIdObserved = mSleepId; + return SLEEPING_FIRST; + } + } else { + return NOT_SLEEPING; + } + } + + + // Call this whenever the current thread sleeps or wakes up + // Calling setSleeping with the same value twice in a row is an error + void setSleeping(int sleeping) { + MOZ_ASSERT(mSleeping != sleeping); + mSleepId++; + mSleeping = sleeping; + } + + bool isSleeping() { + return !!mSleeping; + } + + void ref() { + ++mRefCnt; + } + + void deref() { + int newValue = --mRefCnt; + if (newValue == 0) { + delete this; + } + } +}; + +#endif diff --git a/tools/profiler/public/shared-libraries.h b/tools/profiler/public/shared-libraries.h new file mode 100644 index 000000000..b02a1fb08 --- /dev/null +++ b/tools/profiler/public/shared-libraries.h @@ -0,0 +1,137 @@ +/* -*- 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_ENABLE_PROFILER_SPS +#error This header does not have a useful implementation on your platform! +#endif + +#include <algorithm> +#include <vector> +#include <string> +#include <stdlib.h> +#include <stdint.h> +#ifndef SPS_STANDALONE +#include <nsID.h> +#endif + +class SharedLibrary { +public: + + SharedLibrary(uintptr_t aStart, + uintptr_t aEnd, + uintptr_t aOffset, + const std::string& aBreakpadId, + const std::string& aName) + : mStart(aStart) + , mEnd(aEnd) + , mOffset(aOffset) + , mBreakpadId(aBreakpadId) + , mName(aName) + {} + + SharedLibrary(const SharedLibrary& aEntry) + : mStart(aEntry.mStart) + , mEnd(aEntry.mEnd) + , mOffset(aEntry.mOffset) + , mBreakpadId(aEntry.mBreakpadId) + , mName(aEntry.mName) + {} + + 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; + mName = aEntry.mName; + return *this; + } + + bool operator==(const SharedLibrary& other) const + { + return (mStart == other.mStart) && + (mEnd == other.mEnd) && + (mOffset == other.mOffset) && + (mName == other.mName) && + (mBreakpadId == other.mBreakpadId); + } + + uintptr_t GetStart() const { return mStart; } + uintptr_t GetEnd() const { return mEnd; } + uintptr_t GetOffset() const { return mOffset; } + const std::string &GetBreakpadId() const { return mBreakpadId; } + const std::string &GetName() const { return mName; } + +private: + SharedLibrary() {} + + uintptr_t mStart; + uintptr_t mEnd; + uintptr_t mOffset; + std::string mBreakpadId; + std::string mName; +}; + +static bool +CompareAddresses(const SharedLibrary& first, const SharedLibrary& second) +{ + return first.GetStart() < second.GetStart(); +} + +class SharedLibraryInfo { +public: + static SharedLibraryInfo GetInfoForSelf(); + SharedLibraryInfo() {} + + void AddSharedLibrary(SharedLibrary entry) + { + mEntries.push_back(entry); + } + + const SharedLibrary& GetEntry(size_t i) const + { + 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 |