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:
Benoit Girard 2013-03-25 17:57:28 -04:00
parent 74cbc7eacb
commit 68d10b4fe9
14 changed files with 1231 additions and 2046 deletions

View 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
////////////////////////////////////////////////////////////////////////

View File

@ -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();

View File

@ -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

View File

@ -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 \

View File

@ -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
////////////////////////////////////////////////////////////////////////

View File

@ -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 */

View 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

View File

@ -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);
}

View 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

View File

@ -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);

View File

@ -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
View 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
////////////////////////////////////////////////////////////////////////

View File

@ -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