michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // System michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: // Profiler michael@0: #include "PlatformMacros.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "platform.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "prenv.h" michael@0: #include "shared-libraries.h" michael@0: #include "mozilla/StackWalk.h" michael@0: #include "ProfileEntry.h" michael@0: #include "SyncProfile.h" michael@0: #include "SaveProfileTask.h" michael@0: #include "UnwinderThread2.h" michael@0: #include "TableTicker.h" michael@0: michael@0: // Meta michael@0: #include "nsXPCOM.h" michael@0: #include "nsXPCOMCID.h" michael@0: #include "nsIHttpProtocolHandler.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIXULRuntime.h" michael@0: #include "nsIXULAppInfo.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: // JS michael@0: #include "js/OldDebugAPI.h" michael@0: michael@0: // This file's exports are listed in GeckoProfilerImpl.h. michael@0: michael@0: /* These will be set to something sensible before we take the first michael@0: sample. */ michael@0: UnwMode sUnwindMode = UnwINVALID; michael@0: int sUnwindInterval = 0; michael@0: int sUnwindStackScan = 0; michael@0: int sProfileEntries = 0; michael@0: michael@0: using std::string; michael@0: using namespace mozilla; michael@0: michael@0: #if _MSC_VER michael@0: #define snprintf _snprintf michael@0: #endif michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // BEGIN take samples. michael@0: // Everything in this section RUNS IN SIGHANDLER CONTEXT michael@0: michael@0: // RUNS IN SIGHANDLER CONTEXT michael@0: static michael@0: void genProfileEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, michael@0: volatile StackEntry &entry, michael@0: PseudoStack *stack, void *lastpc) michael@0: { michael@0: int lineno = -1; michael@0: michael@0: // Add a pseudostack-entry start label michael@0: utb__addEntry( utb, ProfileEntry('h', 'P') ); michael@0: // And the SP value, if it is non-zero michael@0: if (entry.stackAddress() != 0) { michael@0: utb__addEntry( utb, ProfileEntry('S', entry.stackAddress()) ); michael@0: } michael@0: michael@0: // First entry has tagName 's' (start) michael@0: // Check for magic pointer bit 1 to indicate copy michael@0: const char* sampleLabel = entry.label(); michael@0: if (entry.isCopyLabel()) { michael@0: // Store the string using 1 or more 'd' (dynamic) tags michael@0: // that will happen to the preceding tag michael@0: michael@0: utb__addEntry( utb, ProfileEntry('c', "") ); michael@0: // Add one to store the null termination michael@0: size_t strLen = strlen(sampleLabel) + 1; michael@0: for (size_t j = 0; j < strLen;) { michael@0: // Store as many characters in the void* as the platform allows michael@0: char text[sizeof(void*)]; michael@0: for (size_t pos = 0; pos < sizeof(void*) && j+pos < strLen; pos++) { michael@0: text[pos] = sampleLabel[j+pos]; michael@0: } michael@0: j += sizeof(void*)/sizeof(char); michael@0: // Cast to *((void**) to pass the text data to a void* michael@0: utb__addEntry( utb, ProfileEntry('d', *((void**)(&text[0]))) ); michael@0: } michael@0: if (entry.js()) { michael@0: if (!entry.pc()) { michael@0: // The JIT only allows the top-most entry to have a nullptr pc michael@0: MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]); michael@0: // If stack-walking was disabled, then that's just unfortunate michael@0: if (lastpc) { michael@0: jsbytecode *jspc = js::ProfilingGetPC(stack->mRuntime, entry.script(), michael@0: lastpc); michael@0: if (jspc) { michael@0: lineno = JS_PCToLineNumber(nullptr, entry.script(), jspc); michael@0: } michael@0: } michael@0: } else { michael@0: lineno = JS_PCToLineNumber(nullptr, entry.script(), entry.pc()); michael@0: } michael@0: } else { michael@0: lineno = entry.line(); michael@0: } michael@0: } else { michael@0: utb__addEntry( utb, ProfileEntry('c', sampleLabel) ); michael@0: lineno = entry.line(); michael@0: } michael@0: if (lineno != -1) { michael@0: utb__addEntry( utb, ProfileEntry('n', lineno) ); michael@0: } michael@0: michael@0: // Add a pseudostack-entry end label michael@0: utb__addEntry( utb, ProfileEntry('h', 'Q') ); michael@0: } michael@0: michael@0: // RUNS IN SIGHANDLER CONTEXT michael@0: // Generate pseudo-backtrace entries and put them in |utb|, with michael@0: // the order outermost frame first. michael@0: void genPseudoBacktraceEntries(/*MODIFIED*/UnwinderThreadBuffer* utb, michael@0: PseudoStack *aStack, TickSample *sample) michael@0: { michael@0: // Call genProfileEntry to generate tags for each profile michael@0: // entry. Each entry will be bounded by a 'h' 'P' tag to michael@0: // mark the start and a 'h' 'Q' tag to mark the end. michael@0: uint32_t nInStack = aStack->stackSize(); michael@0: for (uint32_t i = 0; i < nInStack; i++) { michael@0: genProfileEntry(utb, aStack->mStack[i], aStack, nullptr); michael@0: } michael@0: # ifdef ENABLE_SPS_LEAF_DATA michael@0: if (sample) { michael@0: utb__addEntry( utb, ProfileEntry('l', (void*)sample->pc) ); michael@0: # ifdef ENABLE_ARM_LR_SAVING michael@0: utb__addEntry( utb, ProfileEntry('L', (void*)sample->lr) ); michael@0: # endif michael@0: } michael@0: # endif michael@0: } michael@0: michael@0: // RUNS IN SIGHANDLER CONTEXT michael@0: static michael@0: void populateBuffer(UnwinderThreadBuffer* utb, TickSample* sample, michael@0: UTB_RELEASE_FUNC releaseFunction, bool jankOnly) michael@0: { michael@0: ThreadProfile& sampledThreadProfile = *sample->threadProfile; michael@0: PseudoStack* stack = sampledThreadProfile.GetPseudoStack(); michael@0: michael@0: /* Manufacture the ProfileEntries that we will give to the unwinder michael@0: thread, and park them in |utb|. */ michael@0: bool recordSample = true; michael@0: michael@0: /* Don't process the PeudoStack's markers or honour jankOnly if we're michael@0: immediately sampling the current thread. */ michael@0: if (!sample->isSamplingCurrentThread) { michael@0: // LinkedUWTBuffers before markers michael@0: UWTBufferLinkedList* syncBufs = stack->getLinkedUWTBuffers(); michael@0: while (syncBufs && syncBufs->peek()) { michael@0: LinkedUWTBuffer* syncBuf = syncBufs->popHead(); michael@0: utb__addEntry(utb, ProfileEntry('B', syncBuf->GetBuffer())); michael@0: } michael@0: // Marker(s) come before the sample michael@0: ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers(); michael@0: while (pendingMarkersList && pendingMarkersList->peek()) { michael@0: ProfilerMarker* marker = pendingMarkersList->popHead(); michael@0: stack->addStoredMarker(marker); michael@0: utb__addEntry( utb, ProfileEntry('m', marker) ); michael@0: } michael@0: stack->updateGeneration(sampledThreadProfile.GetGenerationID()); michael@0: if (jankOnly) { michael@0: // if we are on a different event we can discard any temporary samples michael@0: // we've kept around michael@0: if (sLastSampledEventGeneration != sCurrentEventGeneration) { michael@0: // XXX: we also probably want to add an entry to the profile to help michael@0: // distinguish which samples are part of the same event. That, or record michael@0: // the event generation in each sample michael@0: sampledThreadProfile.erase(); michael@0: } michael@0: sLastSampledEventGeneration = sCurrentEventGeneration; michael@0: michael@0: recordSample = false; michael@0: // only record the events when we have a we haven't seen a tracer michael@0: // event for 100ms michael@0: if (!sLastTracerEvent.IsNull()) { michael@0: TimeDuration delta = sample->timestamp - sLastTracerEvent; michael@0: if (delta.ToMilliseconds() > 100.0) { michael@0: recordSample = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // JRS 2012-Sept-27: this logic used to involve mUseStackWalk. michael@0: // That should be reinstated, but for the moment, use the michael@0: // settings in sUnwindMode and sUnwindInterval. michael@0: // Add a native-backtrace request, or add pseudo backtrace entries, michael@0: // or both. michael@0: switch (sUnwindMode) { michael@0: case UnwNATIVE: /* Native only */ michael@0: // add a "do native stack trace now" hint. This will be actioned michael@0: // by the unwinder thread as it processes the entries in this michael@0: // sample. michael@0: utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'N'/*native-trace*/) ); michael@0: break; michael@0: case UnwPSEUDO: /* Pseudo only */ michael@0: /* Add into |utb|, the pseudo backtrace entries */ michael@0: genPseudoBacktraceEntries(utb, stack, sample); michael@0: break; michael@0: case UnwCOMBINED: /* Both Native and Pseudo */ michael@0: utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'N'/*native-trace*/) ); michael@0: genPseudoBacktraceEntries(utb, stack, sample); michael@0: break; michael@0: case UnwINVALID: michael@0: default: michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: if (recordSample) { michael@0: // add a "flush now" hint michael@0: utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'F'/*flush*/) ); michael@0: } michael@0: michael@0: // Add any extras michael@0: if (!sLastTracerEvent.IsNull() && sample) { michael@0: TimeDuration delta = sample->timestamp - sLastTracerEvent; michael@0: utb__addEntry( utb, ProfileEntry('r', static_cast(delta.ToMilliseconds())) ); michael@0: } michael@0: michael@0: if (sample) { michael@0: TimeDuration delta = sample->timestamp - sStartTime; michael@0: utb__addEntry( utb, ProfileEntry('t', static_cast(delta.ToMilliseconds())) ); michael@0: } michael@0: michael@0: if (sLastFrameNumber != sFrameNumber) { michael@0: utb__addEntry( utb, ProfileEntry('f', sFrameNumber) ); michael@0: sLastFrameNumber = sFrameNumber; michael@0: } michael@0: michael@0: /* So now we have, in |utb|, the complete set of entries we want to michael@0: push into the circular buffer. This may also include a 'h' 'F' michael@0: entry, which is "flush now" hint, and/or a 'h' 'N' entry, which michael@0: is a "generate a native backtrace and add it to the buffer right michael@0: now" hint. Hand them off to the helper thread, together with michael@0: stack and register context needed to do a native unwind, if that michael@0: is currently enabled. */ michael@0: michael@0: /* If a native unwind has been requested, we'll start it off using michael@0: the context obtained from the signal handler, to avoid the michael@0: problem of having to unwind through the signal frame itself. */ michael@0: michael@0: /* On Linux and Android, the initial register state is in the michael@0: supplied sample->context. But on MacOS it's not, so we have to michael@0: fake it up here (sigh). */ michael@0: if (sUnwindMode == UnwNATIVE || sUnwindMode == UnwCOMBINED) { michael@0: # if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \ michael@0: || defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android) michael@0: void* ucV = (void*)sample->context; michael@0: # elif defined(SPS_PLAT_amd64_darwin) michael@0: struct __darwin_mcontext64 mc; michael@0: memset(&mc, 0, sizeof(mc)); michael@0: ucontext_t uc; michael@0: memset(&uc, 0, sizeof(uc)); michael@0: uc.uc_mcontext = &mc; michael@0: mc.__ss.__rip = (uint64_t)sample->pc; michael@0: mc.__ss.__rsp = (uint64_t)sample->sp; michael@0: mc.__ss.__rbp = (uint64_t)sample->fp; michael@0: void* ucV = (void*)&uc; michael@0: # elif defined(SPS_PLAT_x86_darwin) michael@0: struct __darwin_mcontext32 mc; michael@0: memset(&mc, 0, sizeof(mc)); michael@0: ucontext_t uc; michael@0: memset(&uc, 0, sizeof(uc)); michael@0: uc.uc_mcontext = &mc; michael@0: mc.__ss.__eip = (uint32_t)sample->pc; michael@0: mc.__ss.__esp = (uint32_t)sample->sp; michael@0: mc.__ss.__ebp = (uint32_t)sample->fp; michael@0: void* ucV = (void*)&uc; michael@0: # elif defined(SPS_OS_windows) michael@0: /* Totally fake this up so it at least builds. No idea if we can michael@0: even ever get here on Windows. */ michael@0: void* ucV = nullptr; michael@0: # else michael@0: # error "Unsupported platform" michael@0: # endif michael@0: releaseFunction(&sampledThreadProfile, utb, ucV); michael@0: } else { michael@0: releaseFunction(&sampledThreadProfile, utb, nullptr); michael@0: } michael@0: } michael@0: michael@0: static michael@0: void sampleCurrent(TickSample* sample) michael@0: { michael@0: // This variant requires sample->threadProfile to be set michael@0: MOZ_ASSERT(sample->threadProfile); michael@0: LinkedUWTBuffer* syncBuf = utb__acquire_sync_buffer(tlsStackTop.get()); michael@0: if (!syncBuf) { michael@0: return; michael@0: } michael@0: SyncProfile* syncProfile = sample->threadProfile->AsSyncProfile(); michael@0: MOZ_ASSERT(syncProfile); michael@0: if (!syncProfile->SetUWTBuffer(syncBuf)) { michael@0: utb__release_sync_buffer(syncBuf); michael@0: return; michael@0: } michael@0: UnwinderThreadBuffer* utb = syncBuf->GetBuffer(); michael@0: populateBuffer(utb, sample, &utb__finish_sync_buffer, false); michael@0: } michael@0: michael@0: // RUNS IN SIGHANDLER CONTEXT michael@0: void TableTicker::UnwinderTick(TickSample* sample) michael@0: { michael@0: if (sample->isSamplingCurrentThread) { michael@0: sampleCurrent(sample); michael@0: return; michael@0: } michael@0: michael@0: if (!sample->threadProfile) { michael@0: // Platform doesn't support multithread, so use the main thread profile we created michael@0: sample->threadProfile = GetPrimaryThreadProfile(); michael@0: } michael@0: michael@0: /* Get hold of an empty inter-thread buffer into which to park michael@0: the ProfileEntries for this sample. */ michael@0: UnwinderThreadBuffer* utb = uwt__acquire_empty_buffer(); michael@0: michael@0: /* This could fail, if no buffers are currently available, in which michael@0: case we must give up right away. We cannot wait for a buffer to michael@0: become available, as that risks deadlock. */ michael@0: if (!utb) michael@0: return; michael@0: michael@0: populateBuffer(utb, sample, &uwt__release_full_buffer, mJankOnly); michael@0: } michael@0: michael@0: // END take samples michael@0: //////////////////////////////////////////////////////////////////////// michael@0: