gecko/tools/profiler/BreakpadSampler.cpp

351 lines
12 KiB
C++

/* -*- 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 <ostream>
#include <fstream>
#include <sstream>
// Profiler
#include "PlatformMacros.h"
#include "GeckoProfiler.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 "SyncProfile.h"
#include "SaveProfileTask.h"
#include "UnwinderThread2.h"
#include "TableTicker.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 "js/OldDebugAPI.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;
int sUnwindStackScan = 0;
int sProfileEntries = 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.isJs()) {
if (!entry.pc()) {
// The JIT only allows the top-most entry to have a nullptr 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(nullptr, entry.script(), jspc);
}
}
} else {
lineno = JS_PCToLineNumber(nullptr, 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
static
void populateBuffer(UnwinderThreadBuffer* utb, TickSample* sample,
UTB_RELEASE_FUNC releaseFunction, bool jankOnly)
{
ThreadProfile& sampledThreadProfile = *sample->threadProfile;
PseudoStack* stack = sampledThreadProfile.GetPseudoStack();
/* Manufacture the ProfileEntries that we will give to the unwinder
thread, and park them in |utb|. */
bool recordSample = true;
/* Don't process the PeudoStack's markers or honour jankOnly if we're
immediately sampling the current thread. */
if (!sample->isSamplingCurrentThread) {
// LinkedUWTBuffers before markers
UWTBufferLinkedList* syncBufs = stack->getLinkedUWTBuffers();
while (syncBufs && syncBufs->peek()) {
LinkedUWTBuffer* syncBuf = syncBufs->popHead();
utb__addEntry(utb, ProfileEntry('B', syncBuf->GetBuffer()));
}
// Marker(s) come before the sample
ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers();
while (pendingMarkersList && pendingMarkersList->peek()) {
ProfilerMarker* marker = pendingMarkersList->popHead();
stack->addStoredMarker(marker);
utb__addEntry( utb, ProfileEntry('m', marker) );
}
stack->updateGeneration(sampledThreadProfile.GetGenerationID());
if (jankOnly) {
// 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
sampledThreadProfile.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', static_cast<float>(delta.ToMilliseconds())) );
}
if (sample) {
TimeDuration delta = sample->timestamp - sStartTime;
utb__addEntry( utb, ProfileEntry('t', static_cast<float>(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 = nullptr;
# else
# error "Unsupported platform"
# endif
releaseFunction(&sampledThreadProfile, utb, ucV);
} else {
releaseFunction(&sampledThreadProfile, utb, nullptr);
}
}
static
void sampleCurrent(TickSample* sample)
{
// This variant requires sample->threadProfile to be set
MOZ_ASSERT(sample->threadProfile);
LinkedUWTBuffer* syncBuf = utb__acquire_sync_buffer(tlsStackTop.get());
if (!syncBuf) {
return;
}
SyncProfile* syncProfile = sample->threadProfile->AsSyncProfile();
MOZ_ASSERT(syncProfile);
if (!syncProfile->SetUWTBuffer(syncBuf)) {
utb__release_sync_buffer(syncBuf);
return;
}
UnwinderThreadBuffer* utb = syncBuf->GetBuffer();
populateBuffer(utb, sample, &utb__finish_sync_buffer, false);
}
// RUNS IN SIGHANDLER CONTEXT
void TableTicker::UnwinderTick(TickSample* sample)
{
if (sample->isSamplingCurrentThread) {
sampleCurrent(sample);
return;
}
if (!sample->threadProfile) {
// Platform doesn't support multithread, so use the main thread profile we created
sample->threadProfile = GetPrimaryThreadProfile();
}
/* 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;
populateBuffer(utb, sample, &uwt__release_full_buffer, mJankOnly);
}
// END take samples
////////////////////////////////////////////////////////////////////////