mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 04:15:43 +00:00
Bug 851748 - Merge TableTicker1+2. r=jseward
--HG-- rename : tools/profiler/TableTicker2.cpp => tools/profiler/BreakpadSampler.cpp rename : tools/profiler/ProfileEntry2.cpp => tools/profiler/ProfileEntry.cpp rename : tools/profiler/ProfileEntry2.h => tools/profiler/ProfileEntry.h
This commit is contained in:
parent
3dcc415cf6
commit
de8429e6a6
303
tools/profiler/BreakpadSampler.cpp
Normal file
303
tools/profiler/BreakpadSampler.cpp
Normal file
@ -0,0 +1,303 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
// System
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#if defined(ANDROID)
|
||||
# include "android-signal-defs.h"
|
||||
#endif
|
||||
|
||||
// Profiler
|
||||
#include "PlatformMacros.h"
|
||||
#include "GeckoProfilerImpl.h"
|
||||
#include "platform.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "prenv.h"
|
||||
#include "shared-libraries.h"
|
||||
#include "mozilla/StackWalk.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "SaveProfileTask.h"
|
||||
#include "UnwinderThread2.h"
|
||||
#include "TableTicker.h"
|
||||
|
||||
// JSON
|
||||
#include "JSObjectBuilder.h"
|
||||
#include "nsIJSRuntimeService.h"
|
||||
|
||||
// Meta
|
||||
#include "nsXPCOM.h"
|
||||
#include "nsXPCOMCID.h"
|
||||
#include "nsIHttpProtocolHandler.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsIXULAppInfo.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "mozilla/Services.h"
|
||||
|
||||
// JS
|
||||
#include "jsdbgapi.h"
|
||||
|
||||
// This file's exports are listed in GeckoProfilerImpl.h.
|
||||
|
||||
/* These will be set to something sensible before we take the first
|
||||
sample. */
|
||||
UnwMode sUnwindMode = UnwINVALID;
|
||||
int sUnwindInterval = 0;
|
||||
|
||||
using std::string;
|
||||
using namespace mozilla;
|
||||
|
||||
#if _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN take samples.
|
||||
// Everything in this section RUNS IN SIGHANDLER CONTEXT
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
static
|
||||
void genProfileEntry(/*MODIFIED*/UnwinderThreadBuffer* utb,
|
||||
volatile StackEntry &entry,
|
||||
PseudoStack *stack, void *lastpc)
|
||||
{
|
||||
int lineno = -1;
|
||||
|
||||
// Add a pseudostack-entry start label
|
||||
utb__addEntry( utb, ProfileEntry('h', 'P') );
|
||||
// And the SP value, if it is non-zero
|
||||
if (entry.stackAddress() != 0) {
|
||||
utb__addEntry( utb, ProfileEntry('S', entry.stackAddress()) );
|
||||
}
|
||||
|
||||
// First entry has tagName 's' (start)
|
||||
// Check for magic pointer bit 1 to indicate copy
|
||||
const char* sampleLabel = entry.label();
|
||||
if (entry.isCopyLabel()) {
|
||||
// Store the string using 1 or more 'd' (dynamic) tags
|
||||
// that will happen to the preceding tag
|
||||
|
||||
utb__addEntry( utb, ProfileEntry('c', "") );
|
||||
// Add one to store the null termination
|
||||
size_t strLen = strlen(sampleLabel) + 1;
|
||||
for (size_t j = 0; j < strLen;) {
|
||||
// Store as many characters in the void* as the platform allows
|
||||
char text[sizeof(void*)];
|
||||
for (size_t pos = 0; pos < sizeof(void*) && j+pos < strLen; pos++) {
|
||||
text[pos] = sampleLabel[j+pos];
|
||||
}
|
||||
j += sizeof(void*)/sizeof(char);
|
||||
// Cast to *((void**) to pass the text data to a void*
|
||||
utb__addEntry( utb, ProfileEntry('d', *((void**)(&text[0]))) );
|
||||
}
|
||||
if (entry.js()) {
|
||||
if (!entry.pc()) {
|
||||
// The JIT only allows the top-most entry to have a NULL pc
|
||||
MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]);
|
||||
// If stack-walking was disabled, then that's just unfortunate
|
||||
if (lastpc) {
|
||||
jsbytecode *jspc = js::ProfilingGetPC(stack->mRuntime, entry.script(),
|
||||
lastpc);
|
||||
if (jspc) {
|
||||
lineno = JS_PCToLineNumber(NULL, entry.script(), jspc);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lineno = JS_PCToLineNumber(NULL, entry.script(), entry.pc());
|
||||
}
|
||||
} else {
|
||||
lineno = entry.line();
|
||||
}
|
||||
} else {
|
||||
utb__addEntry( utb, ProfileEntry('c', sampleLabel) );
|
||||
lineno = entry.line();
|
||||
}
|
||||
if (lineno != -1) {
|
||||
utb__addEntry( utb, ProfileEntry('n', lineno) );
|
||||
}
|
||||
|
||||
// Add a pseudostack-entry end label
|
||||
utb__addEntry( utb, ProfileEntry('h', 'Q') );
|
||||
}
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
// Generate pseudo-backtrace entries and put them in |utb|, with
|
||||
// the order outermost frame first.
|
||||
void genPseudoBacktraceEntries(/*MODIFIED*/UnwinderThreadBuffer* utb,
|
||||
PseudoStack *aStack, TickSample *sample)
|
||||
{
|
||||
// Call genProfileEntry to generate tags for each profile
|
||||
// entry. Each entry will be bounded by a 'h' 'P' tag to
|
||||
// mark the start and a 'h' 'Q' tag to mark the end.
|
||||
uint32_t nInStack = aStack->stackSize();
|
||||
for (uint32_t i = 0; i < nInStack; i++) {
|
||||
genProfileEntry(utb, aStack->mStack[i], aStack, nullptr);
|
||||
}
|
||||
# ifdef ENABLE_SPS_LEAF_DATA
|
||||
if (sample) {
|
||||
utb__addEntry( utb, ProfileEntry('l', (void*)sample->pc) );
|
||||
# ifdef ENABLE_ARM_LR_SAVING
|
||||
utb__addEntry( utb, ProfileEntry('L', (void*)sample->lr) );
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
void BreakpadSampler::Tick(TickSample* sample)
|
||||
{
|
||||
/* Get hold of an empty inter-thread buffer into which to park
|
||||
the ProfileEntries for this sample. */
|
||||
UnwinderThreadBuffer* utb = uwt__acquire_empty_buffer();
|
||||
|
||||
/* This could fail, if no buffers are currently available, in which
|
||||
case we must give up right away. We cannot wait for a buffer to
|
||||
become available, as that risks deadlock. */
|
||||
if (!utb)
|
||||
return;
|
||||
|
||||
/* Manufacture the ProfileEntries that we will give to the unwinder
|
||||
thread, and park them in |utb|. */
|
||||
|
||||
// Marker(s) come before the sample
|
||||
PseudoStack* stack = mPrimaryThreadProfile.GetPseudoStack();
|
||||
for (int i = 0; stack->getMarker(i) != NULL; i++) {
|
||||
utb__addEntry( utb, ProfileEntry('m', stack->getMarker(i)) );
|
||||
}
|
||||
stack->mQueueClearMarker = true;
|
||||
|
||||
bool recordSample = true;
|
||||
if (mJankOnly) {
|
||||
// if we are on a different event we can discard any temporary samples
|
||||
// we've kept around
|
||||
if (sLastSampledEventGeneration != sCurrentEventGeneration) {
|
||||
// XXX: we also probably want to add an entry to the profile to help
|
||||
// distinguish which samples are part of the same event. That, or record
|
||||
// the event generation in each sample
|
||||
mPrimaryThreadProfile.erase();
|
||||
}
|
||||
sLastSampledEventGeneration = sCurrentEventGeneration;
|
||||
|
||||
recordSample = false;
|
||||
// only record the events when we have a we haven't seen a tracer
|
||||
// event for 100ms
|
||||
if (!sLastTracerEvent.IsNull()) {
|
||||
TimeDuration delta = sample->timestamp - sLastTracerEvent;
|
||||
if (delta.ToMilliseconds() > 100.0) {
|
||||
recordSample = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JRS 2012-Sept-27: this logic used to involve mUseStackWalk.
|
||||
// That should be reinstated, but for the moment, use the
|
||||
// settings in sUnwindMode and sUnwindInterval.
|
||||
// Add a native-backtrace request, or add pseudo backtrace entries,
|
||||
// or both.
|
||||
switch (sUnwindMode) {
|
||||
case UnwNATIVE: /* Native only */
|
||||
// add a "do native stack trace now" hint. This will be actioned
|
||||
// by the unwinder thread as it processes the entries in this
|
||||
// sample.
|
||||
utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'N'/*native-trace*/) );
|
||||
break;
|
||||
case UnwPSEUDO: /* Pseudo only */
|
||||
/* Add into |utb|, the pseudo backtrace entries */
|
||||
genPseudoBacktraceEntries(utb, stack, sample);
|
||||
break;
|
||||
case UnwCOMBINED: /* Both Native and Pseudo */
|
||||
utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'N'/*native-trace*/) );
|
||||
genPseudoBacktraceEntries(utb, stack, sample);
|
||||
break;
|
||||
case UnwINVALID:
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
if (recordSample) {
|
||||
// add a "flush now" hint
|
||||
utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'F'/*flush*/) );
|
||||
}
|
||||
|
||||
// Add any extras
|
||||
if (!sLastTracerEvent.IsNull() && sample) {
|
||||
TimeDuration delta = sample->timestamp - sLastTracerEvent;
|
||||
utb__addEntry( utb, ProfileEntry('r', delta.ToMilliseconds()) );
|
||||
}
|
||||
|
||||
if (sample) {
|
||||
TimeDuration delta = sample->timestamp - mStartTime;
|
||||
utb__addEntry( utb, ProfileEntry('t', delta.ToMilliseconds()) );
|
||||
}
|
||||
|
||||
if (sLastFrameNumber != sFrameNumber) {
|
||||
utb__addEntry( utb, ProfileEntry('f', sFrameNumber) );
|
||||
sLastFrameNumber = sFrameNumber;
|
||||
}
|
||||
|
||||
/* So now we have, in |utb|, the complete set of entries we want to
|
||||
push into the circular buffer. This may also include a 'h' 'F'
|
||||
entry, which is "flush now" hint, and/or a 'h' 'N' entry, which
|
||||
is a "generate a native backtrace and add it to the buffer right
|
||||
now" hint. Hand them off to the helper thread, together with
|
||||
stack and register context needed to do a native unwind, if that
|
||||
is currently enabled. */
|
||||
|
||||
/* If a native unwind has been requested, we'll start it off using
|
||||
the context obtained from the signal handler, to avoid the
|
||||
problem of having to unwind through the signal frame itself. */
|
||||
|
||||
/* On Linux and Android, the initial register state is in the
|
||||
supplied sample->context. But on MacOS it's not, so we have to
|
||||
fake it up here (sigh). */
|
||||
if (sUnwindMode == UnwNATIVE || sUnwindMode == UnwCOMBINED) {
|
||||
# if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||||
|| defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
|
||||
void* ucV = (void*)sample->context;
|
||||
# elif defined(SPS_PLAT_amd64_darwin)
|
||||
struct __darwin_mcontext64 mc;
|
||||
memset(&mc, 0, sizeof(mc));
|
||||
ucontext_t uc;
|
||||
memset(&uc, 0, sizeof(uc));
|
||||
uc.uc_mcontext = &mc;
|
||||
mc.__ss.__rip = (uint64_t)sample->pc;
|
||||
mc.__ss.__rsp = (uint64_t)sample->sp;
|
||||
mc.__ss.__rbp = (uint64_t)sample->fp;
|
||||
void* ucV = (void*)&uc;
|
||||
# elif defined(SPS_PLAT_x86_darwin)
|
||||
struct __darwin_mcontext32 mc;
|
||||
memset(&mc, 0, sizeof(mc));
|
||||
ucontext_t uc;
|
||||
memset(&uc, 0, sizeof(uc));
|
||||
uc.uc_mcontext = &mc;
|
||||
mc.__ss.__eip = (uint32_t)sample->pc;
|
||||
mc.__ss.__esp = (uint32_t)sample->sp;
|
||||
mc.__ss.__ebp = (uint32_t)sample->fp;
|
||||
void* ucV = (void*)&uc;
|
||||
# elif defined(SPS_OS_windows)
|
||||
/* Totally fake this up so it at least builds. No idea if we can
|
||||
even ever get here on Windows. */
|
||||
void* ucV = NULL;
|
||||
# else
|
||||
# error "Unsupported platform"
|
||||
# endif
|
||||
uwt__release_full_buffer(&mPrimaryThreadProfile, utb, ucV);
|
||||
} else {
|
||||
uwt__release_full_buffer(&mPrimaryThreadProfile, utb, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// END take samples
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -21,43 +21,30 @@ inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress =
|
||||
inline void mozilla_sampler_call_exit(void* handle);
|
||||
inline void mozilla_sampler_add_marker(const char *aInfo);
|
||||
|
||||
void mozilla_sampler_start1(int aEntries, int aInterval, const char** aFeatures,
|
||||
uint32_t aFeatureCount);
|
||||
void mozilla_sampler_start2(int aEntries, int aInterval, const char** aFeatures,
|
||||
void mozilla_sampler_start(int aEntries, int aInterval, const char** aFeatures,
|
||||
uint32_t aFeatureCount);
|
||||
|
||||
void mozilla_sampler_stop1();
|
||||
void mozilla_sampler_stop2();
|
||||
void mozilla_sampler_stop();
|
||||
|
||||
bool mozilla_sampler_is_active1();
|
||||
bool mozilla_sampler_is_active2();
|
||||
bool mozilla_sampler_is_active();
|
||||
|
||||
void mozilla_sampler_responsiveness1(const TimeStamp& time);
|
||||
void mozilla_sampler_responsiveness2(const TimeStamp& time);
|
||||
void mozilla_sampler_responsiveness(const TimeStamp& time);
|
||||
|
||||
void mozilla_sampler_frame_number1(int frameNumber);
|
||||
void mozilla_sampler_frame_number2(int frameNumber);
|
||||
void mozilla_sampler_frame_number(int frameNumber);
|
||||
|
||||
const double* mozilla_sampler_get_responsiveness1();
|
||||
const double* mozilla_sampler_get_responsiveness2();
|
||||
const double* mozilla_sampler_get_responsiveness();
|
||||
|
||||
void mozilla_sampler_save1();
|
||||
void mozilla_sampler_save2();
|
||||
void mozilla_sampler_save();
|
||||
|
||||
char* mozilla_sampler_get_profile1();
|
||||
char* mozilla_sampler_get_profile2();
|
||||
char* mozilla_sampler_get_profile();
|
||||
|
||||
JSObject *mozilla_sampler_get_profile_data1(JSContext *aCx);
|
||||
JSObject *mozilla_sampler_get_profile_data2(JSContext *aCx);
|
||||
JSObject *mozilla_sampler_get_profile_data(JSContext *aCx);
|
||||
|
||||
const char** mozilla_sampler_get_features1();
|
||||
const char** mozilla_sampler_get_features2();
|
||||
const char** mozilla_sampler_get_features();
|
||||
|
||||
void mozilla_sampler_init1();
|
||||
void mozilla_sampler_init2();
|
||||
void mozilla_sampler_init();
|
||||
|
||||
void mozilla_sampler_shutdown1();
|
||||
void mozilla_sampler_shutdown2();
|
||||
void mozilla_sampler_shutdown();
|
||||
|
||||
void mozilla_sampler_print_location1();
|
||||
void mozilla_sampler_print_location2();
|
||||
@ -65,12 +52,10 @@ void mozilla_sampler_print_location2();
|
||||
// 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_lock1();
|
||||
void mozilla_sampler_lock2();
|
||||
void mozilla_sampler_lock();
|
||||
|
||||
// Unlock the profiler, leaving it stopped and fires profiler-unlocked.
|
||||
void mozilla_sampler_unlock1();
|
||||
void mozilla_sampler_unlock2();
|
||||
void mozilla_sampler_unlock();
|
||||
|
||||
/* Returns true if env var SPS_NEW is set to anything, else false. */
|
||||
extern bool sps_version2();
|
||||
|
@ -38,6 +38,7 @@ class TableTicker;
|
||||
class JSCustomObject;
|
||||
|
||||
extern mozilla::ThreadLocal<PseudoStack *> tlsPseudoStack;
|
||||
extern mozilla::ThreadLocal<TableTicker *> tlsTicker;
|
||||
extern bool stack_key_initialized;
|
||||
|
||||
#ifndef SAMPLE_FUNCTION_NAME
|
||||
@ -53,112 +54,68 @@ extern bool stack_key_initialized;
|
||||
static inline
|
||||
void profiler_init()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
mozilla_sampler_init1();
|
||||
} else {
|
||||
mozilla_sampler_init2();
|
||||
}
|
||||
mozilla_sampler_init();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_shutdown()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
mozilla_sampler_shutdown1();
|
||||
} else {
|
||||
mozilla_sampler_shutdown2();
|
||||
}
|
||||
mozilla_sampler_shutdown();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_start(int aProfileEntries, int aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount)
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
mozilla_sampler_start1(aProfileEntries, aInterval, aFeatures, aFeatureCount);
|
||||
} else {
|
||||
mozilla_sampler_start2(aProfileEntries, aInterval, aFeatures, aFeatureCount);
|
||||
}
|
||||
mozilla_sampler_start(aProfileEntries, aInterval, aFeatures, aFeatureCount);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_stop()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
mozilla_sampler_stop1();
|
||||
} else {
|
||||
mozilla_sampler_stop2();
|
||||
}
|
||||
mozilla_sampler_stop();
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_is_active()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_is_active1();
|
||||
} else {
|
||||
return mozilla_sampler_is_active2();
|
||||
}
|
||||
return mozilla_sampler_is_active();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_responsiveness(const TimeStamp& aTime)
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
mozilla_sampler_responsiveness1(aTime);
|
||||
} else {
|
||||
mozilla_sampler_responsiveness2(aTime);
|
||||
}
|
||||
mozilla_sampler_responsiveness(aTime);
|
||||
}
|
||||
|
||||
static inline
|
||||
const double* profiler_get_responsiveness()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_get_responsiveness1();
|
||||
} else {
|
||||
return mozilla_sampler_get_responsiveness2();
|
||||
}
|
||||
return mozilla_sampler_get_responsiveness();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_set_frame_number(int frameNumber)
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_frame_number1(frameNumber);
|
||||
} else {
|
||||
return mozilla_sampler_frame_number2(frameNumber);
|
||||
}
|
||||
return mozilla_sampler_frame_number(frameNumber);
|
||||
}
|
||||
|
||||
static inline
|
||||
char* profiler_get_profile()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_get_profile1();
|
||||
} else {
|
||||
return mozilla_sampler_get_profile2();
|
||||
}
|
||||
return mozilla_sampler_get_profile();
|
||||
}
|
||||
|
||||
static inline
|
||||
JSObject* profiler_get_profile_jsobject(JSContext* aCx)
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_get_profile_data1(aCx);
|
||||
} else {
|
||||
return mozilla_sampler_get_profile_data2(aCx);
|
||||
}
|
||||
return mozilla_sampler_get_profile_data(aCx);
|
||||
}
|
||||
|
||||
static inline
|
||||
const char** profiler_get_features()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_get_features1();
|
||||
} else {
|
||||
return mozilla_sampler_get_features2();
|
||||
}
|
||||
return mozilla_sampler_get_features();
|
||||
}
|
||||
|
||||
static inline
|
||||
@ -174,21 +131,13 @@ void profiler_print_location()
|
||||
static inline
|
||||
void profiler_lock()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_lock1();
|
||||
} else {
|
||||
return mozilla_sampler_lock2();
|
||||
}
|
||||
return mozilla_sampler_lock();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_unlock()
|
||||
{
|
||||
if (!sps_version2()) {
|
||||
return mozilla_sampler_unlock1();
|
||||
} else {
|
||||
return mozilla_sampler_unlock2();
|
||||
}
|
||||
return mozilla_sampler_unlock();
|
||||
}
|
||||
|
||||
// we want the class and function name but can't easily get that using preprocessor macros
|
||||
|
@ -44,12 +44,13 @@ FAIL_ON_WARNINGS = 1
|
||||
endif # !_MSC_VER
|
||||
|
||||
CPPSRCS = \
|
||||
platform.cpp \
|
||||
nsProfilerFactory.cpp \
|
||||
nsProfiler.cpp \
|
||||
TableTicker.cpp \
|
||||
TableTicker2.cpp \
|
||||
BreakpadSampler.cpp \
|
||||
UnwinderThread2.cpp \
|
||||
ProfileEntry2.cpp \
|
||||
ProfileEntry.cpp \
|
||||
local_debug_info_symbolizer.cc \
|
||||
JSObjectBuilder.cpp \
|
||||
JSCustomObjectBuilder.cpp \
|
||||
|
@ -10,76 +10,77 @@
|
||||
|
||||
// JSON
|
||||
#include "JSObjectBuilder.h"
|
||||
#include "JSCustomObjectBuilder.h"
|
||||
|
||||
// Self
|
||||
#include "ProfileEntry2.h"
|
||||
#include "ProfileEntry.h"
|
||||
|
||||
#if _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN ProfileEntry2
|
||||
// BEGIN ProfileEntry
|
||||
|
||||
ProfileEntry2::ProfileEntry2()
|
||||
ProfileEntry::ProfileEntry()
|
||||
: mTagData(NULL)
|
||||
, mTagName(0)
|
||||
{ }
|
||||
|
||||
// aTagData must not need release (i.e. be a string from the text segment)
|
||||
ProfileEntry2::ProfileEntry2(char aTagName, const char *aTagData)
|
||||
ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
|
||||
: mTagData(aTagData)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry2::ProfileEntry2(char aTagName, void *aTagPtr)
|
||||
ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
|
||||
: mTagPtr(aTagPtr)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry2::ProfileEntry2(char aTagName, double aTagFloat)
|
||||
ProfileEntry::ProfileEntry(char aTagName, double aTagFloat)
|
||||
: mTagFloat(aTagFloat)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry2::ProfileEntry2(char aTagName, uintptr_t aTagOffset)
|
||||
ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
|
||||
: mTagOffset(aTagOffset)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry2::ProfileEntry2(char aTagName, Address aTagAddress)
|
||||
ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
|
||||
: mTagAddress(aTagAddress)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry2::ProfileEntry2(char aTagName, int aTagLine)
|
||||
ProfileEntry::ProfileEntry(char aTagName, int aTagLine)
|
||||
: mTagLine(aTagLine)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry2::ProfileEntry2(char aTagName, char aTagChar)
|
||||
ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
|
||||
: mTagChar(aTagChar)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
bool ProfileEntry2::is_ent_hint(char hintChar) {
|
||||
bool ProfileEntry::is_ent_hint(char hintChar) {
|
||||
return mTagName == 'h' && mTagChar == hintChar;
|
||||
}
|
||||
|
||||
bool ProfileEntry2::is_ent_hint() {
|
||||
bool ProfileEntry::is_ent_hint() {
|
||||
return mTagName == 'h';
|
||||
}
|
||||
|
||||
bool ProfileEntry2::is_ent(char tagChar) {
|
||||
bool ProfileEntry::is_ent(char tagChar) {
|
||||
return mTagName == tagChar;
|
||||
}
|
||||
|
||||
void* ProfileEntry2::get_tagPtr() {
|
||||
void* ProfileEntry::get_tagPtr() {
|
||||
// No consistency checking. Oh well.
|
||||
return mTagPtr;
|
||||
}
|
||||
|
||||
void ProfileEntry2::log()
|
||||
void ProfileEntry::log()
|
||||
{
|
||||
// There is no compiler enforced mapping between tag chars
|
||||
// and union variant fields, so the following was derived
|
||||
@ -105,40 +106,58 @@ void ProfileEntry2::log()
|
||||
}
|
||||
}
|
||||
|
||||
// END ProfileEntry2
|
||||
std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry)
|
||||
{
|
||||
if (entry.mTagName == 'r' || entry.mTagName == 't') {
|
||||
stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
|
||||
} else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
|
||||
// Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
|
||||
// Additionally, stringstream seemed to be ignoring formatter flags.
|
||||
char tagBuff[1024];
|
||||
unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
|
||||
snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
|
||||
stream << tagBuff;
|
||||
} else if (entry.mTagName == 'd') {
|
||||
// TODO implement 'd' tag for text profile
|
||||
} else {
|
||||
stream << entry.mTagName << "-" << entry.mTagData << "\n";
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
// END ProfileEntry
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN ThreadProfile2
|
||||
// BEGIN ThreadProfile
|
||||
|
||||
#define PROFILE_MAX_ENTRY 100000
|
||||
#define DYNAMIC_MAX_STRING 512
|
||||
|
||||
ThreadProfile2::ThreadProfile2(int aEntrySize, PseudoStack *aStack)
|
||||
ThreadProfile::ThreadProfile(int aEntrySize, PseudoStack *aStack)
|
||||
: mWritePos(0)
|
||||
, mLastFlushPos(0)
|
||||
, mReadPos(0)
|
||||
, mEntrySize(aEntrySize)
|
||||
, mPseudoStack(aStack)
|
||||
, mMutex("ThreadProfile2::mMutex")
|
||||
, mMutex("ThreadProfile::mMutex")
|
||||
{
|
||||
mEntries = new ProfileEntry2[mEntrySize];
|
||||
mEntries = new ProfileEntry[mEntrySize];
|
||||
}
|
||||
|
||||
ThreadProfile2::~ThreadProfile2()
|
||||
ThreadProfile::~ThreadProfile()
|
||||
{
|
||||
delete[] mEntries;
|
||||
}
|
||||
|
||||
void ThreadProfile2::addTag(ProfileEntry2 aTag)
|
||||
void ThreadProfile::addTag(ProfileEntry aTag)
|
||||
{
|
||||
// Called from signal, call only reentrant functions
|
||||
mEntries[mWritePos] = aTag;
|
||||
mWritePos = (mWritePos + 1) % mEntrySize;
|
||||
if (mWritePos == mReadPos) {
|
||||
// Keep one slot open
|
||||
mEntries[mReadPos] = ProfileEntry2();
|
||||
mEntries[mReadPos] = ProfileEntry();
|
||||
mReadPos = (mReadPos + 1) % mEntrySize;
|
||||
}
|
||||
// we also need to move the flush pos to ensure we
|
||||
@ -149,7 +168,7 @@ void ThreadProfile2::addTag(ProfileEntry2 aTag)
|
||||
}
|
||||
|
||||
// flush the new entries
|
||||
void ThreadProfile2::flush()
|
||||
void ThreadProfile::flush()
|
||||
{
|
||||
mLastFlushPos = mWritePos;
|
||||
}
|
||||
@ -204,12 +223,12 @@ void ThreadProfile2::flush()
|
||||
// |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
|
||||
// |-----------------------------|
|
||||
|
||||
void ThreadProfile2::erase()
|
||||
void ThreadProfile::erase()
|
||||
{
|
||||
mWritePos = mLastFlushPos;
|
||||
}
|
||||
|
||||
char* ThreadProfile2::processDynamicTag(int readPos,
|
||||
char* ThreadProfile::processDynamicTag(int readPos,
|
||||
int* tagsConsumed, char* tagBuff)
|
||||
{
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
@ -219,7 +238,7 @@ char* ThreadProfile2::processDynamicTag(int readPos,
|
||||
bool seenNullByte = false;
|
||||
while (readAheadPos != mLastFlushPos && !seenNullByte) {
|
||||
(*tagsConsumed)++;
|
||||
ProfileEntry2 readAheadEntry = mEntries[readAheadPos];
|
||||
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
||||
for (size_t pos = 0; pos < sizeof(void*); pos++) {
|
||||
tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
|
||||
if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
|
||||
@ -234,22 +253,64 @@ char* ThreadProfile2::processDynamicTag(int readPos,
|
||||
return tagBuff;
|
||||
}
|
||||
|
||||
JSCustomObject* ThreadProfile2::ToJSObject(JSContext *aCx)
|
||||
void ThreadProfile::IterateTags(IterateTagsCallback aCallback)
|
||||
{
|
||||
JSObjectBuilder b(aCx);
|
||||
|
||||
JSCustomObject *profile = b.CreateObject();
|
||||
JSCustomArray *samples = b.CreateArray();
|
||||
b.DefineProperty(profile, "samples", samples);
|
||||
|
||||
JSCustomObject *sample = NULL;
|
||||
JSCustomArray *frames = NULL;
|
||||
MOZ_ASSERT(aCallback);
|
||||
|
||||
int readPos = mReadPos;
|
||||
while (readPos != mLastFlushPos) {
|
||||
// Number of tag consumed
|
||||
int incBy = 1;
|
||||
ProfileEntry2 entry = mEntries[readPos];
|
||||
const ProfileEntry& entry = mEntries[readPos];
|
||||
|
||||
// Read ahead to the next tag, if it's a 'd' tag process it now
|
||||
const char* tagStringData = entry.mTagData;
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
char tagBuff[DYNAMIC_MAX_STRING];
|
||||
// Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
|
||||
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
||||
|
||||
if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
|
||||
tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
|
||||
}
|
||||
|
||||
aCallback(entry, tagStringData);
|
||||
|
||||
readPos = (readPos + incBy) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadProfile::ToStreamAsJSON(std::ostream& stream)
|
||||
{
|
||||
JSCustomObjectBuilder b;
|
||||
JSCustomObject *profile = b.CreateObject();
|
||||
BuildJSObject(b, profile);
|
||||
b.Serialize(profile, stream);
|
||||
b.DeleteObject(profile);
|
||||
}
|
||||
|
||||
JSCustomObject* ThreadProfile::ToJSObject(JSContext *aCx)
|
||||
{
|
||||
JSObjectBuilder b(aCx);
|
||||
JSCustomObject *profile = b.CreateObject();
|
||||
BuildJSObject(b, profile);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
void ThreadProfile::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile) {
|
||||
JSCustomArray *samples = b.CreateArray();
|
||||
b.DefineProperty(profile, "samples", samples);
|
||||
|
||||
JSCustomObject *sample = nullptr;
|
||||
JSCustomArray *frames = nullptr;
|
||||
JSCustomArray *marker = nullptr;
|
||||
|
||||
int readPos = mReadPos;
|
||||
while (readPos != mLastFlushPos) {
|
||||
// Number of tag consumed
|
||||
int incBy = 1;
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
|
||||
// Read ahead to the next tag, if it's a 'd' tag process it now
|
||||
const char* tagStringData = entry.mTagData;
|
||||
@ -270,6 +331,19 @@ JSCustomObject* ThreadProfile2::ToJSObject(JSContext *aCx)
|
||||
frames = b.CreateArray();
|
||||
b.DefineProperty(sample, "frames", frames);
|
||||
b.ArrayPush(samples, sample);
|
||||
// Created lazily
|
||||
marker = nullptr;
|
||||
break;
|
||||
case 'm':
|
||||
{
|
||||
if (sample) {
|
||||
if (!marker) {
|
||||
marker = b.CreateArray();
|
||||
b.DefineProperty(sample, "marker", marker);
|
||||
}
|
||||
b.ArrayPush(marker, tagStringData);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
{
|
||||
@ -320,19 +394,27 @@ JSCustomObject* ThreadProfile2::ToJSObject(JSContext *aCx)
|
||||
}
|
||||
readPos = (readPos + incBy) % mEntrySize;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
PseudoStack* ThreadProfile2::GetPseudoStack()
|
||||
PseudoStack* ThreadProfile::GetPseudoStack()
|
||||
{
|
||||
return mPseudoStack;
|
||||
}
|
||||
|
||||
mozilla::Mutex* ThreadProfile2::GetMutex()
|
||||
mozilla::Mutex* ThreadProfile::GetMutex()
|
||||
{
|
||||
return &mMutex;
|
||||
}
|
||||
|
||||
// END ThreadProfile2
|
||||
std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile)
|
||||
{
|
||||
int readPos = profile.mReadPos;
|
||||
while (readPos != profile.mLastFlushPos) {
|
||||
stream << profile.mEntries[readPos];
|
||||
readPos = (readPos + 1) % profile.mEntrySize;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
// END ThreadProfile
|
||||
////////////////////////////////////////////////////////////////////////
|
@ -7,32 +7,37 @@
|
||||
#define MOZ_PROFILE_ENTRY_H
|
||||
|
||||
#include "GeckoProfilerImpl.h"
|
||||
#include "JSAObjectBuilder.h"
|
||||
#include "platform.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
|
||||
class ThreadProfile2;
|
||||
class ThreadProfile;
|
||||
class ThreadProfile;
|
||||
|
||||
class ProfileEntry2
|
||||
class ProfileEntry
|
||||
{
|
||||
public:
|
||||
ProfileEntry2();
|
||||
ProfileEntry();
|
||||
|
||||
// aTagData must not need release (i.e. be a string from the text segment)
|
||||
ProfileEntry2(char aTagName, const char *aTagData);
|
||||
ProfileEntry2(char aTagName, void *aTagPtr);
|
||||
ProfileEntry2(char aTagName, double aTagFloat);
|
||||
ProfileEntry2(char aTagName, uintptr_t aTagOffset);
|
||||
ProfileEntry2(char aTagName, Address aTagAddress);
|
||||
ProfileEntry2(char aTagName, int aTagLine);
|
||||
ProfileEntry2(char aTagName, char aTagChar);
|
||||
friend std::ostream& operator<<(std::ostream& stream, const ProfileEntry2& entry);
|
||||
ProfileEntry(char aTagName, const char *aTagData);
|
||||
ProfileEntry(char aTagName, void *aTagPtr);
|
||||
ProfileEntry(char aTagName, double aTagFloat);
|
||||
ProfileEntry(char aTagName, uintptr_t aTagOffset);
|
||||
ProfileEntry(char aTagName, Address aTagAddress);
|
||||
ProfileEntry(char aTagName, int aTagLine);
|
||||
ProfileEntry(char aTagName, char aTagChar);
|
||||
friend std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry);
|
||||
bool is_ent_hint(char hintChar);
|
||||
bool is_ent_hint();
|
||||
bool is_ent(char tagName);
|
||||
void* get_tagPtr();
|
||||
void log();
|
||||
|
||||
char getTagName() const { return mTagName; }
|
||||
|
||||
private:
|
||||
friend class ThreadProfile2;
|
||||
friend class ThreadProfile;
|
||||
union {
|
||||
const char* mTagData;
|
||||
char mTagChars[sizeof(void*)];
|
||||
@ -46,25 +51,29 @@ private:
|
||||
char mTagName;
|
||||
};
|
||||
|
||||
typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagStringData);
|
||||
|
||||
class ThreadProfile2
|
||||
class ThreadProfile
|
||||
{
|
||||
public:
|
||||
ThreadProfile2(int aEntrySize, PseudoStack *aStack);
|
||||
~ThreadProfile2();
|
||||
void addTag(ProfileEntry2 aTag);
|
||||
ThreadProfile(int aEntrySize, PseudoStack *aStack);
|
||||
~ThreadProfile();
|
||||
void addTag(ProfileEntry aTag);
|
||||
void flush();
|
||||
void erase();
|
||||
char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff);
|
||||
void IterateTags(IterateTagsCallback aCallback);
|
||||
friend std::ostream& operator<<(std::ostream& stream,
|
||||
const ThreadProfile2& profile);
|
||||
const ThreadProfile& profile);
|
||||
void ToStreamAsJSON(std::ostream& stream);
|
||||
JSCustomObject *ToJSObject(JSContext *aCx);
|
||||
PseudoStack* GetPseudoStack();
|
||||
mozilla::Mutex* GetMutex();
|
||||
void BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile);
|
||||
private:
|
||||
// Circular buffer 'Keep One Slot Open' implementation
|
||||
// for simplicity
|
||||
ProfileEntry2* mEntries;
|
||||
ProfileEntry* mEntries;
|
||||
int mWritePos; // points to the next entry we will write to
|
||||
int mLastFlushPos; // points to the next entry since the last flush()
|
||||
int mReadPos; // points to the next entry we will read to
|
||||
@ -73,4 +82,6 @@ private:
|
||||
mozilla::Mutex mMutex;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile);
|
||||
|
||||
#endif /* ndef MOZ_PROFILE_ENTRY_H */
|
111
tools/profiler/SaveProfileTask.h
Normal file
111
tools/profiler/SaveProfileTask.h
Normal file
@ -0,0 +1,111 @@
|
||||
/* -*- 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_SAVETASK_H_
|
||||
#define PROFILER_SAVETASK_H_
|
||||
|
||||
#include "platform.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsIJSRuntimeService.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#define getpid GetCurrentProcessId
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static JSBool
|
||||
WriteCallback(const jschar *buf, uint32_t len, void *data)
|
||||
{
|
||||
std::ofstream& stream = *static_cast<std::ofstream*>(data);
|
||||
nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
|
||||
stream << profile.Data();
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an event used to save the profile on the main thread
|
||||
* to be sure that it is not being modified while saving.
|
||||
*/
|
||||
class SaveProfileTask : public nsRunnable {
|
||||
public:
|
||||
SaveProfileTask() {}
|
||||
|
||||
NS_IMETHOD Run() {
|
||||
// Get file path
|
||||
# if defined(SPS_PLAT_arm_android)
|
||||
nsCString tmpPath;
|
||||
tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||||
# else
|
||||
nsCOMPtr<nsIFile> tmpFile;
|
||||
nsAutoCString tmpPath;
|
||||
if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
|
||||
LOG("Failed to find temporary directory.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||||
|
||||
nsresult rv = tmpFile->AppendNative(tmpPath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = tmpFile->GetNativePath(tmpPath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
# endif
|
||||
|
||||
// Create a JSContext to run a JSObjectBuilder :(
|
||||
// Based on XPCShellEnvironment
|
||||
JSRuntime *rt;
|
||||
JSContext *cx;
|
||||
nsCOMPtr<nsIJSRuntimeService> rtsvc
|
||||
= do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
|
||||
if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
|
||||
LOG("failed to get RuntimeService");
|
||||
return NS_ERROR_FAILURE;;
|
||||
}
|
||||
|
||||
cx = JS_NewContext(rt, 8192);
|
||||
if (!cx) {
|
||||
LOG("Failed to get context");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
{
|
||||
JSAutoRequest ar(cx);
|
||||
static JSClass c = {
|
||||
"global", JSCLASS_GLOBAL_FLAGS,
|
||||
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
|
||||
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
|
||||
};
|
||||
JSObject *obj = JS_NewGlobalObject(cx, &c, NULL);
|
||||
|
||||
std::ofstream stream;
|
||||
stream.open(tmpPath.get());
|
||||
if (stream.is_open()) {
|
||||
JSAutoCompartment autoComp(cx, obj);
|
||||
JSObject* profileObj = profiler_get_profile_jsobject(cx);
|
||||
jsval val = OBJECT_TO_JSVAL(profileObj);
|
||||
JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
|
||||
stream.close();
|
||||
LOGF("Saved to %s", tmpPath.get());
|
||||
} else {
|
||||
LOG("Fail to open profile log file.");
|
||||
}
|
||||
}
|
||||
JS_EndRequest(cx);
|
||||
JS_DestroyContext(cx);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -8,17 +8,18 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "GeckoProfilerImpl.h"
|
||||
#include "SaveProfileTask.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "platform.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "prenv.h"
|
||||
#include "shared-libraries.h"
|
||||
#include "mozilla/StackWalk.h"
|
||||
#include "TableTicker.h"
|
||||
|
||||
// JSON
|
||||
#include "JSObjectBuilder.h"
|
||||
#include "JSCustomObjectBuilder.h"
|
||||
#include "nsIJSRuntimeService.h"
|
||||
|
||||
// Meta
|
||||
#include "nsXPCOM.h"
|
||||
@ -56,13 +57,6 @@
|
||||
using std::string;
|
||||
using namespace mozilla;
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#define getpid GetCurrentProcessId
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifndef MAXPATHLEN
|
||||
#ifdef PATH_MAX
|
||||
#define MAXPATHLEN PATH_MAX
|
||||
@ -77,526 +71,11 @@ using namespace mozilla;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
static const int DYNAMIC_MAX_STRING = 512;
|
||||
|
||||
static mozilla::ThreadLocal<TableTicker *> tlsTicker;
|
||||
|
||||
TimeStamp sLastTracerEvent;
|
||||
int sFrameNumber = 0;
|
||||
int sLastFrameNumber = 0;
|
||||
|
||||
class ThreadProfile;
|
||||
|
||||
class ProfileEntry
|
||||
{
|
||||
public:
|
||||
ProfileEntry()
|
||||
: mTagData(NULL)
|
||||
, mTagName(0)
|
||||
{ }
|
||||
|
||||
// aTagData must not need release (i.e. be a string from the text segment)
|
||||
ProfileEntry(char aTagName, const char *aTagData)
|
||||
: mTagData(aTagData)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry(char aTagName, void *aTagPtr)
|
||||
: mTagPtr(aTagPtr)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry(char aTagName, double aTagFloat)
|
||||
: mTagFloat(aTagFloat)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry(char aTagName, uintptr_t aTagOffset)
|
||||
: mTagOffset(aTagOffset)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry(char aTagName, Address aTagAddress)
|
||||
: mTagAddress(aTagAddress)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry(char aTagName, int aTagLine)
|
||||
: mTagLine(aTagLine)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry);
|
||||
|
||||
union {
|
||||
const char* mTagData;
|
||||
char mTagChars[sizeof(void*)];
|
||||
void* mTagPtr;
|
||||
double mTagFloat;
|
||||
Address mTagAddress;
|
||||
uintptr_t mTagOffset;
|
||||
int mTagLine;
|
||||
};
|
||||
char mTagName;
|
||||
};
|
||||
|
||||
typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagStringData);
|
||||
|
||||
#define PROFILE_MAX_ENTRY 100000
|
||||
class ThreadProfile
|
||||
{
|
||||
public:
|
||||
ThreadProfile(int aEntrySize, PseudoStack *aStack)
|
||||
: mWritePos(0)
|
||||
, mLastFlushPos(0)
|
||||
, mReadPos(0)
|
||||
, mEntrySize(aEntrySize)
|
||||
, mStack(aStack)
|
||||
{
|
||||
mEntries = new ProfileEntry[mEntrySize];
|
||||
}
|
||||
|
||||
~ThreadProfile()
|
||||
{
|
||||
delete[] mEntries;
|
||||
}
|
||||
|
||||
void addTag(ProfileEntry aTag)
|
||||
{
|
||||
// Called from signal, call only reentrant functions
|
||||
mEntries[mWritePos] = aTag;
|
||||
mWritePos = (mWritePos + 1) % mEntrySize;
|
||||
if (mWritePos == mReadPos) {
|
||||
// Keep one slot open
|
||||
mEntries[mReadPos] = ProfileEntry();
|
||||
mReadPos = (mReadPos + 1) % mEntrySize;
|
||||
}
|
||||
// we also need to move the flush pos to ensure we
|
||||
// do not pass it
|
||||
if (mWritePos == mLastFlushPos) {
|
||||
mLastFlushPos = (mLastFlushPos + 1) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
// flush the new entries
|
||||
void flush()
|
||||
{
|
||||
mLastFlushPos = mWritePos;
|
||||
}
|
||||
|
||||
// discards all of the entries since the last flush()
|
||||
// NOTE: that if mWritePos happens to wrap around past
|
||||
// mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries
|
||||
//
|
||||
// r = mReadPos
|
||||
// w = mWritePos
|
||||
// f = mLastFlushPos
|
||||
//
|
||||
// r f w
|
||||
// |-----------------------------|
|
||||
// | abcdefghijklmnopq | -> 'abcdefghijklmnopq'
|
||||
// |-----------------------------|
|
||||
//
|
||||
//
|
||||
// mWritePos and mReadPos have passed mLastFlushPos
|
||||
// f
|
||||
// w r
|
||||
// |-----------------------------|
|
||||
// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
|
||||
// |-----------------------------|
|
||||
// w
|
||||
// r
|
||||
// |-----------------------------|
|
||||
// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
|
||||
// |-----------------------------|
|
||||
//
|
||||
//
|
||||
// mWritePos will end up the same as mReadPos
|
||||
// r
|
||||
// w f
|
||||
// |-----------------------------|
|
||||
// |ABCDEFGHIJKLMklmnopqrstuvwxyz|
|
||||
// |-----------------------------|
|
||||
// r
|
||||
// w
|
||||
// |-----------------------------|
|
||||
// |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> ''
|
||||
// |-----------------------------|
|
||||
//
|
||||
//
|
||||
// mWritePos has moved past mReadPos
|
||||
// w r f
|
||||
// |-----------------------------|
|
||||
// |ABCDEFdefghijklmnopqrstuvwxyz|
|
||||
// |-----------------------------|
|
||||
// r w
|
||||
// |-----------------------------|
|
||||
// |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
|
||||
// |-----------------------------|
|
||||
|
||||
void erase()
|
||||
{
|
||||
mWritePos = mLastFlushPos;
|
||||
}
|
||||
|
||||
char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff)
|
||||
{
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
int tagBuffPos = 0;
|
||||
|
||||
// Read the string stored in mTagData until the null character is seen
|
||||
bool seenNullByte = false;
|
||||
while (readAheadPos != mLastFlushPos && !seenNullByte) {
|
||||
(*tagsConsumed)++;
|
||||
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
||||
for (size_t pos = 0; pos < sizeof(void*); pos++) {
|
||||
tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
|
||||
if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
|
||||
seenNullByte = true;
|
||||
break;
|
||||
}
|
||||
tagBuffPos++;
|
||||
}
|
||||
if (!seenNullByte)
|
||||
readAheadPos = (readAheadPos + 1) % mEntrySize;
|
||||
}
|
||||
return tagBuff;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile);
|
||||
|
||||
void IterateTags(IterateTagsCallback aCallback)
|
||||
{
|
||||
MOZ_ASSERT(aCallback);
|
||||
|
||||
int readPos = mReadPos;
|
||||
while (readPos != mLastFlushPos) {
|
||||
// Number of tag consumed
|
||||
int incBy = 1;
|
||||
const ProfileEntry& entry = mEntries[readPos];
|
||||
|
||||
// Read ahead to the next tag, if it's a 'd' tag process it now
|
||||
const char* tagStringData = entry.mTagData;
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
char tagBuff[DYNAMIC_MAX_STRING];
|
||||
// Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
|
||||
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
||||
|
||||
if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
|
||||
tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
|
||||
}
|
||||
|
||||
aCallback(entry, tagStringData);
|
||||
|
||||
readPos = (readPos + incBy) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
void ToStreamAsJSON(std::ostream& stream)
|
||||
{
|
||||
JSCustomObjectBuilder b;
|
||||
JSCustomObject *profile = b.CreateObject();
|
||||
BuildJSObject(b, profile);
|
||||
b.Serialize(profile, stream);
|
||||
b.DeleteObject(profile);
|
||||
}
|
||||
|
||||
JSCustomObject *ToJSObject(JSContext *aCx)
|
||||
{
|
||||
JSObjectBuilder b(aCx);
|
||||
JSCustomObject *profile = b.CreateObject();
|
||||
BuildJSObject(b, profile);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
void BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile) {
|
||||
JSCustomArray *samples = b.CreateArray();
|
||||
b.DefineProperty(profile, "samples", samples);
|
||||
|
||||
JSCustomObject *sample = nullptr;
|
||||
JSCustomArray *frames = nullptr;
|
||||
JSCustomArray *marker = nullptr;
|
||||
|
||||
int readPos = mReadPos;
|
||||
while (readPos != mLastFlushPos) {
|
||||
// Number of tag consumed
|
||||
int incBy = 1;
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
|
||||
// Read ahead to the next tag, if it's a 'd' tag process it now
|
||||
const char* tagStringData = entry.mTagData;
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
char tagBuff[DYNAMIC_MAX_STRING];
|
||||
// Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
|
||||
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
||||
|
||||
if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
|
||||
tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
|
||||
}
|
||||
|
||||
switch (entry.mTagName) {
|
||||
case 's':
|
||||
sample = b.CreateObject();
|
||||
b.DefineProperty(sample, "name", tagStringData);
|
||||
frames = b.CreateArray();
|
||||
b.DefineProperty(sample, "frames", frames);
|
||||
b.ArrayPush(samples, sample);
|
||||
// Created lazily
|
||||
marker = NULL;
|
||||
break;
|
||||
case 'm':
|
||||
{
|
||||
if (sample) {
|
||||
if (!marker) {
|
||||
marker = b.CreateArray();
|
||||
b.DefineProperty(sample, "marker", marker);
|
||||
}
|
||||
b.ArrayPush(marker, tagStringData);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
{
|
||||
if (sample) {
|
||||
b.DefineProperty(sample, "responsiveness", entry.mTagFloat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
{
|
||||
if (sample) {
|
||||
b.DefineProperty(sample, "frameNumber", entry.mTagLine);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
{
|
||||
if (sample) {
|
||||
b.DefineProperty(sample, "time", entry.mTagFloat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
case 'l':
|
||||
{
|
||||
if (sample) {
|
||||
JSCustomObject *frame = b.CreateObject();
|
||||
if (entry.mTagName == 'l') {
|
||||
// Bug 753041
|
||||
// We need a double cast here to tell GCC that we don't want to sign
|
||||
// extend 32-bit addresses starting with 0xFXXXXXX.
|
||||
unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
|
||||
snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
|
||||
b.DefineProperty(frame, "location", tagBuff);
|
||||
} else {
|
||||
b.DefineProperty(frame, "location", tagStringData);
|
||||
readAheadPos = (readPos + incBy) % mEntrySize;
|
||||
if (readAheadPos != mLastFlushPos &&
|
||||
mEntries[readAheadPos].mTagName == 'n') {
|
||||
b.DefineProperty(frame, "line",
|
||||
mEntries[readAheadPos].mTagLine);
|
||||
incBy++;
|
||||
}
|
||||
}
|
||||
b.ArrayPush(frames, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
readPos = (readPos + incBy) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
PseudoStack* GetStack()
|
||||
{
|
||||
return mStack;
|
||||
}
|
||||
private:
|
||||
// Circular buffer 'Keep One Slot Open' implementation
|
||||
// for simplicity
|
||||
ProfileEntry *mEntries;
|
||||
int mWritePos; // points to the next entry we will write to
|
||||
int mLastFlushPos; // points to the next entry since the last flush()
|
||||
int mReadPos; // points to the next entry we will read to
|
||||
int mEntrySize;
|
||||
PseudoStack *mStack;
|
||||
};
|
||||
|
||||
class SaveProfileTask;
|
||||
|
||||
static bool
|
||||
hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) {
|
||||
for(size_t i = 0; i < aFeatureCount; i++) {
|
||||
if (strcmp(aFeatures[i], aFeature) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class TableTicker: public Sampler {
|
||||
public:
|
||||
TableTicker(int aInterval, int aEntrySize, PseudoStack *aStack,
|
||||
const char** aFeatures, uint32_t aFeatureCount)
|
||||
: Sampler(aInterval, true)
|
||||
, mPrimaryThreadProfile(aEntrySize, aStack)
|
||||
, mStartTime(TimeStamp::Now())
|
||||
, mSaveRequested(false)
|
||||
{
|
||||
mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
|
||||
|
||||
//XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
|
||||
mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
|
||||
mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
|
||||
mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf");
|
||||
mPrimaryThreadProfile.addTag(ProfileEntry('m', "Start"));
|
||||
}
|
||||
|
||||
~TableTicker() { if (IsActive()) Stop(); }
|
||||
|
||||
virtual void SampleStack(TickSample* sample) {}
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void Tick(TickSample* sample);
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void RequestSave()
|
||||
{
|
||||
mSaveRequested = true;
|
||||
}
|
||||
|
||||
virtual void HandleSaveRequest();
|
||||
|
||||
ThreadProfile* GetPrimaryThreadProfile()
|
||||
{
|
||||
return &mPrimaryThreadProfile;
|
||||
}
|
||||
|
||||
void ToStreamAsJSON(std::ostream& stream);
|
||||
JSObject *ToJSObject(JSContext *aCx);
|
||||
JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b);
|
||||
|
||||
const bool ProfileJS() { return mProfileJS; }
|
||||
|
||||
private:
|
||||
// Not implemented on platforms which do not support backtracing
|
||||
void doBacktrace(ThreadProfile &aProfile, TickSample* aSample);
|
||||
|
||||
void BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile);
|
||||
private:
|
||||
// This represent the application's main thread (SAMPLER_INIT)
|
||||
ThreadProfile mPrimaryThreadProfile;
|
||||
TimeStamp mStartTime;
|
||||
bool mSaveRequested;
|
||||
bool mAddLeafAddresses;
|
||||
bool mUseStackWalk;
|
||||
bool mJankOnly;
|
||||
bool mProfileJS;
|
||||
};
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SaveProfileTask et al
|
||||
|
||||
std::string GetSharedLibraryInfoString();
|
||||
|
||||
static JSBool
|
||||
WriteCallback(const jschar *buf, uint32_t len, void *data)
|
||||
{
|
||||
std::ofstream& stream = *static_cast<std::ofstream*>(data);
|
||||
nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
|
||||
stream << profile.Data();
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an event used to save the profile on the main thread
|
||||
* to be sure that it is not being modified while saving.
|
||||
*/
|
||||
class SaveProfileTask : public nsRunnable {
|
||||
public:
|
||||
SaveProfileTask() {}
|
||||
|
||||
NS_IMETHOD Run() {
|
||||
TableTicker *t = tlsTicker.get();
|
||||
// Pause the profiler during saving.
|
||||
// This will prevent us from recording sampling
|
||||
// regarding profile saving. This will also
|
||||
// prevent bugs caused by the circular buffer not
|
||||
// being thread safe. Bug 750989.
|
||||
t->SetPaused(true);
|
||||
|
||||
// Get file path
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
nsCString tmpPath;
|
||||
tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||||
#else
|
||||
nsCOMPtr<nsIFile> tmpFile;
|
||||
nsAutoCString tmpPath;
|
||||
if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
|
||||
LOG("Failed to find temporary directory.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||||
|
||||
nsresult rv = tmpFile->AppendNative(tmpPath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = tmpFile->GetNativePath(tmpPath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
#endif
|
||||
|
||||
// Create a JSContext to run a JSCustomObjectBuilder :(
|
||||
// Based on XPCShellEnvironment
|
||||
JSRuntime *rt;
|
||||
JSContext *cx;
|
||||
nsCOMPtr<nsIJSRuntimeService> rtsvc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
|
||||
if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
|
||||
LOG("failed to get RuntimeService");
|
||||
return NS_ERROR_FAILURE;;
|
||||
}
|
||||
|
||||
cx = JS_NewContext(rt, 8192);
|
||||
if (!cx) {
|
||||
LOG("Failed to get context");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
{
|
||||
JSAutoRequest ar(cx);
|
||||
static JSClass c = {
|
||||
"global", JSCLASS_GLOBAL_FLAGS,
|
||||
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
|
||||
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
|
||||
};
|
||||
JSObject *obj = JS_NewGlobalObject(cx, &c, NULL, JS::SystemZone);
|
||||
|
||||
std::ofstream stream;
|
||||
stream.open(tmpPath.get());
|
||||
// Pause the profiler during saving.
|
||||
// This will prevent us from recording sampling
|
||||
// regarding profile saving. This will also
|
||||
// prevent bugs caused by the circular buffer not
|
||||
// being thread safe. Bug 750989.
|
||||
if (stream.is_open()) {
|
||||
JSAutoCompartment autoComp(cx, obj);
|
||||
JSObject* profileObj = mozilla_sampler_get_profile_data1(cx);
|
||||
JS::Value val = OBJECT_TO_JSVAL(profileObj);
|
||||
JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
|
||||
stream.close();
|
||||
LOGF("Saved to %s", tmpPath.get());
|
||||
} else {
|
||||
LOG("Fail to open profile log file.");
|
||||
}
|
||||
}
|
||||
JS_EndRequest(cx);
|
||||
JS_DestroyContext(cx);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
void TableTicker::HandleSaveRequest()
|
||||
{
|
||||
if (!mSaveRequested)
|
||||
@ -696,12 +175,18 @@ void TableTicker::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile)
|
||||
|
||||
// For now we only have one thread
|
||||
SetPaused(true);
|
||||
ThreadProfile* prof = GetPrimaryThreadProfile();
|
||||
prof->GetMutex()->Lock();
|
||||
JSCustomObject* threadSamples = b.CreateObject();
|
||||
GetPrimaryThreadProfile()->BuildJSObject(b, threadSamples);
|
||||
prof->BuildJSObject(b, threadSamples);
|
||||
b.ArrayPush(threads, threadSamples);
|
||||
prof->GetMutex()->Unlock();
|
||||
SetPaused(false);
|
||||
}
|
||||
|
||||
// END SaveProfileTask et al
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static
|
||||
void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr)
|
||||
{
|
||||
@ -764,7 +249,7 @@ void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile,
|
||||
}
|
||||
|
||||
#ifdef USE_BACKTRACE
|
||||
void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
|
||||
void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
|
||||
{
|
||||
void *array[100];
|
||||
int count = backtrace (array, 100);
|
||||
@ -797,7 +282,7 @@ void StackWalkCallback(void* aPC, void* aSP, void* aClosure)
|
||||
array->count++;
|
||||
}
|
||||
|
||||
void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
|
||||
void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
|
||||
{
|
||||
#ifndef XP_MACOSX
|
||||
uintptr_t thread = GetThreadHandle(platform_data());
|
||||
@ -838,7 +323,7 @@ void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aProfile.addTag(ProfileEntry('s', "(root)"));
|
||||
|
||||
PseudoStack* stack = aProfile.GetStack();
|
||||
PseudoStack* stack = aProfile.GetPseudoStack();
|
||||
uint32_t pseudoStackPos = 0;
|
||||
|
||||
/* We have two stacks, the native C stack we extracted from unwinding,
|
||||
@ -896,22 +381,10 @@ void doSampleStackTrace(PseudoStack *aStack, ThreadProfile &aProfile, TickSample
|
||||
#endif
|
||||
}
|
||||
|
||||
/* used to keep track of the last event that we sampled during */
|
||||
unsigned int sLastSampledEventGeneration = 0;
|
||||
|
||||
/* a counter that's incremented everytime we get responsiveness event
|
||||
* note: it might also be worth tracking everytime we go around
|
||||
* the event loop */
|
||||
unsigned int sCurrentEventGeneration = 0;
|
||||
/* we don't need to worry about overflow because we only treat the
|
||||
* case of them being the same as special. i.e. we only run into
|
||||
* a problem if 2^32 events happen between samples that we need
|
||||
* to know are associated with different events */
|
||||
|
||||
void TableTicker::Tick(TickSample* sample)
|
||||
{
|
||||
// Marker(s) come before the sample
|
||||
PseudoStack* stack = mPrimaryThreadProfile.GetStack();
|
||||
PseudoStack* stack = mPrimaryThreadProfile.GetPseudoStack();
|
||||
for (int i = 0; stack->getMarker(i) != NULL; i++) {
|
||||
addDynamicTag(mPrimaryThreadProfile, 'm', stack->getMarker(i));
|
||||
}
|
||||
@ -941,7 +414,7 @@ void TableTicker::Tick(TickSample* sample)
|
||||
|
||||
#if defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK)
|
||||
if (mUseStackWalk) {
|
||||
doBacktrace(mPrimaryThreadProfile, sample);
|
||||
doNativeBacktrace(mPrimaryThreadProfile, sample);
|
||||
} else {
|
||||
doSampleStackTrace(stack, mPrimaryThreadProfile, mAddLeafAddresses ? sample : nullptr);
|
||||
}
|
||||
@ -968,290 +441,8 @@ void TableTicker::Tick(TickSample* sample)
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile)
|
||||
{
|
||||
int readPos = profile.mReadPos;
|
||||
while (readPos != profile.mLastFlushPos) {
|
||||
stream << profile.mEntries[readPos];
|
||||
readPos = (readPos + 1) % profile.mEntrySize;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry)
|
||||
{
|
||||
if (entry.mTagName == 'r' || entry.mTagName == 't') {
|
||||
stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
|
||||
} else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
|
||||
// Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
|
||||
// Additionally, stringstream seemed to be ignoring formatter flags.
|
||||
char tagBuff[1024];
|
||||
unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
|
||||
snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
|
||||
stream << tagBuff;
|
||||
} else if (entry.mTagName == 'd') {
|
||||
// TODO implement 'd' tag for text profile
|
||||
} else {
|
||||
stream << entry.mTagName << "-" << entry.mTagData << "\n";
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
bool sps_version2()
|
||||
{
|
||||
static int version = 0; // Raced on, potentially
|
||||
|
||||
if (version == 0) {
|
||||
bool allow2 = false; // Is v2 allowable on this platform?
|
||||
# if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||||
|| defined(SPS_PLAT_x86_linux)
|
||||
allow2 = true;
|
||||
# elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin) \
|
||||
|| defined(SPS_PLAT_x86_windows) || defined(SPS_PLAT_x86_android) \
|
||||
|| defined(SPS_PLAT_amd64_windows)
|
||||
allow2 = false;
|
||||
# else
|
||||
# error "Unknown platform"
|
||||
# endif
|
||||
|
||||
bool req2 = PR_GetEnv("MOZ_PROFILER_NEW") != NULL; // Has v2 been requested?
|
||||
|
||||
bool elfhackd = false;
|
||||
# if defined(USE_ELF_HACK)
|
||||
bool elfhackd = true;
|
||||
# endif
|
||||
|
||||
if (req2 && allow2) {
|
||||
version = 2;
|
||||
LOG("------------------- MOZ_PROFILER_NEW set -------------------");
|
||||
} else if (req2 && !allow2) {
|
||||
version = 1;
|
||||
LOG("--------------- MOZ_PROFILER_NEW requested, ----------------");
|
||||
LOG("---------- but is not available on this platform -----------");
|
||||
} else if (req2 && elfhackd) {
|
||||
version = 1;
|
||||
LOG("--------------- MOZ_PROFILER_NEW requested, ----------------");
|
||||
LOG("--- but this build was not done with --disable-elf-hack ----");
|
||||
} else {
|
||||
version = 1;
|
||||
LOG("----------------- MOZ_PROFILER_NEW not set -----------------");
|
||||
}
|
||||
}
|
||||
return version == 2;
|
||||
}
|
||||
|
||||
void mozilla_sampler_init1()
|
||||
{
|
||||
if (stack_key_initialized)
|
||||
return;
|
||||
|
||||
if (!tlsPseudoStack.init() || !tlsTicker.init()) {
|
||||
LOG("Failed to init.");
|
||||
return;
|
||||
}
|
||||
stack_key_initialized = true;
|
||||
|
||||
PseudoStack *stack = new PseudoStack();
|
||||
tlsPseudoStack.set(stack);
|
||||
|
||||
// Allow the profiler to be started using signals
|
||||
OS::RegisterStartHandler();
|
||||
|
||||
// We can't open pref so we use an environment variable
|
||||
// to know if we should trigger the profiler on startup
|
||||
// NOTE: Default
|
||||
const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP");
|
||||
if (!val || !*val) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* features[] = {"js", "leaf"
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
, "stackwalk"
|
||||
#endif
|
||||
};
|
||||
mozilla_sampler_start1(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
|
||||
features, sizeof(features)/sizeof(const char*));
|
||||
}
|
||||
|
||||
void mozilla_sampler_shutdown1()
|
||||
{
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (t) {
|
||||
const char *val = PR_GetEnv("MOZ_PROFILER_SHUTDOWN");
|
||||
if (val) {
|
||||
std::ofstream stream;
|
||||
stream.open(val);
|
||||
if (stream.is_open()) {
|
||||
t->ToStreamAsJSON(stream);
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mozilla_sampler_stop1();
|
||||
// We can't delete the Stack because we can be between a
|
||||
// sampler call_enter/call_exit point.
|
||||
// TODO Need to find a safe time to delete Stack
|
||||
}
|
||||
|
||||
void mozilla_sampler_save1()
|
||||
{
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
|
||||
t->RequestSave();
|
||||
// We're on the main thread already so we don't
|
||||
// have to wait to handle the save request.
|
||||
t->HandleSaveRequest();
|
||||
}
|
||||
|
||||
char* mozilla_sampler_get_profile1()
|
||||
{
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::stringstream profile;
|
||||
t->SetPaused(true);
|
||||
profile << *(t->GetPrimaryThreadProfile());
|
||||
t->SetPaused(false);
|
||||
|
||||
std::string profileString = profile.str();
|
||||
char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) );
|
||||
strcpy(rtn, profileString.c_str());
|
||||
return rtn;
|
||||
}
|
||||
|
||||
JSObject *mozilla_sampler_get_profile_data1(JSContext *aCx)
|
||||
{
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return t->ToJSObject(aCx);
|
||||
}
|
||||
|
||||
|
||||
const char** mozilla_sampler_get_features1()
|
||||
{
|
||||
static const char* features[] = {
|
||||
#if defined(MOZ_PROFILING) && (defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK))
|
||||
"stackwalk",
|
||||
#endif
|
||||
#if defined(ENABLE_SPS_LEAF_DATA)
|
||||
"leaf",
|
||||
#endif
|
||||
"jank",
|
||||
"js",
|
||||
NULL
|
||||
};
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
// Values are only honored on the first start
|
||||
void mozilla_sampler_start1(int aProfileEntries, int aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount)
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
mozilla_sampler_init1();
|
||||
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla_sampler_stop1();
|
||||
|
||||
TableTicker *t = new TableTicker(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
|
||||
aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
|
||||
stack, aFeatures, aFeatureCount);
|
||||
tlsTicker.set(t);
|
||||
t->Start();
|
||||
if (t->ProfileJS())
|
||||
stack->enableJSSampling();
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-started", nullptr);
|
||||
}
|
||||
|
||||
void mozilla_sampler_stop1()
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
mozilla_sampler_init1();
|
||||
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool disableJS = t->ProfileJS();
|
||||
|
||||
t->Stop();
|
||||
delete t;
|
||||
tlsTicker.set(NULL);
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
ASSERT(stack != NULL);
|
||||
|
||||
if (disableJS)
|
||||
stack->disableJSSampling();
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
|
||||
}
|
||||
|
||||
bool mozilla_sampler_is_active1()
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
mozilla_sampler_init1();
|
||||
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return t->IsActive();
|
||||
}
|
||||
|
||||
static double sResponsivenessTimes[100];
|
||||
static unsigned int sResponsivenessLoc = 0;
|
||||
void mozilla_sampler_responsiveness1(const TimeStamp& aTime)
|
||||
{
|
||||
if (!sLastTracerEvent.IsNull()) {
|
||||
if (sResponsivenessLoc == 100) {
|
||||
for(size_t i = 0; i < 100-1; i++) {
|
||||
sResponsivenessTimes[i] = sResponsivenessTimes[i+1];
|
||||
}
|
||||
sResponsivenessLoc--;
|
||||
}
|
||||
TimeDuration delta = aTime - sLastTracerEvent;
|
||||
sResponsivenessTimes[sResponsivenessLoc++] = delta.ToMilliseconds();
|
||||
}
|
||||
sCurrentEventGeneration++;
|
||||
|
||||
sLastTracerEvent = aTime;
|
||||
}
|
||||
|
||||
const double* mozilla_sampler_get_responsiveness1()
|
||||
{
|
||||
return sResponsivenessTimes;
|
||||
}
|
||||
|
||||
void mozilla_sampler_frame_number1(int frameNumber)
|
||||
{
|
||||
sFrameNumber = frameNumber;
|
||||
}
|
||||
|
||||
static void print_callback(const ProfileEntry& entry, const char* tagStringData) {
|
||||
switch (entry.mTagName) {
|
||||
switch (entry.getTagName()) {
|
||||
case 's':
|
||||
case 'c':
|
||||
printf_stderr(" %s\n", tagStringData);
|
||||
@ -1261,7 +452,7 @@ static void print_callback(const ProfileEntry& entry, const char* tagStringData)
|
||||
void mozilla_sampler_print_location1()
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
mozilla_sampler_init1();
|
||||
profiler_init();
|
||||
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
@ -1278,17 +469,4 @@ void mozilla_sampler_print_location1()
|
||||
threadProfile.IterateTags(print_callback);
|
||||
}
|
||||
|
||||
void mozilla_sampler_lock1()
|
||||
{
|
||||
mozilla_sampler_stop1();
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-locked", nullptr);
|
||||
}
|
||||
|
||||
void mozilla_sampler_unlock1()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-unlocked", nullptr);
|
||||
}
|
||||
|
105
tools/profiler/TableTicker.h
Normal file
105
tools/profiler/TableTicker.h
Normal file
@ -0,0 +1,105 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#include "platform.h"
|
||||
#include "ProfileEntry.h"
|
||||
|
||||
static bool
|
||||
hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) {
|
||||
for(size_t i = 0; i < aFeatureCount; i++) {
|
||||
if (strcmp(aFeatures[i], aFeature) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void read_env_vars();
|
||||
typedef enum { UnwINVALID, UnwNATIVE, UnwPSEUDO, UnwCOMBINED } UnwMode;
|
||||
extern UnwMode sUnwindMode;
|
||||
extern int sUnwindInterval;
|
||||
|
||||
extern TimeStamp sLastTracerEvent;
|
||||
extern int sFrameNumber;
|
||||
extern int sLastFrameNumber;
|
||||
extern unsigned int sCurrentEventGeneration;
|
||||
extern unsigned int sLastSampledEventGeneration;
|
||||
|
||||
class BreakpadSampler;
|
||||
|
||||
class TableTicker: public Sampler {
|
||||
public:
|
||||
TableTicker(int aInterval, int aEntrySize, PseudoStack *aStack,
|
||||
const char** aFeatures, uint32_t aFeatureCount)
|
||||
: Sampler(aInterval, true)
|
||||
, mPrimaryThreadProfile(aEntrySize, aStack)
|
||||
, mStartTime(TimeStamp::Now())
|
||||
, mSaveRequested(false)
|
||||
{
|
||||
mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
|
||||
|
||||
//XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
|
||||
mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
|
||||
mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
|
||||
mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf");
|
||||
mPrimaryThreadProfile.addTag(ProfileEntry('m', "Start"));
|
||||
}
|
||||
|
||||
~TableTicker() { if (IsActive()) Stop(); }
|
||||
|
||||
virtual void SampleStack(TickSample* sample) {}
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void Tick(TickSample* sample);
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void RequestSave()
|
||||
{
|
||||
mSaveRequested = true;
|
||||
}
|
||||
|
||||
virtual void HandleSaveRequest();
|
||||
|
||||
ThreadProfile* GetPrimaryThreadProfile()
|
||||
{
|
||||
return &mPrimaryThreadProfile;
|
||||
}
|
||||
|
||||
void ToStreamAsJSON(std::ostream& stream);
|
||||
virtual JSObject *ToJSObject(JSContext *aCx);
|
||||
JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b);
|
||||
|
||||
const bool ProfileJS() { return mProfileJS; }
|
||||
|
||||
virtual BreakpadSampler* AsBreakpadSampler() { return nullptr; }
|
||||
|
||||
protected:
|
||||
// Not implemented on platforms which do not support backtracing
|
||||
void doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample);
|
||||
|
||||
void BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile);
|
||||
|
||||
// This represent the application's main thread (SAMPLER_INIT)
|
||||
ThreadProfile mPrimaryThreadProfile;
|
||||
TimeStamp mStartTime;
|
||||
bool mSaveRequested;
|
||||
bool mAddLeafAddresses;
|
||||
bool mUseStackWalk;
|
||||
bool mJankOnly;
|
||||
bool mProfileJS;
|
||||
};
|
||||
|
||||
class BreakpadSampler: public TableTicker {
|
||||
public:
|
||||
BreakpadSampler(int aInterval, int aEntrySize, PseudoStack *aStack,
|
||||
const char** aFeatures, uint32_t aFeatureCount)
|
||||
: TableTicker(aInterval, aEntrySize, aStack, aFeatures, aFeatureCount)
|
||||
{}
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void Tick(TickSample* sample);
|
||||
|
||||
virtual BreakpadSampler* AsBreakpadSampler() { return this; }
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,7 @@
|
||||
#include "platform.h"
|
||||
#include <iostream>
|
||||
|
||||
#include "ProfileEntry2.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "UnwinderThread2.h"
|
||||
|
||||
#if !defined(SPS_OS_windows)
|
||||
@ -90,7 +90,7 @@ UnwinderThreadBuffer* uwt__acquire_empty_buffer()
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
void
|
||||
uwt__release_full_buffer(ThreadProfile2* aProfile,
|
||||
uwt__release_full_buffer(ThreadProfile* aProfile,
|
||||
UnwinderThreadBuffer* utb,
|
||||
void* /* ucontext_t*, really */ ucV )
|
||||
{
|
||||
@ -98,7 +98,7 @@ uwt__release_full_buffer(ThreadProfile2* aProfile,
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
void
|
||||
utb__addEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
|
||||
utb__addEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry ent)
|
||||
{
|
||||
}
|
||||
|
||||
@ -133,12 +133,12 @@ static UnwinderThreadBuffer* acquire_empty_buffer();
|
||||
// is the partially-filled-in buffer, containing ProfileEntries.
|
||||
// UCV is the ucontext_t* from the signal handler. If non-NULL, is
|
||||
// taken as a cue to request native unwind.
|
||||
static void release_full_buffer(ThreadProfile2* aProfile,
|
||||
static void release_full_buffer(ThreadProfile* aProfile,
|
||||
UnwinderThreadBuffer* utb,
|
||||
void* /* ucontext_t*, really */ ucV );
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
static void utb_add_prof_ent(UnwinderThreadBuffer* utb, ProfileEntry2 ent);
|
||||
static void utb_add_prof_ent(UnwinderThreadBuffer* utb, ProfileEntry ent);
|
||||
|
||||
// Do a store memory barrier.
|
||||
static void do_MBAR();
|
||||
@ -175,7 +175,7 @@ UnwinderThreadBuffer* uwt__acquire_empty_buffer()
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
void
|
||||
uwt__release_full_buffer(ThreadProfile2* aProfile,
|
||||
uwt__release_full_buffer(ThreadProfile* aProfile,
|
||||
UnwinderThreadBuffer* utb,
|
||||
void* /* ucontext_t*, really */ ucV )
|
||||
{
|
||||
@ -184,7 +184,7 @@ uwt__release_full_buffer(ThreadProfile2* aProfile,
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
void
|
||||
utb__addEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
|
||||
utb__addEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry ent)
|
||||
{
|
||||
utb_add_prof_ent(utb, ent);
|
||||
}
|
||||
@ -253,24 +253,24 @@ typedef struct { uintptr_t val; } SpinLock;
|
||||
#define N_STACK_BYTES 32768
|
||||
|
||||
/* CONFIGURABLE */
|
||||
/* The number of fixed ProfileEntry2 slots. If more are required, they
|
||||
/* The number of fixed ProfileEntry slots. If more are required, they
|
||||
are placed in mmap'd pages. */
|
||||
#define N_FIXED_PROF_ENTS 20
|
||||
|
||||
/* CONFIGURABLE */
|
||||
/* The number of extra pages of ProfileEntries. If (on arm) each
|
||||
ProfileEntry2 is 8 bytes, then a page holds 512, and so 100 pages
|
||||
ProfileEntry is 8 bytes, then a page holds 512, and so 100 pages
|
||||
is enough to hold 51200. */
|
||||
#define N_PROF_ENT_PAGES 100
|
||||
|
||||
/* DERIVATIVE */
|
||||
#define N_PROF_ENTS_PER_PAGE (SPS_PAGE_SIZE / sizeof(ProfileEntry2))
|
||||
#define N_PROF_ENTS_PER_PAGE (SPS_PAGE_SIZE / sizeof(ProfileEntry))
|
||||
|
||||
/* A page of ProfileEntry2s. This might actually be slightly smaller
|
||||
/* A page of ProfileEntrys. This might actually be slightly smaller
|
||||
than a page if SPS_PAGE_SIZE is not an exact multiple of
|
||||
sizeof(ProfileEntry2). */
|
||||
sizeof(ProfileEntry). */
|
||||
typedef
|
||||
struct { ProfileEntry2 ents[N_PROF_ENTS_PER_PAGE]; }
|
||||
struct { ProfileEntry ents[N_PROF_ENTS_PER_PAGE]; }
|
||||
ProfEntsPage;
|
||||
|
||||
#define ProfEntsPage_INVALID ((ProfEntsPage*)1)
|
||||
@ -289,11 +289,11 @@ struct _UnwinderThreadBuffer {
|
||||
fields. */
|
||||
/* Sample number, needed to process samples in order */
|
||||
uint64_t seqNo;
|
||||
/* The ThreadProfile2 into which the results are eventually to be
|
||||
/* The ThreadProfile into which the results are eventually to be
|
||||
dumped. */
|
||||
ThreadProfile2* aProfile;
|
||||
ThreadProfile* aProfile;
|
||||
/* Pseudostack and other info, always present */
|
||||
ProfileEntry2 entsFixed[N_FIXED_PROF_ENTS];
|
||||
ProfileEntry entsFixed[N_FIXED_PROF_ENTS];
|
||||
ProfEntsPage* entsPages[N_PROF_ENT_PAGES];
|
||||
uintptr_t entsUsed;
|
||||
/* Do we also have data to do a native unwind? */
|
||||
@ -605,7 +605,7 @@ static UnwinderThreadBuffer* acquire_empty_buffer()
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
/* The calling thread owns the buffer, as denoted by its state being
|
||||
S_FILLING. So we can mess with it without further locking. */
|
||||
static void release_full_buffer(ThreadProfile2* aProfile,
|
||||
static void release_full_buffer(ThreadProfile* aProfile,
|
||||
UnwinderThreadBuffer* buff,
|
||||
void* /* ucontext_t*, really */ ucV )
|
||||
{
|
||||
@ -745,13 +745,13 @@ static void munmap_ProfEntsPage(ProfEntsPage* pep)
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
void
|
||||
utb_add_prof_ent(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
|
||||
utb_add_prof_ent(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry ent)
|
||||
{
|
||||
uintptr_t limit
|
||||
= N_FIXED_PROF_ENTS + (N_PROF_ENTS_PER_PAGE * N_PROF_ENT_PAGES);
|
||||
if (utb->entsUsed == limit) {
|
||||
/* We're full. Now what? */
|
||||
LOG("BPUnw: utb__addEntry: NO SPACE for ProfileEntry2; ignoring.");
|
||||
LOG("BPUnw: utb__addEntry: NO SPACE for ProfileEntry; ignoring.");
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(utb->entsUsed < limit);
|
||||
@ -773,7 +773,7 @@ utb_add_prof_ent(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
|
||||
pep = mmap_anon_ProfEntsPage();
|
||||
if (pep == ProfEntsPage_INVALID) {
|
||||
/* Urr, we ran out of memory. Now what? */
|
||||
LOG("BPUnw: utb__addEntry: MMAP FAILED for ProfileEntry2; ignoring.");
|
||||
LOG("BPUnw: utb__addEntry: MMAP FAILED for ProfileEntry; ignoring.");
|
||||
return;
|
||||
}
|
||||
utb->entsPages[j_div] = pep;
|
||||
@ -784,7 +784,7 @@ utb_add_prof_ent(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
|
||||
|
||||
|
||||
// misc helper
|
||||
static ProfileEntry2 utb_get_profent(UnwinderThreadBuffer* buff, uintptr_t i)
|
||||
static ProfileEntry utb_get_profent(UnwinderThreadBuffer* buff, uintptr_t i)
|
||||
{
|
||||
MOZ_ASSERT(i < buff->entsUsed);
|
||||
if (i < N_FIXED_PROF_ENTS) {
|
||||
@ -949,7 +949,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
|
||||
uintptr_t k;
|
||||
for (k = 0; k < buff->entsUsed; k++) {
|
||||
ProfileEntry2 ent = utb_get_profent(buff, k);
|
||||
ProfileEntry ent = utb_get_profent(buff, k);
|
||||
if (ent.is_ent_hint('N')) {
|
||||
need_native_unw = true;
|
||||
}
|
||||
@ -981,7 +981,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
Just copy to the output. */
|
||||
if (!need_native_unw && !have_P) {
|
||||
for (k = 0; k < buff->entsUsed; k++) {
|
||||
ProfileEntry2 ent = utb_get_profent(buff, k);
|
||||
ProfileEntry ent = utb_get_profent(buff, k);
|
||||
// action flush-hints
|
||||
if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
|
||||
// skip ones we can't copy
|
||||
@ -993,17 +993,17 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
else /* Native only-case. */
|
||||
if (need_native_unw && !have_P) {
|
||||
for (k = 0; k < buff->entsUsed; k++) {
|
||||
ProfileEntry2 ent = utb_get_profent(buff, k);
|
||||
ProfileEntry ent = utb_get_profent(buff, k);
|
||||
// action a native-unwind-now hint
|
||||
if (ent.is_ent_hint('N')) {
|
||||
MOZ_ASSERT(buff->haveNativeInfo);
|
||||
PCandSP* pairs = NULL;
|
||||
unsigned int nPairs = 0;
|
||||
do_breakpad_unwind_Buffer(&pairs, &nPairs, buff, oldest_ix);
|
||||
buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
|
||||
buff->aProfile->addTag( ProfileEntry('s', "(root)") );
|
||||
for (unsigned int i = 0; i < nPairs; i++) {
|
||||
buff->aProfile
|
||||
->addTag( ProfileEntry2('l', reinterpret_cast<void*>(pairs[i].pc)) );
|
||||
->addTag( ProfileEntry('l', reinterpret_cast<void*>(pairs[i].pc)) );
|
||||
}
|
||||
if (pairs)
|
||||
free(pairs);
|
||||
@ -1025,10 +1025,10 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
stack-pointer tags. Except, insert a sample-start tag when
|
||||
we see the start of the first pseudostack frame. */
|
||||
for (k = 0; k < buff->entsUsed; k++) {
|
||||
ProfileEntry2 ent = utb_get_profent(buff, k);
|
||||
ProfileEntry ent = utb_get_profent(buff, k);
|
||||
// We need to insert a sample-start tag before the first frame
|
||||
if (k == ix_first_hP) {
|
||||
buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
|
||||
buff->aProfile->addTag( ProfileEntry('s', "(root)") );
|
||||
}
|
||||
// action flush-hints
|
||||
if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
|
||||
@ -1056,7 +1056,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
|
||||
// Entries before the pseudostack frames
|
||||
for (k = 0; k < ix_first_hP; k++) {
|
||||
ProfileEntry2 ent = utb_get_profent(buff, k);
|
||||
ProfileEntry ent = utb_get_profent(buff, k);
|
||||
// action flush-hints
|
||||
if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
|
||||
// skip ones we can't copy
|
||||
@ -1066,7 +1066,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
}
|
||||
|
||||
// BEGIN merge
|
||||
buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
|
||||
buff->aProfile->addTag( ProfileEntry('s', "(root)") );
|
||||
unsigned int next_N = 0; // index in pairs[]
|
||||
unsigned int next_P = ix_first_hP; // index in buff profent array
|
||||
bool last_was_P = false;
|
||||
@ -1111,7 +1111,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
input, we must eventually find the hint-Q that marks
|
||||
the end of this frame's entries. */
|
||||
MOZ_ASSERT(m < buff->entsUsed);
|
||||
ProfileEntry2 ent = utb_get_profent(buff, m);
|
||||
ProfileEntry ent = utb_get_profent(buff, m);
|
||||
if (ent.is_ent_hint('Q'))
|
||||
break;
|
||||
if (ent.is_ent('S')) {
|
||||
@ -1136,7 +1136,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
unsigned int m = next_P + 1;
|
||||
while (true) {
|
||||
MOZ_ASSERT(m < buff->entsUsed);
|
||||
ProfileEntry2 ent = utb_get_profent(buff, m);
|
||||
ProfileEntry ent = utb_get_profent(buff, m);
|
||||
if (ent.is_ent_hint('Q')) {
|
||||
next_P = m + 1;
|
||||
break;
|
||||
@ -1151,7 +1151,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
}
|
||||
} else {
|
||||
buff->aProfile
|
||||
->addTag( ProfileEntry2('l', reinterpret_cast<void*>(pairs[next_N].pc)) );
|
||||
->addTag( ProfileEntry('l', reinterpret_cast<void*>(pairs[next_N].pc)) );
|
||||
next_N++;
|
||||
}
|
||||
/* Remember what we chose, for next time. */
|
||||
@ -1164,7 +1164,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
|
||||
// Entries after the pseudostack frames
|
||||
for (k = ix_last_hQ+1; k < buff->entsUsed; k++) {
|
||||
ProfileEntry2 ent = utb_get_profent(buff, k);
|
||||
ProfileEntry ent = utb_get_profent(buff, k);
|
||||
// action flush-hints
|
||||
if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
|
||||
// skip ones we can't copy
|
||||
@ -1182,7 +1182,7 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
bool show = true;
|
||||
if (show) LOG("----------------");
|
||||
for (k = 0; k < buff->entsUsed; k++) {
|
||||
ProfileEntry2 ent = utb_get_profent(buff, k);
|
||||
ProfileEntry ent = utb_get_profent(buff, k);
|
||||
if (show) ent.log();
|
||||
if (ent.is_ent_hint('F')) {
|
||||
/* This is a flush-hint */
|
||||
@ -1194,10 +1194,10 @@ static void* unwind_thr_fn(void* exit_nowV)
|
||||
PCandSP* pairs = NULL;
|
||||
unsigned int nPairs = 0;
|
||||
do_breakpad_unwind_Buffer(&pairs, &nPairs, buff, oldest_ix);
|
||||
buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
|
||||
buff->aProfile->addTag( ProfileEntry('s', "(root)") );
|
||||
for (unsigned int i = 0; i < nPairs; i++) {
|
||||
buff->aProfile
|
||||
->addTag( ProfileEntry2('l', reinterpret_cast<void*>(pairs[i].pc)) );
|
||||
->addTag( ProfileEntry('l', reinterpret_cast<void*>(pairs[i].pc)) );
|
||||
}
|
||||
if (pairs)
|
||||
free(pairs);
|
||||
|
@ -7,7 +7,7 @@
|
||||
#define MOZ_UNWINDER_THREAD_2_H
|
||||
|
||||
#include "GeckoProfilerImpl.h"
|
||||
#include "ProfileEntry2.h"
|
||||
#include "ProfileEntry.h"
|
||||
|
||||
/* Top level exports of UnwinderThread.cpp. */
|
||||
|
||||
@ -18,11 +18,11 @@ typedef
|
||||
UnwinderThreadBuffer;
|
||||
|
||||
// RUNS IN SIGHANDLER CONTEXT
|
||||
// Called in the sampled thread (signal) context. Adds a ProfileEntry2
|
||||
// Called in the sampled thread (signal) context. Adds a ProfileEntry
|
||||
// into an UnwinderThreadBuffer that the thread has previously obtained
|
||||
// by a call to utb__acquire_empty_buffer.
|
||||
void utb__addEntry(/*MOD*/UnwinderThreadBuffer* utb,
|
||||
ProfileEntry2 ent);
|
||||
ProfileEntry ent);
|
||||
|
||||
// Create the unwinder thread. At the moment there can be only one.
|
||||
void uwt__init();
|
||||
@ -53,7 +53,7 @@ UnwinderThreadBuffer* uwt__acquire_empty_buffer();
|
||||
// it is assumed to point to a ucontext_t* that holds the initial
|
||||
// register state for the unwind. The results of all of this are
|
||||
// dumped into |aProfile| (by the unwinder thread, not the calling thread).
|
||||
void uwt__release_full_buffer(ThreadProfile2* aProfile,
|
||||
void uwt__release_full_buffer(ThreadProfile* aProfile,
|
||||
UnwinderThreadBuffer* utb,
|
||||
void* /* ucontext_t*, really */ ucV);
|
||||
|
||||
|
451
tools/profiler/platform.cpp
Normal file
451
tools/profiler/platform.cpp
Normal file
@ -0,0 +1,451 @@
|
||||
/* 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/. */
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <errno.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "prenv.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "PseudoStack.h"
|
||||
#include "TableTicker.h"
|
||||
#include "UnwinderThread2.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "mozilla/Services.h"
|
||||
|
||||
mozilla::ThreadLocal<PseudoStack *> tlsPseudoStack;
|
||||
mozilla::ThreadLocal<TableTicker *> tlsTicker;
|
||||
// We need to track whether we've been initialized otherwise
|
||||
// we end up using tlsStack without initializing it.
|
||||
// Because tlsStack is totally opaque to us we can't reuse
|
||||
// it as the flag itself.
|
||||
bool stack_key_initialized;
|
||||
|
||||
TimeStamp sLastTracerEvent; // is raced on
|
||||
int sFrameNumber = 0;
|
||||
int sLastFrameNumber = 0;
|
||||
|
||||
/* used to keep track of the last event that we sampled during */
|
||||
unsigned int sLastSampledEventGeneration = 0;
|
||||
|
||||
/* a counter that's incremented everytime we get responsiveness event
|
||||
* note: it might also be worth tracking everytime we go around
|
||||
* the event loop */
|
||||
unsigned int sCurrentEventGeneration = 0;
|
||||
/* we don't need to worry about overflow because we only treat the
|
||||
* case of them being the same as special. i.e. we only run into
|
||||
* a problem if 2^32 events happen between samples that we need
|
||||
* to know are associated with different events */
|
||||
|
||||
bool sps_version2()
|
||||
{
|
||||
static int version = 0; // Raced on, potentially
|
||||
|
||||
if (version == 0) {
|
||||
bool allow2 = false; // Is v2 allowable on this platform?
|
||||
# if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||||
|| defined(SPS_PLAT_x86_linux)
|
||||
allow2 = true;
|
||||
# elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin) \
|
||||
|| defined(SPS_PLAT_x86_windows) || defined(SPS_PLAT_x86_android) \
|
||||
|| defined(SPS_PLAT_amd64_windows)
|
||||
allow2 = false;
|
||||
# else
|
||||
# error "Unknown platform"
|
||||
# endif
|
||||
|
||||
bool req2 = PR_GetEnv("MOZ_PROFILER_NEW") != NULL; // Has v2 been requested?
|
||||
|
||||
bool elfhackd = false;
|
||||
# if defined(USE_ELF_HACK)
|
||||
bool elfhackd = true;
|
||||
# endif
|
||||
|
||||
if (req2 && allow2) {
|
||||
version = 2;
|
||||
LOG("------------------- MOZ_PROFILER_NEW set -------------------");
|
||||
} else if (req2 && !allow2) {
|
||||
version = 1;
|
||||
LOG("--------------- MOZ_PROFILER_NEW requested, ----------------");
|
||||
LOG("---------- but is not available on this platform -----------");
|
||||
} else if (req2 && elfhackd) {
|
||||
version = 1;
|
||||
LOG("--------------- MOZ_PROFILER_NEW requested, ----------------");
|
||||
LOG("--- but this build was not done with --disable-elf-hack ----");
|
||||
} else {
|
||||
version = 1;
|
||||
LOG("----------------- MOZ_PROFILER_NEW not set -----------------");
|
||||
}
|
||||
}
|
||||
return version == 2;
|
||||
}
|
||||
|
||||
static inline const char* name_UnwMode(UnwMode m)
|
||||
{
|
||||
switch (m) {
|
||||
case UnwINVALID: return "invalid";
|
||||
case UnwNATIVE: return "native";
|
||||
case UnwPSEUDO: return "pseudo";
|
||||
case UnwCOMBINED: return "combined";
|
||||
default: return "??name_UnwMode??";
|
||||
}
|
||||
}
|
||||
|
||||
// Read env vars at startup, so as to set sUnwindMode and sInterval.
|
||||
void read_env_vars()
|
||||
{
|
||||
bool nativeAvail = false;
|
||||
# if defined(HAVE_NATIVE_UNWIND)
|
||||
nativeAvail = true;
|
||||
# endif
|
||||
|
||||
MOZ_ASSERT(sUnwindMode == UnwINVALID);
|
||||
MOZ_ASSERT(sUnwindInterval == 0);
|
||||
|
||||
/* Set defaults */
|
||||
sUnwindMode = nativeAvail ? UnwCOMBINED : UnwPSEUDO;
|
||||
sUnwindInterval = 0; /* We'll have to look elsewhere */
|
||||
|
||||
const char* strM = PR_GetEnv("MOZ_PROFILER_MODE");
|
||||
const char* strI = PR_GetEnv("MOZ_PROFILER_INTERVAL");
|
||||
|
||||
if (strM) {
|
||||
if (0 == strcmp(strM, "pseudo"))
|
||||
sUnwindMode = UnwPSEUDO;
|
||||
else if (0 == strcmp(strM, "native") && nativeAvail)
|
||||
sUnwindMode = UnwNATIVE;
|
||||
else if (0 == strcmp(strM, "combined") && nativeAvail)
|
||||
sUnwindMode = UnwCOMBINED;
|
||||
else goto usage;
|
||||
}
|
||||
|
||||
if (strI) {
|
||||
errno = 0;
|
||||
long int n = strtol(strI, (char**)NULL, 10);
|
||||
if (errno == 0 && n >= 1 && n <= 1000) {
|
||||
sUnwindInterval = n;
|
||||
}
|
||||
else goto usage;
|
||||
}
|
||||
|
||||
goto out;
|
||||
|
||||
usage:
|
||||
LOG( "SPS: ");
|
||||
LOG( "SPS: Environment variable usage:");
|
||||
LOG( "SPS: ");
|
||||
LOG( "SPS: MOZ_PROFILER_MODE=native for native unwind only");
|
||||
LOG( "SPS: MOZ_PROFILER_MODE=pseudo for pseudo unwind only");
|
||||
LOG( "SPS: MOZ_PROFILER_MODE=combined for combined native & pseudo unwind");
|
||||
LOG( "SPS: If unset, default is 'combined' on native-capable");
|
||||
LOG( "SPS: platforms, 'pseudo' on others.");
|
||||
LOG( "SPS: ");
|
||||
LOG( "SPS: MOZ_PROFILER_INTERVAL=<number> (milliseconds, 1 to 1000)");
|
||||
LOG( "SPS: If unset, platform default is used.");
|
||||
LOG( "SPS: ");
|
||||
LOGF("SPS: This platform %s native unwinding.",
|
||||
nativeAvail ? "supports" : "does not support");
|
||||
LOG( "SPS: ");
|
||||
/* Re-set defaults */
|
||||
sUnwindMode = nativeAvail ? UnwCOMBINED : UnwPSEUDO;
|
||||
sUnwindInterval = 0; /* We'll have to look elsewhere */
|
||||
|
||||
out:
|
||||
LOG( "SPS:");
|
||||
LOGF("SPS: Unwind mode = %s", name_UnwMode(sUnwindMode));
|
||||
LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")",
|
||||
(int)sUnwindInterval);
|
||||
LOG( "SPS: Use env var MOZ_PROFILER_MODE=help for further information.");
|
||||
LOG( "SPS:");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN externally visible functions
|
||||
|
||||
void mozilla_sampler_init()
|
||||
{
|
||||
if (stack_key_initialized)
|
||||
return;
|
||||
|
||||
LOG("BEGIN mozilla_sampler_init");
|
||||
if (!tlsPseudoStack.init() || !tlsTicker.init()) {
|
||||
LOG("Failed to init.");
|
||||
return;
|
||||
}
|
||||
stack_key_initialized = true;
|
||||
|
||||
PseudoStack *stack = new PseudoStack();
|
||||
tlsPseudoStack.set(stack);
|
||||
|
||||
if (sps_version2()) {
|
||||
// Read mode settings from MOZ_PROFILER_MODE and interval
|
||||
// settings from MOZ_PROFILER_INTERVAL.
|
||||
read_env_vars();
|
||||
|
||||
// Create the unwinder thread. ATM there is only one.
|
||||
uwt__init();
|
||||
|
||||
# if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||||
|| defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android) \
|
||||
|| defined(SPS_PLAT_x86_windows) || defined(SPS_PLAT_amd64_windows) /* no idea if windows is correct */
|
||||
// On Linuxes, register this thread (temporarily) for profiling
|
||||
int aLocal;
|
||||
uwt__register_thread_for_profiling( &aLocal );
|
||||
# elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin)
|
||||
// Registration is done in platform-macos.cc
|
||||
# else
|
||||
# error "Unknown plat"
|
||||
# endif
|
||||
}
|
||||
|
||||
// Allow the profiler to be started using signals
|
||||
OS::RegisterStartHandler();
|
||||
|
||||
// We can't open pref so we use an environment variable
|
||||
// to know if we should trigger the profiler on startup
|
||||
// NOTE: Default
|
||||
const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP");
|
||||
if (!val || !*val) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* features[] = {"js"
|
||||
, "leaf"
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
, "stackwalk"
|
||||
#endif
|
||||
};
|
||||
profiler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
|
||||
features, sizeof(features)/sizeof(const char*));
|
||||
LOG("END mozilla_sampler_init");
|
||||
}
|
||||
|
||||
void mozilla_sampler_shutdown()
|
||||
{
|
||||
// Save the profile on shutdown if requested.
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (t) {
|
||||
const char *val = PR_GetEnv("MOZ_PROFILER_SHUTDOWN");
|
||||
if (val) {
|
||||
std::ofstream stream;
|
||||
stream.open(val);
|
||||
if (stream.is_open()) {
|
||||
t->ToStreamAsJSON(stream);
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shut down and reap the unwinder thread. We have to do this
|
||||
// before stopping the sampler, so as to guarantee that the unwinder
|
||||
// thread doesn't try to access memory that the subsequent call to
|
||||
// mozilla_sampler_stop causes to be freed.
|
||||
if (sps_version2()) {
|
||||
uwt__deinit();
|
||||
}
|
||||
|
||||
profiler_stop();
|
||||
// We can't delete the Stack because we can be between a
|
||||
// sampler call_enter/call_exit point.
|
||||
// TODO Need to find a safe time to delete Stack
|
||||
}
|
||||
|
||||
void mozilla_sampler_save()
|
||||
{
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
|
||||
t->RequestSave();
|
||||
// We're on the main thread already so we don't
|
||||
// have to wait to handle the save request.
|
||||
t->HandleSaveRequest();
|
||||
}
|
||||
|
||||
char* mozilla_sampler_get_profile()
|
||||
{
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::stringstream profile;
|
||||
t->SetPaused(true);
|
||||
profile << *(t->GetPrimaryThreadProfile());
|
||||
t->SetPaused(false);
|
||||
|
||||
std::string profileString = profile.str();
|
||||
char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) );
|
||||
strcpy(rtn, profileString.c_str());
|
||||
return rtn;
|
||||
}
|
||||
|
||||
JSObject *mozilla_sampler_get_profile_data(JSContext *aCx)
|
||||
{
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return t->ToJSObject(aCx);
|
||||
}
|
||||
|
||||
|
||||
const char** mozilla_sampler_get_features()
|
||||
{
|
||||
static const char* features[] = {
|
||||
#if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND)
|
||||
"stackwalk",
|
||||
#endif
|
||||
#if defined(ENABLE_SPS_LEAF_DATA)
|
||||
"leaf",
|
||||
#endif
|
||||
"jank",
|
||||
"js",
|
||||
NULL
|
||||
};
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
// Values are only honored on the first start
|
||||
void mozilla_sampler_start(int aProfileEntries, int aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount)
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
profiler_init();
|
||||
|
||||
/* If the sampling interval was set using env vars, use that
|
||||
in preference to anything else. */
|
||||
if (sUnwindInterval > 0)
|
||||
aInterval = sUnwindInterval;
|
||||
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the current state if the profiler is running
|
||||
profiler_stop();
|
||||
|
||||
TableTicker* t;
|
||||
if (sps_version2()) {
|
||||
t = new BreakpadSampler(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
|
||||
aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
|
||||
stack, aFeatures, aFeatureCount);
|
||||
} else {
|
||||
t = new TableTicker(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
|
||||
aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
|
||||
stack, aFeatures, aFeatureCount);
|
||||
}
|
||||
tlsTicker.set(t);
|
||||
t->Start();
|
||||
if (t->ProfileJS())
|
||||
stack->enableJSSampling();
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-started", nullptr);
|
||||
}
|
||||
|
||||
void mozilla_sampler_stop()
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
profiler_init();
|
||||
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool disableJS = t->ProfileJS();
|
||||
|
||||
t->Stop();
|
||||
delete t;
|
||||
tlsTicker.set(NULL);
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
ASSERT(stack != NULL);
|
||||
|
||||
if (disableJS)
|
||||
stack->disableJSSampling();
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
|
||||
}
|
||||
|
||||
bool mozilla_sampler_is_active()
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
profiler_init();
|
||||
|
||||
TableTicker *t = tlsTicker.get();
|
||||
if (!t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return t->IsActive();
|
||||
}
|
||||
|
||||
static double sResponsivenessTimes[100];
|
||||
static unsigned int sResponsivenessLoc = 0;
|
||||
void mozilla_sampler_responsiveness(const TimeStamp& aTime)
|
||||
{
|
||||
if (!sLastTracerEvent.IsNull()) {
|
||||
if (sResponsivenessLoc == 100) {
|
||||
for(size_t i = 0; i < 100-1; i++) {
|
||||
sResponsivenessTimes[i] = sResponsivenessTimes[i+1];
|
||||
}
|
||||
sResponsivenessLoc--;
|
||||
}
|
||||
TimeDuration delta = aTime - sLastTracerEvent;
|
||||
sResponsivenessTimes[sResponsivenessLoc++] = delta.ToMilliseconds();
|
||||
}
|
||||
sCurrentEventGeneration++;
|
||||
|
||||
sLastTracerEvent = aTime;
|
||||
}
|
||||
|
||||
const double* mozilla_sampler_get_responsiveness()
|
||||
{
|
||||
return sResponsivenessTimes;
|
||||
}
|
||||
|
||||
void mozilla_sampler_frame_number(int frameNumber)
|
||||
{
|
||||
sFrameNumber = frameNumber;
|
||||
}
|
||||
|
||||
void mozilla_sampler_print_location2()
|
||||
{
|
||||
// FIXME
|
||||
}
|
||||
|
||||
void mozilla_sampler_lock()
|
||||
{
|
||||
profiler_stop();
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-locked", nullptr);
|
||||
}
|
||||
|
||||
void mozilla_sampler_unlock()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os)
|
||||
os->NotifyObservers(nullptr, "profiler-unlocked", nullptr);
|
||||
}
|
||||
|
||||
// END externally visible functions
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "mozilla/Util.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "v8-support.h"
|
||||
#include <vector>
|
||||
#define ASSERT(a) MOZ_ASSERT(a)
|
||||
@ -190,7 +191,22 @@ class Thread {
|
||||
DISALLOW_COPY_AND_ASSIGN(Thread);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// HAVE_NATIVE_UNWIND
|
||||
//
|
||||
// Pseudo backtraces are available on all platforms. Native
|
||||
// backtraces are available only on selected platforms. Breakpad is
|
||||
// the only supported native unwinder. HAVE_NATIVE_UNWIND is set at
|
||||
// build time to indicate whether native unwinding is possible on this
|
||||
// platform. The actual unwind mode currently in use is stored in
|
||||
// sUnwindMode.
|
||||
|
||||
#undef HAVE_NATIVE_UNWIND
|
||||
#if defined(MOZ_PROFILING) \
|
||||
&& (defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||||
|| defined(SPS_PLAT_x86_linux))
|
||||
# define HAVE_NATIVE_UNWIND
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sampler
|
||||
|
Loading…
Reference in New Issue
Block a user