diff options
Diffstat (limited to 'tools/profiler/core')
30 files changed, 0 insertions, 8953 deletions
diff --git a/tools/profiler/core/EHABIStackWalk.cpp b/tools/profiler/core/EHABIStackWalk.cpp deleted file mode 100644 index 76068cdea..000000000 --- a/tools/profiler/core/EHABIStackWalk.cpp +++ /dev/null @@ -1,678 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* - * This is an implementation of stack unwinding according to a subset - * of the ARM Exception Handling ABI, as described in: - * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf - * - * This handles only the ARM-defined "personality routines" (chapter - * 9), and don't track the value of FP registers, because profiling - * needs only chain of PC/SP values. - * - * Because the exception handling info may not be accurate for all - * possible places where an async signal could occur (e.g., in a - * prologue or epilogue), this bounds-checks all stack accesses. - * - * This file uses "struct" for structures in the exception tables and - * "class" otherwise. We should avoid violating the C++11 - * standard-layout rules in the former. - */ - -#include "EHABIStackWalk.h" - -#include "shared-libraries.h" -#include "platform.h" - -#include "mozilla/Atomics.h" -#include "mozilla/Attributes.h" -#include "mozilla/DebugOnly.h" -#include "mozilla/EndianUtils.h" - -#include <algorithm> -#include <elf.h> -#include <stdint.h> -#include <vector> -#include <string> - -#ifndef PT_ARM_EXIDX -#define PT_ARM_EXIDX 0x70000001 -#endif - -// Bug 1082817: ICS B2G has a buggy linker that doesn't always ensure -// that the EXIDX is sorted by address, as the spec requires. So in -// that case we build and sort an array of pointers into the index, -// and binary-search that; otherwise, we search the index in place -// (avoiding the time and space overhead of the indirection). -#if defined(ANDROID_VERSION) && ANDROID_VERSION < 16 -#define HAVE_UNSORTED_EXIDX -#endif - -namespace mozilla { - -struct PRel31 { - uint32_t mBits; - bool topBit() const { return mBits & 0x80000000; } - uint32_t value() const { return mBits & 0x7fffffff; } - int32_t offset() const { return (static_cast<int32_t>(mBits) << 1) >> 1; } - const void *compute() const { - return reinterpret_cast<const char *>(this) + offset(); - } -private: - PRel31(const PRel31 &copied) = delete; - PRel31() = delete; -}; - -struct EHEntry { - PRel31 startPC; - PRel31 exidx; -private: - EHEntry(const EHEntry &copied) = delete; - EHEntry() = delete; -}; - -class EHState { - // Note that any core register can be used as a "frame pointer" to - // influence the unwinding process, so this must track all of them. - uint32_t mRegs[16]; -public: - bool unwind(const EHEntry *aEntry, const void *stackBase); - uint32_t &operator[](int i) { return mRegs[i]; } - const uint32_t &operator[](int i) const { return mRegs[i]; } - EHState(const mcontext_t &); -}; - -enum { - R_SP = 13, - R_LR = 14, - R_PC = 15 -}; - -#ifdef HAVE_UNSORTED_EXIDX -class EHEntryHandle { - const EHEntry *mValue; -public: - EHEntryHandle(const EHEntry *aEntry) : mValue(aEntry) { } - const EHEntry *value() const { return mValue; } -}; - -bool operator<(const EHEntryHandle &lhs, const EHEntryHandle &rhs) { - return lhs.value()->startPC.compute() < rhs.value()->startPC.compute(); -} -#endif - -class EHTable { - uint32_t mStartPC; - uint32_t mEndPC; - uint32_t mLoadOffset; -#ifdef HAVE_UNSORTED_EXIDX - // In principle we should be able to binary-search the index section in - // place, but the ICS toolchain's linker is noncompliant and produces - // indices that aren't entirely sorted (e.g., libc). So we have this: - std::vector<EHEntryHandle> mEntries; - typedef std::vector<EHEntryHandle>::const_iterator EntryIterator; - EntryIterator entriesBegin() const { return mEntries.begin(); } - EntryIterator entriesEnd() const { return mEntries.end(); } - static const EHEntry* entryGet(EntryIterator aEntry) { - return aEntry->value(); - } -#else - typedef const EHEntry *EntryIterator; - EntryIterator mEntriesBegin, mEntriesEnd; - EntryIterator entriesBegin() const { return mEntriesBegin; } - EntryIterator entriesEnd() const { return mEntriesEnd; } - static const EHEntry* entryGet(EntryIterator aEntry) { return aEntry; } -#endif - std::string mName; -public: - EHTable(const void *aELF, size_t aSize, const std::string &aName); - const EHEntry *lookup(uint32_t aPC) const; - bool isValid() const { return entriesEnd() != entriesBegin(); } - const std::string &name() const { return mName; } - uint32_t startPC() const { return mStartPC; } - uint32_t endPC() const { return mEndPC; } - uint32_t loadOffset() const { return mLoadOffset; } -}; - -class EHAddrSpace { - std::vector<uint32_t> mStarts; - std::vector<EHTable> mTables; - static mozilla::Atomic<const EHAddrSpace*> sCurrent; -public: - explicit EHAddrSpace(const std::vector<EHTable>& aTables); - const EHTable *lookup(uint32_t aPC) const; - static void Update(); - static const EHAddrSpace *Get(); -}; - - -void EHABIStackWalkInit() -{ - EHAddrSpace::Update(); -} - -size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase, - void **aSPs, void **aPCs, const size_t aNumFrames) -{ - const EHAddrSpace *space = EHAddrSpace::Get(); - EHState state(aContext); - size_t count = 0; - - while (count < aNumFrames) { - uint32_t pc = state[R_PC], sp = state[R_SP]; - aPCs[count] = reinterpret_cast<void *>(pc); - aSPs[count] = reinterpret_cast<void *>(sp); - count++; - - if (!space) - break; - // TODO: cache these lookups. Binary-searching libxul is - // expensive (possibly more expensive than doing the actual - // unwind), and even a small cache should help. - const EHTable *table = space->lookup(pc); - if (!table) - break; - const EHEntry *entry = table->lookup(pc); - if (!entry) - break; - if (!state.unwind(entry, stackBase)) - break; - } - - return count; -} - - -class EHInterp { -public: - // Note that stackLimit is exclusive and stackBase is inclusive - // (i.e, stackLimit < SP <= stackBase), following the convention - // set by the AAPCS spec. - EHInterp(EHState &aState, const EHEntry *aEntry, - uint32_t aStackLimit, uint32_t aStackBase) - : mState(aState), - mStackLimit(aStackLimit), - mStackBase(aStackBase), - mNextWord(0), - mWordsLeft(0), - mFailed(false) - { - const PRel31 &exidx = aEntry->exidx; - uint32_t firstWord; - - if (exidx.mBits == 1) { // EXIDX_CANTUNWIND - mFailed = true; - return; - } - if (exidx.topBit()) { - firstWord = exidx.mBits; - } else { - mNextWord = reinterpret_cast<const uint32_t *>(exidx.compute()); - firstWord = *mNextWord++; - } - - switch (firstWord >> 24) { - case 0x80: // short - mWord = firstWord << 8; - mBytesLeft = 3; - break; - case 0x81: case 0x82: // long; catch descriptor size ignored - mWord = firstWord << 16; - mBytesLeft = 2; - mWordsLeft = (firstWord >> 16) & 0xff; - break; - default: - // unknown personality - mFailed = true; - } - } - - bool unwind(); - -private: - // TODO: GCC has been observed not CSEing repeated reads of - // mState[R_SP] with writes to mFailed between them, suggesting that - // it hasn't determined that they can't alias and is thus missing - // optimization opportunities. So, we may want to flatten EHState - // into this class; this may also make the code simpler. - EHState &mState; - uint32_t mStackLimit; - uint32_t mStackBase; - const uint32_t *mNextWord; - uint32_t mWord; - uint8_t mWordsLeft; - uint8_t mBytesLeft; - bool mFailed; - - enum { - I_ADDSP = 0x00, // 0sxxxxxx (subtract if s) - M_ADDSP = 0x80, - I_POPMASK = 0x80, // 1000iiii iiiiiiii (if any i set) - M_POPMASK = 0xf0, - I_MOVSP = 0x90, // 1001nnnn - M_MOVSP = 0xf0, - I_POPN = 0xa0, // 1010lnnn - M_POPN = 0xf0, - I_FINISH = 0xb0, // 10110000 - I_POPLO = 0xb1, // 10110001 0000iiii (if any i set) - I_ADDSPBIG = 0xb2, // 10110010 uleb128 - I_POPFDX = 0xb3, // 10110011 sssscccc - I_POPFDX8 = 0xb8, // 10111nnn - M_POPFDX8 = 0xf8, - // "Intel Wireless MMX" extensions omitted. - I_POPFDD = 0xc8, // 1100100h sssscccc - M_POPFDD = 0xfe, - I_POPFDD8 = 0xd0, // 11010nnn - M_POPFDD8 = 0xf8 - }; - - uint8_t next() { - if (mBytesLeft == 0) { - if (mWordsLeft == 0) { - return I_FINISH; - } - mWordsLeft--; - mWord = *mNextWord++; - mBytesLeft = 4; - } - mBytesLeft--; - mWord = (mWord << 8) | (mWord >> 24); // rotate - return mWord; - } - - uint32_t &vSP() { return mState[R_SP]; } - uint32_t *ptrSP() { return reinterpret_cast<uint32_t *>(vSP()); } - - void checkStackBase() { if (vSP() > mStackBase) mFailed = true; } - void checkStackLimit() { if (vSP() <= mStackLimit) mFailed = true; } - void checkStackAlign() { if ((vSP() & 3) != 0) mFailed = true; } - void checkStack() { - checkStackBase(); - checkStackLimit(); - checkStackAlign(); - } - - void popRange(uint8_t first, uint8_t last, uint16_t mask) { - bool hasSP = false; - uint32_t tmpSP; - if (mask == 0) - mFailed = true; - for (uint8_t r = first; r <= last; ++r) { - if (mask & 1) { - if (r == R_SP) { - hasSP = true; - tmpSP = *ptrSP(); - } else - mState[r] = *ptrSP(); - vSP() += 4; - checkStackBase(); - if (mFailed) - return; - } - mask >>= 1; - } - if (hasSP) { - vSP() = tmpSP; - checkStack(); - } - } -}; - - -bool EHState::unwind(const EHEntry *aEntry, const void *stackBasePtr) { - // The unwinding program cannot set SP to less than the initial value. - uint32_t stackLimit = mRegs[R_SP] - 4; - uint32_t stackBase = reinterpret_cast<uint32_t>(stackBasePtr); - EHInterp interp(*this, aEntry, stackLimit, stackBase); - return interp.unwind(); -} - -bool EHInterp::unwind() { - mState[R_PC] = 0; - checkStack(); - while (!mFailed) { - uint8_t insn = next(); -#if DEBUG_EHABI_UNWIND - LOGF("unwind insn = %02x", (unsigned)insn); -#endif - // Try to put the common cases first. - - // 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4 - // 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4 - if ((insn & M_ADDSP) == I_ADDSP) { - uint32_t offset = ((insn & 0x3f) << 2) + 4; - if (insn & 0x40) { - vSP() -= offset; - checkStackLimit(); - } else { - vSP() += offset; - checkStackBase(); - } - continue; - } - - // 10100nnn: Pop r4-r[4+nnn] - // 10101nnn: Pop r4-r[4+nnn], r14 - if ((insn & M_POPN) == I_POPN) { - uint8_t n = (insn & 0x07) + 1; - bool lr = insn & 0x08; - uint32_t *ptr = ptrSP(); - vSP() += (n + (lr ? 1 : 0)) * 4; - checkStackBase(); - for (uint8_t r = 4; r < 4 + n; ++r) - mState[r] = *ptr++; - if (lr) - mState[R_LR] = *ptr++; - continue; - } - - // 1011000: Finish - if (insn == I_FINISH) { - if (mState[R_PC] == 0) { - mState[R_PC] = mState[R_LR]; - // Non-standard change (bug 916106): Prevent the caller from - // re-using LR. Since the caller is by definition not a leaf - // routine, it will have to restore LR from somewhere to - // return to its own caller, so we can safely zero it here. - // This makes a difference only if an error in unwinding - // (e.g., caused by starting from within a prologue/epilogue) - // causes us to load a pointer to a leaf routine as LR; if we - // don't do something, we'll go into an infinite loop of - // "returning" to that same function. - mState[R_LR] = 0; - } - return true; - } - - // 1001nnnn: Set vsp = r[nnnn] - if ((insn & M_MOVSP) == I_MOVSP) { - vSP() = mState[insn & 0x0f]; - checkStack(); - continue; - } - - // 11001000 sssscccc: Pop VFP regs D[16+ssss]-D[16+ssss+cccc] (as FLDMFDD) - // 11001001 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDD) - if ((insn & M_POPFDD) == I_POPFDD) { - uint8_t n = (next() & 0x0f) + 1; - // Note: if the 16+ssss+cccc > 31, the encoding is reserved. - // As the space is currently unused, we don't try to check. - vSP() += 8 * n; - checkStackBase(); - continue; - } - - // 11010nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDD) - if ((insn & M_POPFDD8) == I_POPFDD8) { - uint8_t n = (insn & 0x07) + 1; - vSP() += 8 * n; - checkStackBase(); - continue; - } - - // 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2) - if (insn == I_ADDSPBIG) { - uint32_t acc = 0; - uint8_t shift = 0; - uint8_t byte; - do { - if (shift >= 32) - return false; - byte = next(); - acc |= (byte & 0x7f) << shift; - shift += 7; - } while (byte & 0x80); - uint32_t offset = 0x204 + (acc << 2); - // The calculations above could have overflowed. - // But the one we care about is this: - if (vSP() + offset < vSP()) - mFailed = true; - vSP() += offset; - // ...so that this is the only other check needed: - checkStackBase(); - continue; - } - - // 1000iiii iiiiiiii (i not all 0): Pop under masks {r15-r12}, {r11-r4} - if ((insn & M_POPMASK) == I_POPMASK) { - popRange(4, 15, ((insn & 0x0f) << 8) | next()); - continue; - } - - // 1011001 0000iiii (i not all 0): Pop under mask {r3-r0} - if (insn == I_POPLO) { - popRange(0, 3, next() & 0x0f); - continue; - } - - // 10110011 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDX) - if (insn == I_POPFDX) { - uint8_t n = (next() & 0x0f) + 1; - vSP() += 8 * n + 4; - checkStackBase(); - continue; - } - - // 10111nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDX) - if ((insn & M_POPFDX8) == I_POPFDX8) { - uint8_t n = (insn & 0x07) + 1; - vSP() += 8 * n + 4; - checkStackBase(); - continue; - } - - // unhandled instruction -#ifdef DEBUG_EHABI_UNWIND - LOGF("Unhandled EHABI instruction 0x%02x", insn); -#endif - mFailed = true; - } - return false; -} - - -bool operator<(const EHTable &lhs, const EHTable &rhs) { - return lhs.startPC() < rhs.startPC(); -} - -// Async signal unsafe. -EHAddrSpace::EHAddrSpace(const std::vector<EHTable>& aTables) - : mTables(aTables) -{ - std::sort(mTables.begin(), mTables.end()); - DebugOnly<uint32_t> lastEnd = 0; - for (std::vector<EHTable>::iterator i = mTables.begin(); - i != mTables.end(); ++i) { - MOZ_ASSERT(i->startPC() >= lastEnd); - mStarts.push_back(i->startPC()); - lastEnd = i->endPC(); - } -} - -const EHTable *EHAddrSpace::lookup(uint32_t aPC) const { - ptrdiff_t i = (std::upper_bound(mStarts.begin(), mStarts.end(), aPC) - - mStarts.begin()) - 1; - - if (i < 0 || aPC >= mTables[i].endPC()) - return 0; - return &mTables[i]; -} - - -const EHEntry *EHTable::lookup(uint32_t aPC) const { - MOZ_ASSERT(aPC >= mStartPC); - if (aPC >= mEndPC) - return nullptr; - - EntryIterator begin = entriesBegin(); - EntryIterator end = entriesEnd(); - MOZ_ASSERT(begin < end); - if (aPC < reinterpret_cast<uint32_t>(entryGet(begin)->startPC.compute())) - return nullptr; - - while (end - begin > 1) { -#ifdef EHABI_UNWIND_MORE_ASSERTS - if (entryGet(end - 1)->startPC.compute() - < entryGet(begin)->startPC.compute()) { - MOZ_CRASH("unsorted exidx"); - } -#endif - EntryIterator mid = begin + (end - begin) / 2; - if (aPC < reinterpret_cast<uint32_t>(entryGet(mid)->startPC.compute())) - end = mid; - else - begin = mid; - } - return entryGet(begin); -} - - -#if MOZ_LITTLE_ENDIAN -static const unsigned char hostEndian = ELFDATA2LSB; -#elif MOZ_BIG_ENDIAN -static const unsigned char hostEndian = ELFDATA2MSB; -#else -#error "No endian?" -#endif - -// Async signal unsafe: std::vector::reserve, std::string copy ctor. -EHTable::EHTable(const void *aELF, size_t aSize, const std::string &aName) - : mStartPC(~0), // largest uint32_t - mEndPC(0), -#ifndef HAVE_UNSORTED_EXIDX - mEntriesBegin(nullptr), - mEntriesEnd(nullptr), -#endif - mName(aName) -{ - const uint32_t base = reinterpret_cast<uint32_t>(aELF); - - if (aSize < sizeof(Elf32_Ehdr)) - return; - - const Elf32_Ehdr &file = *(reinterpret_cast<Elf32_Ehdr *>(base)); - if (memcmp(&file.e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 || - file.e_ident[EI_CLASS] != ELFCLASS32 || - file.e_ident[EI_DATA] != hostEndian || - file.e_ident[EI_VERSION] != EV_CURRENT || - file.e_ident[EI_OSABI] != ELFOSABI_SYSV || -#ifdef EI_ABIVERSION - file.e_ident[EI_ABIVERSION] != 0 || -#endif - file.e_machine != EM_ARM || - file.e_version != EV_CURRENT) - // e_flags? - return; - - MOZ_ASSERT(file.e_phoff + file.e_phnum * file.e_phentsize <= aSize); - const Elf32_Phdr *exidxHdr = 0, *zeroHdr = 0; - for (unsigned i = 0; i < file.e_phnum; ++i) { - const Elf32_Phdr &phdr = - *(reinterpret_cast<Elf32_Phdr *>(base + file.e_phoff - + i * file.e_phentsize)); - if (phdr.p_type == PT_ARM_EXIDX) { - exidxHdr = &phdr; - } else if (phdr.p_type == PT_LOAD) { - if (phdr.p_offset == 0) { - zeroHdr = &phdr; - } - if (phdr.p_flags & PF_X) { - mStartPC = std::min(mStartPC, phdr.p_vaddr); - mEndPC = std::max(mEndPC, phdr.p_vaddr + phdr.p_memsz); - } - } - } - if (!exidxHdr) - return; - if (!zeroHdr) - return; - mLoadOffset = base - zeroHdr->p_vaddr; - mStartPC += mLoadOffset; - mEndPC += mLoadOffset; - - // Create a sorted index of the index to work around linker bugs. - const EHEntry *startTable = - reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr); - const EHEntry *endTable = - reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr - + exidxHdr->p_memsz); -#ifdef HAVE_UNSORTED_EXIDX - mEntries.reserve(endTable - startTable); - for (const EHEntry *i = startTable; i < endTable; ++i) - mEntries.push_back(i); - std::sort(mEntries.begin(), mEntries.end()); -#else - mEntriesBegin = startTable; - mEntriesEnd = endTable; -#endif -} - - -mozilla::Atomic<const EHAddrSpace*> EHAddrSpace::sCurrent(nullptr); - -// Async signal safe; can fail if Update() hasn't returned yet. -const EHAddrSpace *EHAddrSpace::Get() { - return sCurrent; -} - -// Collect unwinding information from loaded objects. Calls after the -// first have no effect. Async signal unsafe. -void EHAddrSpace::Update() { - const EHAddrSpace *space = sCurrent; - if (space) - return; - - SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf(); - std::vector<EHTable> tables; - - for (size_t i = 0; i < info.GetSize(); ++i) { - const SharedLibrary &lib = info.GetEntry(i); - if (lib.GetOffset() != 0) - // TODO: if it has a name, and we haven't seen a mapping of - // offset 0 for that file, try opening it and reading the - // headers instead. The only thing I've seen so far that's - // linked so as to need that treatment is the dynamic linker - // itself. - continue; - EHTable tab(reinterpret_cast<const void *>(lib.GetStart()), - lib.GetEnd() - lib.GetStart(), lib.GetName()); - if (tab.isValid()) - tables.push_back(tab); - } - space = new EHAddrSpace(tables); - - if (!sCurrent.compareExchange(nullptr, space)) { - delete space; - space = sCurrent; - } -} - - -EHState::EHState(const mcontext_t &context) { -#ifdef linux - mRegs[0] = context.arm_r0; - mRegs[1] = context.arm_r1; - mRegs[2] = context.arm_r2; - mRegs[3] = context.arm_r3; - mRegs[4] = context.arm_r4; - mRegs[5] = context.arm_r5; - mRegs[6] = context.arm_r6; - mRegs[7] = context.arm_r7; - mRegs[8] = context.arm_r8; - mRegs[9] = context.arm_r9; - mRegs[10] = context.arm_r10; - mRegs[11] = context.arm_fp; - mRegs[12] = context.arm_ip; - mRegs[13] = context.arm_sp; - mRegs[14] = context.arm_lr; - mRegs[15] = context.arm_pc; -#else -# error "Unhandled OS for ARM EHABI unwinding" -#endif -} - -} // namespace mozilla - diff --git a/tools/profiler/core/EHABIStackWalk.h b/tools/profiler/core/EHABIStackWalk.h deleted file mode 100644 index 5529d9511..000000000 --- a/tools/profiler/core/EHABIStackWalk.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* - * This is an implementation of stack unwinding according to a subset - * of the ARM Exception Handling ABI; see the comment at the top of - * the .cpp file for details. - */ - -#ifndef mozilla_EHABIStackWalk_h__ -#define mozilla_EHABIStackWalk_h__ - -#include <stddef.h> -#include <ucontext.h> - -namespace mozilla { - -void EHABIStackWalkInit(); - -size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase, - void **aSPs, void **aPCs, size_t aNumFrames); - -} - -#endif diff --git a/tools/profiler/core/GeckoSampler.cpp b/tools/profiler/core/GeckoSampler.cpp deleted file mode 100644 index 4283542f6..000000000 --- a/tools/profiler/core/GeckoSampler.cpp +++ /dev/null @@ -1,1306 +0,0 @@ -/* -*- 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 <algorithm> -#include <string> -#include <stdio.h> -#include <fstream> -#include <sstream> -#include "GeckoProfiler.h" -#ifndef SPS_STANDALONE -#include "SaveProfileTask.h" -#include "nsThreadUtils.h" -#include "prenv.h" -#include "prtime.h" -#include "nsXULAppAPI.h" -#endif -#include "ProfileEntry.h" -#include "SyncProfile.h" -#include "platform.h" -#include "shared-libraries.h" -#include "mozilla/StackWalk.h" -#include "GeckoSampler.h" - -// JSON -#include "ProfileJSONWriter.h" - -#ifndef SPS_STANDALONE -// 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" -#include "PlatformMacros.h" -#include "nsTArray.h" - -#include "mozilla/ProfileGatherer.h" -#endif - -#if defined(SPS_OS_android) - #include "FennecJNIWrappers.h" -#endif - -#ifndef SPS_STANDALONE -// JS -#include "jsfriendapi.h" -#include "js/ProfilingFrameIterator.h" -#endif - -#if defined(MOZ_PROFILING) && (defined(XP_MACOSX) || defined(XP_WIN)) - #define USE_NS_STACKWALK -#endif - -#if defined(XP_WIN) -typedef CONTEXT tickcontext_t; -#elif defined(LINUX) -#include <ucontext.h> -typedef ucontext_t tickcontext_t; -#endif - -#if defined(LINUX) || defined(XP_MACOSX) -#include <sys/types.h> -pid_t gettid(); -#endif - -#if defined(__arm__) && defined(ANDROID) - // Should also work on ARM Linux, but not tested there yet. - #define USE_EHABI_STACKWALK -#endif -#ifdef USE_EHABI_STACKWALK - #include "EHABIStackWalk.h" -#endif - -#ifndef SPS_STANDALONE -#if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux) -# define USE_LUL_STACKWALK -# include "lul/LulMain.h" -# include "lul/platform-linux-lul.h" -#endif -#endif - -using std::string; -using namespace mozilla; - -#ifndef MAXPATHLEN - #ifdef PATH_MAX - #define MAXPATHLEN PATH_MAX - #elif defined(MAX_PATH) - #define MAXPATHLEN MAX_PATH - #elif defined(_MAX_PATH) - #define MAXPATHLEN _MAX_PATH - #elif defined(CCHMAXPATH) - #define MAXPATHLEN CCHMAXPATH - #else - #define MAXPATHLEN 1024 - #endif -#endif - -#ifdef MOZ_VALGRIND -# include <valgrind/memcheck.h> -#else -# define VALGRIND_MAKE_MEM_DEFINED(_addr,_len) ((void)0) -#endif - - -/////////////////////////////////////////////////////////////////////// -// BEGIN SaveProfileTask et al - -static void -AddSharedLibraryInfoToStream(std::ostream& aStream, const SharedLibrary& aLib) -{ - aStream << "{"; - aStream << "\"start\":" << aLib.GetStart(); - aStream << ",\"end\":" << aLib.GetEnd(); - aStream << ",\"offset\":" << aLib.GetOffset(); - aStream << ",\"name\":\"" << aLib.GetName() << "\""; - const std::string &breakpadId = aLib.GetBreakpadId(); - aStream << ",\"breakpadId\":\"" << breakpadId << "\""; -#ifdef XP_WIN - // FIXME: remove this XP_WIN code when the profiler plugin has switched to - // using breakpadId. - std::string pdbSignature = breakpadId.substr(0, 32); - std::string pdbAgeStr = breakpadId.substr(32, breakpadId.size() - 1); - - std::stringstream stream; - stream << pdbAgeStr; - - unsigned pdbAge; - stream << std::hex; - stream >> pdbAge; - -#ifdef DEBUG - std::ostringstream oStream; - oStream << pdbSignature << std::hex << std::uppercase << pdbAge; - MOZ_ASSERT(breakpadId == oStream.str()); -#endif - - aStream << ",\"pdbSignature\":\"" << pdbSignature << "\""; - aStream << ",\"pdbAge\":" << pdbAge; - aStream << ",\"pdbName\":\"" << aLib.GetName() << "\""; -#endif - aStream << "}"; -} - -std::string -GetSharedLibraryInfoStringInternal() -{ - SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf(); - if (info.GetSize() == 0) - return "[]"; - - std::ostringstream os; - os << "["; - AddSharedLibraryInfoToStream(os, info.GetEntry(0)); - - for (size_t i = 1; i < info.GetSize(); i++) { - os << ","; - AddSharedLibraryInfoToStream(os, info.GetEntry(i)); - } - - os << "]"; - return os.str(); -} - -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; -} - -GeckoSampler::GeckoSampler(double aInterval, int aEntrySize, - const char** aFeatures, uint32_t aFeatureCount, - const char** aThreadNameFilters, uint32_t aFilterCount) - : Sampler(aInterval, true, aEntrySize) - , mPrimaryThreadProfile(nullptr) - , mBuffer(new ProfileBuffer(aEntrySize)) - , mSaveRequested(false) -#if defined(XP_WIN) - , mIntelPowerGadget(nullptr) -#endif -{ - mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk"); - - mProfileJS = hasFeature(aFeatures, aFeatureCount, "js"); - mProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu"); - mProfilePower = hasFeature(aFeatures, aFeatureCount, "power"); - // Users sometimes ask to filter by a list of threads but forget to request - // profiling non main threads. Let's make it implificit if we have a filter - mProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads") || aFilterCount > 0; - mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf"); - mPrivacyMode = hasFeature(aFeatures, aFeatureCount, "privacy"); - mAddMainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio"); - mProfileMemory = hasFeature(aFeatures, aFeatureCount, "memory"); - mTaskTracer = hasFeature(aFeatures, aFeatureCount, "tasktracer"); - mLayersDump = hasFeature(aFeatures, aFeatureCount, "layersdump"); - mDisplayListDump = hasFeature(aFeatures, aFeatureCount, "displaylistdump"); - mProfileRestyle = hasFeature(aFeatures, aFeatureCount, "restyle"); - -#if defined(XP_WIN) - if (mProfilePower) { - mIntelPowerGadget = new IntelPowerGadget(); - mProfilePower = mIntelPowerGadget->Init(); - } -#endif - -#if defined(SPS_OS_android) - mProfileJava = mozilla::jni::IsFennec() && - hasFeature(aFeatures, aFeatureCount, "java"); -#else - mProfileJava = false; -#endif - - // Deep copy aThreadNameFilters - MOZ_ALWAYS_TRUE(mThreadNameFilters.resize(aFilterCount)); - for (uint32_t i = 0; i < aFilterCount; ++i) { - mThreadNameFilters[i] = aThreadNameFilters[i]; - } - - // Deep copy aFeatures - MOZ_ALWAYS_TRUE(mFeatures.resize(aFeatureCount)); - for (uint32_t i = 0; i < aFeatureCount; ++i) { - mFeatures[i] = aFeatures[i]; - } - - bool ignore; - sStartTime = mozilla::TimeStamp::ProcessCreation(ignore); - - { - ::MutexAutoLock lock(*sRegisteredThreadsMutex); - - // Create ThreadProfile for each registered thread - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - - RegisterThread(info); - } - - SetActiveSampler(this); - } - -#ifdef MOZ_TASK_TRACER - if (mTaskTracer) { - mozilla::tasktracer::StartLogging(); - } -#endif - - mGatherer = new mozilla::ProfileGatherer(this); -} - -GeckoSampler::~GeckoSampler() -{ - if (IsActive()) - Stop(); - - SetActiveSampler(nullptr); - - // Destroy ThreadProfile for all threads - { - ::MutexAutoLock lock(*sRegisteredThreadsMutex); - - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - ThreadProfile* profile = info->Profile(); - if (profile) { - delete profile; - info->SetProfile(nullptr); - } - // We've stopped profiling. We no longer need to retain - // information for an old thread. - if (info->IsPendingDelete()) { - delete info; - sRegisteredThreads->erase(sRegisteredThreads->begin() + i); - i--; - } - } - } -#if defined(XP_WIN) - delete mIntelPowerGadget; -#endif - - // Cancel any in-flight async profile gatherering - // requests - mGatherer->Cancel(); -} - -void GeckoSampler::HandleSaveRequest() -{ - if (!mSaveRequested) - return; - mSaveRequested = false; - -#ifndef SPS_STANDALONE - // TODO: Use use the ipc/chromium Tasks here to support processes - // without XPCOM. - nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask(); - NS_DispatchToMainThread(runnable); -#endif -} - -void GeckoSampler::DeleteExpiredMarkers() -{ - mBuffer->deleteExpiredStoredMarkers(); -} - -void GeckoSampler::StreamTaskTracer(SpliceableJSONWriter& aWriter) -{ -#ifdef MOZ_TASK_TRACER - aWriter.StartArrayProperty("data"); - UniquePtr<nsTArray<nsCString>> data = mozilla::tasktracer::GetLoggedData(sStartTime); - for (uint32_t i = 0; i < data->Length(); ++i) { - aWriter.StringElement((data->ElementAt(i)).get()); - } - aWriter.EndArray(); - - aWriter.StartArrayProperty("threads"); - ::MutexAutoLock lock(*sRegisteredThreadsMutex); - for (size_t i = 0; i < sRegisteredThreads->size(); i++) { - // Thread meta data - ThreadInfo* info = sRegisteredThreads->at(i); - aWriter.StartObjectElement(); - if (XRE_GetProcessType() == GeckoProcessType_Plugin) { - // TODO Add the proper plugin name - aWriter.StringProperty("name", "Plugin"); - } else { - aWriter.StringProperty("name", info->Name()); - } - aWriter.IntProperty("tid", static_cast<int>(info->ThreadId())); - aWriter.EndObject(); - } - aWriter.EndArray(); - - aWriter.DoubleProperty("start", static_cast<double>(mozilla::tasktracer::GetStartTime())); -#endif -} - - -void GeckoSampler::StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter) -{ - aWriter.IntProperty("version", 3); - aWriter.DoubleProperty("interval", interval()); - aWriter.IntProperty("stackwalk", mUseStackWalk); - -#ifndef SPS_STANDALONE - mozilla::TimeDuration delta = mozilla::TimeStamp::Now() - sStartTime; - aWriter.DoubleProperty("startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds())); - - aWriter.IntProperty("processType", XRE_GetProcessType()); - - nsresult res; - nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res); - if (!NS_FAILED(res)) { - nsAutoCString string; - - res = http->GetPlatform(string); - if (!NS_FAILED(res)) - aWriter.StringProperty("platform", string.Data()); - - res = http->GetOscpu(string); - if (!NS_FAILED(res)) - aWriter.StringProperty("oscpu", string.Data()); - - res = http->GetMisc(string); - if (!NS_FAILED(res)) - aWriter.StringProperty("misc", string.Data()); - } - - nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1"); - if (runtime) { - nsAutoCString string; - - res = runtime->GetXPCOMABI(string); - if (!NS_FAILED(res)) - aWriter.StringProperty("abi", string.Data()); - - res = runtime->GetWidgetToolkit(string); - if (!NS_FAILED(res)) - aWriter.StringProperty("toolkit", string.Data()); - } - - nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1"); - if (appInfo) { - nsAutoCString string; - - res = appInfo->GetName(string); - if (!NS_FAILED(res)) - aWriter.StringProperty("product", string.Data()); - } -#endif -} - -void GeckoSampler::ToStreamAsJSON(std::ostream& stream, double aSinceTime) -{ - SpliceableJSONWriter b(mozilla::MakeUnique<OStreamJSONWriteFunc>(stream)); - StreamJSON(b, aSinceTime); -} - -#ifndef SPS_STANDALONE -JSObject* GeckoSampler::ToJSObject(JSContext *aCx, double aSinceTime) -{ - JS::RootedValue val(aCx); - { - UniquePtr<char[]> buf = ToJSON(aSinceTime); - NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get())); - MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(js_string.get()), - js_string.Length(), &val)); - } - return &val.toObject(); -} - -void GeckoSampler::GetGatherer(nsISupports** aRetVal) -{ - if (!aRetVal || NS_WARN_IF(!mGatherer)) { - return; - } - NS_ADDREF(*aRetVal = mGatherer); -} -#endif - -UniquePtr<char[]> GeckoSampler::ToJSON(double aSinceTime) -{ - SpliceableChunkedJSONWriter b; - StreamJSON(b, aSinceTime); - return b.WriteFunc()->CopyData(); -} - -void GeckoSampler::ToJSObjectAsync(double aSinceTime, - mozilla::dom::Promise* aPromise) -{ - if (NS_WARN_IF(!mGatherer)) { - return; - } - - mGatherer->Start(aSinceTime, aPromise); -} - -struct SubprocessClosure { - explicit SubprocessClosure(SpliceableJSONWriter* aWriter) - : mWriter(aWriter) - {} - - SpliceableJSONWriter* mWriter; -}; - -void SubProcessCallback(const char* aProfile, void* aClosure) -{ - // Called by the observer to get their profile data included - // as a sub profile - SubprocessClosure* closure = (SubprocessClosure*)aClosure; - - // Add the string profile into the profile - closure->mWriter->StringElement(aProfile); -} - - -#if defined(SPS_OS_android) -static -void BuildJavaThreadJSObject(SpliceableJSONWriter& aWriter) -{ - aWriter.StringProperty("name", "Java Main Thread"); - - aWriter.StartArrayProperty("samples"); - - // for each sample - for (int sampleId = 0; true; sampleId++) { - bool firstRun = true; - // for each frame - for (int frameId = 0; true; frameId++) { - jni::String::LocalRef frameName = - java::GeckoJavaSampler::GetFrameName(0, sampleId, frameId); - // when we run out of frames, we stop looping - if (!frameName) { - // if we found at least one frame, we have objects to close - if (!firstRun) { - aWriter.EndArray(); - aWriter.EndObject(); - } - break; - } - // the first time around, open the sample object and frames array - if (firstRun) { - firstRun = false; - - double sampleTime = - java::GeckoJavaSampler::GetSampleTime(0, sampleId); - - aWriter.StartObjectElement(); - aWriter.DoubleProperty("time", sampleTime); - - aWriter.StartArrayProperty("frames"); - } - // add a frame to the sample - aWriter.StartObjectElement(); - aWriter.StringProperty("location", - frameName->ToCString().BeginReading()); - aWriter.EndObject(); - } - // if we found no frames for this sample, we are done - if (firstRun) { - break; - } - } - - aWriter.EndArray(); -} -#endif - -void GeckoSampler::StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime) -{ - aWriter.Start(SpliceableJSONWriter::SingleLineStyle); - { - // Put shared library info - aWriter.StringProperty("libs", GetSharedLibraryInfoStringInternal().c_str()); - - // Put meta data - aWriter.StartObjectProperty("meta"); - StreamMetaJSCustomObject(aWriter); - aWriter.EndObject(); - - // Data of TaskTracer doesn't belong in the circular buffer. - if (TaskTracer()) { - aWriter.StartObjectProperty("tasktracer"); - StreamTaskTracer(aWriter); - aWriter.EndObject(); - } - - // Lists the samples for each ThreadProfile - aWriter.StartArrayProperty("threads"); - { - SetPaused(true); - - { - ::MutexAutoLock lock(*sRegisteredThreadsMutex); - - for (size_t i = 0; i < sRegisteredThreads->size(); i++) { - // Thread not being profiled, skip it - if (!sRegisteredThreads->at(i)->Profile()) - continue; - - // Note that we intentionally include ThreadProfile which - // have been marked for pending delete. - - ::MutexAutoLock lock(sRegisteredThreads->at(i)->Profile()->GetMutex()); - - sRegisteredThreads->at(i)->Profile()->StreamJSON(aWriter, aSinceTime); - } - } - -#ifndef SPS_STANDALONE - if (Sampler::CanNotifyObservers()) { - // Send a event asking any subprocesses (plugins) to - // give us their information - SubprocessClosure closure(&aWriter); - nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - if (os) { - RefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback, &closure); - os->NotifyObservers(pse, "profiler-subprocess", nullptr); - } - } - - #if defined(SPS_OS_android) - if (ProfileJava()) { - java::GeckoJavaSampler::Pause(); - - aWriter.Start(); - { - BuildJavaThreadJSObject(aWriter); - } - aWriter.End(); - - java::GeckoJavaSampler::Unpause(); - } - #endif -#endif - - SetPaused(false); - } - aWriter.EndArray(); - } - aWriter.End(); -} - -void GeckoSampler::FlushOnJSShutdown(JSContext* aContext) -{ -#ifndef SPS_STANDALONE - SetPaused(true); - - { - ::MutexAutoLock lock(*sRegisteredThreadsMutex); - - for (size_t i = 0; i < sRegisteredThreads->size(); i++) { - // Thread not being profiled, skip it. - if (!sRegisteredThreads->at(i)->Profile() || - sRegisteredThreads->at(i)->IsPendingDelete()) { - continue; - } - - // Thread not profiling the context that's going away, skip it. - if (sRegisteredThreads->at(i)->Profile()->GetPseudoStack()->mContext != aContext) { - continue; - } - - ::MutexAutoLock lock(sRegisteredThreads->at(i)->Profile()->GetMutex()); - sRegisteredThreads->at(i)->Profile()->FlushSamplesAndMarkers(); - } - } - - SetPaused(false); -#endif -} - -void PseudoStack::flushSamplerOnJSShutdown() -{ -#ifndef SPS_STANDALONE - MOZ_ASSERT(mContext); - GeckoSampler* t = tlsTicker.get(); - if (t) { - t->FlushOnJSShutdown(mContext); - } -#endif -} - -// END SaveProfileTask et al -//////////////////////////////////////////////////////////////////////// - -static -void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr) -{ - aProfile.addTag(ProfileEntry(aTagName, "")); - // Add one to store the null termination - size_t strLen = strlen(aStr) + 1; - for (size_t j = 0; j < strLen;) { - // Store as many characters in the void* as the platform allows - char text[sizeof(void*)]; - size_t len = sizeof(void*)/sizeof(char); - if (j+len >= strLen) { - len = strLen - j; - } - memcpy(text, &aStr[j], len); - j += sizeof(void*)/sizeof(char); - // Cast to *((void**) to pass the text data to a void* - aProfile.addTag(ProfileEntry('d', *((void**)(&text[0])))); - } -} - -static -void addPseudoEntry(volatile StackEntry &entry, ThreadProfile &aProfile, - PseudoStack *stack, void *lastpc) -{ - // Pseudo-frames with the BEGIN_PSEUDO_JS flag are just annotations - // and should not be recorded in the profile. - if (entry.hasFlag(StackEntry::BEGIN_PSEUDO_JS)) - return; - - int lineno = -1; - - // 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 - - addDynamicTag(aProfile, 'c', sampleLabel); -#ifndef SPS_STANDALONE - if (entry.isJs()) { - JSScript* script = entry.script(); - if (script) { - 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->mContext, script, - lastpc); - if (jspc) { - lineno = JS_PCToLineNumber(script, jspc); - } - } - } else { - lineno = JS_PCToLineNumber(script, entry.pc()); - } - } - } else { - lineno = entry.line(); - } -#endif - } else { - aProfile.addTag(ProfileEntry('c', sampleLabel)); - - // XXX: Bug 1010578. Don't assume a CPP entry and try to get the - // line for js entries as well. - if (entry.isCpp()) { - lineno = entry.line(); - } - } - - if (lineno != -1) { - aProfile.addTag(ProfileEntry('n', lineno)); - } - - uint32_t category = entry.category(); - MOZ_ASSERT(!(category & StackEntry::IS_CPP_ENTRY)); - MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY)); - - if (category) { - aProfile.addTag(ProfileEntry('y', (int)category)); - } -} - -struct NativeStack -{ - void** pc_array; - void** sp_array; - size_t size; - size_t count; -}; - -mozilla::Atomic<bool> WALKING_JS_STACK(false); - -struct AutoWalkJSStack { - bool walkAllowed; - - AutoWalkJSStack() : walkAllowed(false) { - walkAllowed = WALKING_JS_STACK.compareExchange(false, true); - } - - ~AutoWalkJSStack() { - if (walkAllowed) - WALKING_JS_STACK = false; - } -}; - -static -void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack) -{ - PseudoStack* pseudoStack = aProfile.GetPseudoStack(); - volatile StackEntry *pseudoFrames = pseudoStack->mStack; - uint32_t pseudoCount = pseudoStack->stackSize(); - - // Make a copy of the JS stack into a JSFrame array. This is necessary since, - // like the native stack, the JS stack is iterated youngest-to-oldest and we - // need to iterate oldest-to-youngest when adding entries to aProfile. - - // Synchronous sampling reports an invalid buffer generation to - // ProfilingFrameIterator to avoid incorrectly resetting the generation of - // sampled JIT entries inside the JS engine. See note below concerning 'J' - // entries. - uint32_t startBufferGen; - if (aSample->isSamplingCurrentThread) { - startBufferGen = UINT32_MAX; - } else { - startBufferGen = aProfile.bufferGeneration(); - } - uint32_t jsCount = 0; -#ifndef SPS_STANDALONE - JS::ProfilingFrameIterator::Frame jsFrames[1000]; - // Only walk jit stack if profiling frame iterator is turned on. - if (pseudoStack->mContext && JS::IsProfilingEnabledForContext(pseudoStack->mContext)) { - AutoWalkJSStack autoWalkJSStack; - const uint32_t maxFrames = mozilla::ArrayLength(jsFrames); - - if (aSample && autoWalkJSStack.walkAllowed) { - JS::ProfilingFrameIterator::RegisterState registerState; - registerState.pc = aSample->pc; - registerState.sp = aSample->sp; -#ifdef ENABLE_ARM_LR_SAVING - registerState.lr = aSample->lr; -#endif - - JS::ProfilingFrameIterator jsIter(pseudoStack->mContext, - registerState, - startBufferGen); - for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) { - // See note below regarding 'J' entries. - if (aSample->isSamplingCurrentThread || jsIter.isWasm()) { - uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames); - jsCount += extracted; - if (jsCount == maxFrames) - break; - } else { - mozilla::Maybe<JS::ProfilingFrameIterator::Frame> frame = - jsIter.getPhysicalFrameWithoutLabel(); - if (frame.isSome()) - jsFrames[jsCount++] = mozilla::Move(frame.ref()); - } - } - } - } -#endif - - // Start the sample with a root entry. - aProfile.addTag(ProfileEntry('s', "(root)")); - - // While the pseudo-stack array is ordered oldest-to-youngest, the JS and - // native arrays are ordered youngest-to-oldest. We must add frames to - // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards - // and JS and native arrays backwards. Note: this means the terminating - // condition jsIndex and nativeIndex is being < 0. - uint32_t pseudoIndex = 0; - int32_t jsIndex = jsCount - 1; - int32_t nativeIndex = aNativeStack.count - 1; - - uint8_t *lastPseudoCppStackAddr = nullptr; - - // Iterate as long as there is at least one frame remaining. - while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) { - // There are 1 to 3 frames available. Find and add the oldest. - - uint8_t *pseudoStackAddr = nullptr; - uint8_t *jsStackAddr = nullptr; - uint8_t *nativeStackAddr = nullptr; - - if (pseudoIndex != pseudoCount) { - volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; - - if (pseudoFrame.isCpp()) - lastPseudoCppStackAddr = (uint8_t *) pseudoFrame.stackAddress(); - -#ifndef SPS_STANDALONE - // Skip any pseudo-stack JS frames which are marked isOSR - // Pseudostack frames are marked isOSR when the JS interpreter - // enters a jit frame on a loop edge (via on-stack-replacement, - // or OSR). To avoid both the pseudoframe and jit frame being - // recorded (and showing up twice), the interpreter marks the - // interpreter pseudostack entry with the OSR flag to ensure that - // it doesn't get counted. - if (pseudoFrame.isJs() && pseudoFrame.isOSR()) { - pseudoIndex++; - continue; - } -#endif - - MOZ_ASSERT(lastPseudoCppStackAddr); - pseudoStackAddr = lastPseudoCppStackAddr; - } - -#ifndef SPS_STANDALONE - if (jsIndex >= 0) - jsStackAddr = (uint8_t *) jsFrames[jsIndex].stackAddress; -#endif - - if (nativeIndex >= 0) - nativeStackAddr = (uint8_t *) aNativeStack.sp_array[nativeIndex]; - - // If there's a native stack entry which has the same SP as a - // pseudo stack entry, pretend we didn't see the native stack - // entry. Ditto for a native stack entry which has the same SP as - // a JS stack entry. In effect this means pseudo or JS entries - // trump conflicting native entries. - if (nativeStackAddr && (pseudoStackAddr == nativeStackAddr || jsStackAddr == nativeStackAddr)) { - nativeStackAddr = nullptr; - nativeIndex--; - MOZ_ASSERT(pseudoStackAddr || jsStackAddr); - } - - // Sanity checks. - MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr && - pseudoStackAddr != nativeStackAddr); - MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr && - jsStackAddr != nativeStackAddr); - MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr && - nativeStackAddr != jsStackAddr); - - // Check to see if pseudoStack frame is top-most. - if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) { - MOZ_ASSERT(pseudoIndex < pseudoCount); - volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; - addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr); - pseudoIndex++; - continue; - } - -#ifndef SPS_STANDALONE - // Check to see if JS jit stack frame is top-most - if (jsStackAddr > nativeStackAddr) { - MOZ_ASSERT(jsIndex >= 0); - const JS::ProfilingFrameIterator::Frame& jsFrame = jsFrames[jsIndex]; - - // Stringifying non-wasm JIT frames is delayed until streaming - // time. To re-lookup the entry in the JitcodeGlobalTable, we need to - // store the JIT code address ('J') in the circular buffer. - // - // Note that we cannot do this when we are sychronously sampling the - // current thread; that is, when called from profiler_get_backtrace. The - // captured backtrace is usually externally stored for an indeterminate - // amount of time, such as in nsRefreshDriver. Problematically, the - // stored backtrace may be alive across a GC during which the profiler - // itself is disabled. In that case, the JS engine is free to discard - // its JIT code. This means that if we inserted such 'J' entries into - // the buffer, nsRefreshDriver would now be holding on to a backtrace - // with stale JIT code return addresses. - if (aSample->isSamplingCurrentThread || - jsFrame.kind == JS::ProfilingFrameIterator::Frame_Wasm) { - addDynamicTag(aProfile, 'c', jsFrame.label.get()); - } else { - MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion || - jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline); - aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress)); - } - - jsIndex--; - continue; - } -#endif - - // If we reach here, there must be a native stack entry and it must be the - // greatest entry. - if (nativeStackAddr) { - MOZ_ASSERT(nativeIndex >= 0); - aProfile - .addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex])); - } - if (nativeIndex >= 0) { - nativeIndex--; - } - } - -#ifndef SPS_STANDALONE - // Update the JS context with the current profile sample buffer generation. - // - // Do not do this for synchronous sampling, which create their own - // ProfileBuffers. - if (!aSample->isSamplingCurrentThread && pseudoStack->mContext) { - MOZ_ASSERT(aProfile.bufferGeneration() >= startBufferGen); - uint32_t lapCount = aProfile.bufferGeneration() - startBufferGen; - JS::UpdateJSContextProfilerSampleBufferGen(pseudoStack->mContext, - aProfile.bufferGeneration(), - lapCount); - } -#endif -} - -#ifdef USE_NS_STACKWALK -static -void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, - void* aClosure) -{ - NativeStack* nativeStack = static_cast<NativeStack*>(aClosure); - MOZ_ASSERT(nativeStack->count < nativeStack->size); - nativeStack->sp_array[nativeStack->count] = aSP; - nativeStack->pc_array[nativeStack->count] = aPC; - nativeStack->count++; -} - -void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) -{ - void* pc_array[1000]; - void* sp_array[1000]; - NativeStack nativeStack = { - pc_array, - sp_array, - mozilla::ArrayLength(pc_array), - 0 - }; - - // Start with the current function. We use 0 as the frame number here because - // the FramePointerStackWalk() and MozStackWalk() calls below will use 1..N. - // This is a bit weird but it doesn't matter because StackWalkCallback() - // doesn't use the frame number argument. - StackWalkCallback(/* frameNumber */ 0, aSample->pc, aSample->sp, &nativeStack); - - uint32_t maxFrames = uint32_t(nativeStack.size - nativeStack.count); - // win X64 doesn't support disabling frame pointers emission so we need - // to fallback to using StackWalk64 which is slower. -#if defined(XP_MACOSX) || (defined(XP_WIN) && !defined(V8_HOST_ARCH_X64)) - void *stackEnd = aSample->threadProfile->GetStackTop(); - bool rv = true; - if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd) - rv = FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0, - maxFrames, &nativeStack, - reinterpret_cast<void**>(aSample->fp), stackEnd); -#else - void *platformData = nullptr; - - uintptr_t thread = GetThreadHandle(aSample->threadProfile->GetPlatformData()); - MOZ_ASSERT(thread); - bool rv = MozStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames, - &nativeStack, thread, platformData); -#endif - if (rv) - mergeStacksIntoProfile(aProfile, aSample, nativeStack); -} -#endif - - -#ifdef USE_EHABI_STACKWALK -void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) -{ - void *pc_array[1000]; - void *sp_array[1000]; - NativeStack nativeStack = { - pc_array, - sp_array, - mozilla::ArrayLength(pc_array), - 0 - }; - - const mcontext_t *mcontext = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext; - mcontext_t savedContext; - PseudoStack *pseudoStack = aProfile.GetPseudoStack(); - - nativeStack.count = 0; - // The pseudostack contains an "EnterJIT" frame whenever we enter - // JIT code with profiling enabled; the stack pointer value points - // the saved registers. We use this to unwind resume unwinding - // after encounting JIT code. - for (uint32_t i = pseudoStack->stackSize(); i > 0; --i) { - // The pseudostack grows towards higher indices, so we iterate - // backwards (from callee to caller). - volatile StackEntry &entry = pseudoStack->mStack[i - 1]; - if (!entry.isJs() && strcmp(entry.label(), "EnterJIT") == 0) { - // Found JIT entry frame. Unwind up to that point (i.e., force - // the stack walk to stop before the block of saved registers; - // note that it yields nondecreasing stack pointers), then restore - // the saved state. - uint32_t *vSP = reinterpret_cast<uint32_t*>(entry.stackAddress()); - - nativeStack.count += EHABIStackWalk(*mcontext, - /* stackBase = */ vSP, - sp_array + nativeStack.count, - pc_array + nativeStack.count, - nativeStack.size - nativeStack.count); - - memset(&savedContext, 0, sizeof(savedContext)); - // See also: struct EnterJITStack in js/src/jit/arm/Trampoline-arm.cpp - savedContext.arm_r4 = *vSP++; - savedContext.arm_r5 = *vSP++; - savedContext.arm_r6 = *vSP++; - savedContext.arm_r7 = *vSP++; - savedContext.arm_r8 = *vSP++; - savedContext.arm_r9 = *vSP++; - savedContext.arm_r10 = *vSP++; - savedContext.arm_fp = *vSP++; - savedContext.arm_lr = *vSP++; - savedContext.arm_sp = reinterpret_cast<uint32_t>(vSP); - savedContext.arm_pc = savedContext.arm_lr; - mcontext = &savedContext; - } - } - - // Now unwind whatever's left (starting from either the last EnterJIT - // frame or, if no EnterJIT was found, the original registers). - nativeStack.count += EHABIStackWalk(*mcontext, - aProfile.GetStackTop(), - sp_array + nativeStack.count, - pc_array + nativeStack.count, - nativeStack.size - nativeStack.count); - - mergeStacksIntoProfile(aProfile, aSample, nativeStack); -} -#endif - - -#ifdef USE_LUL_STACKWALK -void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) -{ - const mcontext_t* mc - = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext; - - lul::UnwindRegs startRegs; - memset(&startRegs, 0, sizeof(startRegs)); - -# if defined(SPS_PLAT_amd64_linux) - startRegs.xip = lul::TaggedUWord(mc->gregs[REG_RIP]); - startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_RSP]); - startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_RBP]); -# elif defined(SPS_PLAT_arm_android) - startRegs.r15 = lul::TaggedUWord(mc->arm_pc); - startRegs.r14 = lul::TaggedUWord(mc->arm_lr); - startRegs.r13 = lul::TaggedUWord(mc->arm_sp); - startRegs.r12 = lul::TaggedUWord(mc->arm_ip); - startRegs.r11 = lul::TaggedUWord(mc->arm_fp); - startRegs.r7 = lul::TaggedUWord(mc->arm_r7); -# elif defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android) - startRegs.xip = lul::TaggedUWord(mc->gregs[REG_EIP]); - startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_ESP]); - startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_EBP]); -# else -# error "Unknown plat" -# endif - - /* Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not - going past the stack's registered top point. Do some basic - sanity checks too. This assumes that the TaggedUWord holding - the stack pointer value is valid, but it should be, since it - was constructed that way in the code just above. */ - - lul::StackImage stackImg; - - { -# if defined(SPS_PLAT_amd64_linux) - uintptr_t rEDZONE_SIZE = 128; - uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE; -# elif defined(SPS_PLAT_arm_android) - uintptr_t rEDZONE_SIZE = 0; - uintptr_t start = startRegs.r13.Value() - rEDZONE_SIZE; -# elif defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android) - uintptr_t rEDZONE_SIZE = 0; - uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE; -# else -# error "Unknown plat" -# endif - uintptr_t end = reinterpret_cast<uintptr_t>(aProfile.GetStackTop()); - uintptr_t ws = sizeof(void*); - start &= ~(ws-1); - end &= ~(ws-1); - uintptr_t nToCopy = 0; - if (start < end) { - nToCopy = end - start; - if (nToCopy > lul::N_STACK_BYTES) - nToCopy = lul::N_STACK_BYTES; - } - MOZ_ASSERT(nToCopy <= lul::N_STACK_BYTES); - stackImg.mLen = nToCopy; - stackImg.mStartAvma = start; - if (nToCopy > 0) { - memcpy(&stackImg.mContents[0], (void*)start, nToCopy); - (void)VALGRIND_MAKE_MEM_DEFINED(&stackImg.mContents[0], nToCopy); - } - } - - // The maximum number of frames that LUL will produce. Setting it - // too high gives a risk of it wasting a lot of time looping on - // corrupted stacks. - const int MAX_NATIVE_FRAMES = 256; - - size_t scannedFramesAllowed = 0; - - uintptr_t framePCs[MAX_NATIVE_FRAMES]; - uintptr_t frameSPs[MAX_NATIVE_FRAMES]; - size_t framesAvail = mozilla::ArrayLength(framePCs); - size_t framesUsed = 0; - size_t scannedFramesAcquired = 0; - sLUL->Unwind( &framePCs[0], &frameSPs[0], - &framesUsed, &scannedFramesAcquired, - framesAvail, scannedFramesAllowed, - &startRegs, &stackImg ); - - NativeStack nativeStack = { - reinterpret_cast<void**>(framePCs), - reinterpret_cast<void**>(frameSPs), - mozilla::ArrayLength(framePCs), - 0 - }; - - nativeStack.count = framesUsed; - - mergeStacksIntoProfile(aProfile, aSample, nativeStack); - - // Update stats in the LUL stats object. Unfortunately this requires - // three global memory operations. - sLUL->mStats.mContext += 1; - sLUL->mStats.mCFI += framesUsed - 1 - scannedFramesAcquired; - sLUL->mStats.mScanned += scannedFramesAcquired; -} -#endif - - -static -void doSampleStackTrace(ThreadProfile &aProfile, TickSample *aSample, bool aAddLeafAddresses) -{ - NativeStack nativeStack = { nullptr, nullptr, 0, 0 }; - mergeStacksIntoProfile(aProfile, aSample, nativeStack); - -#ifdef ENABLE_SPS_LEAF_DATA - if (aSample && aAddLeafAddresses) { - aProfile.addTag(ProfileEntry('l', (void*)aSample->pc)); -#ifdef ENABLE_ARM_LR_SAVING - aProfile.addTag(ProfileEntry('L', (void*)aSample->lr)); -#endif - } -#endif -} - -void GeckoSampler::Tick(TickSample* sample) -{ - // Don't allow for ticks to happen within other ticks. - InplaceTick(sample); -} - -void GeckoSampler::InplaceTick(TickSample* sample) -{ - ThreadProfile& currThreadProfile = *sample->threadProfile; - - currThreadProfile.addTag(ProfileEntry('T', currThreadProfile.ThreadId())); - - if (sample) { - mozilla::TimeDuration delta = sample->timestamp - sStartTime; - currThreadProfile.addTag(ProfileEntry('t', delta.ToMilliseconds())); - } - - PseudoStack* stack = currThreadProfile.GetPseudoStack(); - -#if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) || \ - defined(USE_LUL_STACKWALK) - if (mUseStackWalk) { - doNativeBacktrace(currThreadProfile, sample); - } else { - doSampleStackTrace(currThreadProfile, sample, mAddLeafAddresses); - } -#else - doSampleStackTrace(currThreadProfile, sample, mAddLeafAddresses); -#endif - - // Don't process the PeudoStack's markers if we're - // synchronously sampling the current thread. - if (!sample->isSamplingCurrentThread) { - ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers(); - while (pendingMarkersList && pendingMarkersList->peek()) { - ProfilerMarker* marker = pendingMarkersList->popHead(); - currThreadProfile.addStoredMarker(marker); - currThreadProfile.addTag(ProfileEntry('m', marker)); - } - } - -#ifndef SPS_STANDALONE - if (sample && currThreadProfile.GetThreadResponsiveness()->HasData()) { - mozilla::TimeDuration delta = currThreadProfile.GetThreadResponsiveness()->GetUnresponsiveDuration(sample->timestamp); - currThreadProfile.addTag(ProfileEntry('r', delta.ToMilliseconds())); - } -#endif - - // rssMemory is equal to 0 when we are not recording. - if (sample && sample->rssMemory != 0) { - currThreadProfile.addTag(ProfileEntry('R', static_cast<double>(sample->rssMemory))); - } - - // ussMemory is equal to 0 when we are not recording. - if (sample && sample->ussMemory != 0) { - currThreadProfile.addTag(ProfileEntry('U', static_cast<double>(sample->ussMemory))); - } - -#if defined(XP_WIN) - if (mProfilePower) { - mIntelPowerGadget->TakeSample(); - currThreadProfile.addTag(ProfileEntry('p', static_cast<double>(mIntelPowerGadget->GetTotalPackagePowerInWatts()))); - } -#endif - - if (sLastFrameNumber != sFrameNumber) { - currThreadProfile.addTag(ProfileEntry('f', sFrameNumber)); - sLastFrameNumber = sFrameNumber; - } -} - -namespace { - -SyncProfile* NewSyncProfile() -{ - PseudoStack* stack = tlsPseudoStack.get(); - if (!stack) { - MOZ_ASSERT(stack); - return nullptr; - } - Thread::tid_t tid = Thread::GetCurrentId(); - - ThreadInfo* info = new ThreadInfo("SyncProfile", tid, false, stack, nullptr); - SyncProfile* profile = new SyncProfile(info, GET_BACKTRACE_DEFAULT_ENTRY); - return profile; -} - -} // namespace - -SyncProfile* GeckoSampler::GetBacktrace() -{ - SyncProfile* profile = NewSyncProfile(); - - TickSample sample; - sample.threadProfile = profile; - -#if defined(HAVE_NATIVE_UNWIND) || defined(USE_LUL_STACKWALK) -#if defined(XP_WIN) || defined(LINUX) - tickcontext_t context; - sample.PopulateContext(&context); -#elif defined(XP_MACOSX) - sample.PopulateContext(nullptr); -#endif -#endif - - sample.isSamplingCurrentThread = true; - sample.timestamp = mozilla::TimeStamp::Now(); - - profile->BeginUnwind(); - Tick(&sample); - profile->EndUnwind(); - - return profile; -} - -void -GeckoSampler::GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration) -{ - *aCurrentPosition = mBuffer->mWritePos; - *aTotalSize = mBuffer->mEntrySize; - *aGeneration = mBuffer->mGeneration; -} diff --git a/tools/profiler/core/GeckoSampler.h b/tools/profiler/core/GeckoSampler.h deleted file mode 100644 index da1fdfe43..000000000 --- a/tools/profiler/core/GeckoSampler.h +++ /dev/null @@ -1,181 +0,0 @@ -/* -*- 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 GeckoSampler_h -#define GeckoSampler_h - -#include "platform.h" -#include "ProfileEntry.h" -#include "mozilla/Vector.h" -#include "ThreadProfile.h" -#include "ThreadInfo.h" -#ifndef SPS_STANDALONE -#include "IntelPowerGadget.h" -#endif -#ifdef MOZ_TASK_TRACER -#include "GeckoTaskTracer.h" -#endif - -#include <algorithm> - -namespace mozilla { -class ProfileGatherer; -} // namespace mozilla - -typedef mozilla::Vector<std::string> ThreadNameFilterList; -typedef mozilla::Vector<std::string> FeatureList; - -static bool -threadSelected(ThreadInfo* aInfo, const ThreadNameFilterList &aThreadNameFilters) { - if (aThreadNameFilters.empty()) { - return true; - } - - std::string name = aInfo->Name(); - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - - for (uint32_t i = 0; i < aThreadNameFilters.length(); ++i) { - std::string filter = aThreadNameFilters[i]; - std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower); - - // Crude, non UTF-8 compatible, case insensitive substring search - if (name.find(filter) != std::string::npos) { - return true; - } - } - - return false; -} - -extern mozilla::TimeStamp sLastTracerEvent; -extern int sFrameNumber; -extern int sLastFrameNumber; - -class GeckoSampler: public Sampler { - public: - GeckoSampler(double aInterval, int aEntrySize, - const char** aFeatures, uint32_t aFeatureCount, - const char** aThreadNameFilters, uint32_t aFilterCount); - ~GeckoSampler(); - - void RegisterThread(ThreadInfo* aInfo) { - if (!aInfo->IsMainThread() && !mProfileThreads) { - return; - } - - if (!threadSelected(aInfo, mThreadNameFilters)) { - return; - } - - ThreadProfile* profile = new ThreadProfile(aInfo, mBuffer); - aInfo->SetProfile(profile); - } - - // Called within a signal. This function must be reentrant - virtual void Tick(TickSample* sample) override; - - // Immediately captures the calling thread's call stack and returns it. - virtual SyncProfile* GetBacktrace() override; - - // Called within a signal. This function must be reentrant - virtual void RequestSave() override - { - mSaveRequested = true; -#ifdef MOZ_TASK_TRACER - if (mTaskTracer) { - mozilla::tasktracer::StopLogging(); - } -#endif - } - - virtual void HandleSaveRequest() override; - virtual void DeleteExpiredMarkers() override; - - ThreadProfile* GetPrimaryThreadProfile() - { - if (!mPrimaryThreadProfile) { - ::MutexAutoLock lock(*sRegisteredThreadsMutex); - - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - if (info->IsMainThread() && !info->IsPendingDelete()) { - mPrimaryThreadProfile = info->Profile(); - break; - } - } - } - - return mPrimaryThreadProfile; - } - - void ToStreamAsJSON(std::ostream& stream, double aSinceTime = 0); -#ifndef SPS_STANDALONE - virtual JSObject *ToJSObject(JSContext *aCx, double aSinceTime = 0); - void GetGatherer(nsISupports** aRetVal); -#endif - mozilla::UniquePtr<char[]> ToJSON(double aSinceTime = 0); - virtual void ToJSObjectAsync(double aSinceTime = 0, mozilla::dom::Promise* aPromise = 0); - void StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter); - void StreamTaskTracer(SpliceableJSONWriter& aWriter); - void FlushOnJSShutdown(JSContext* aContext); - bool ProfileJS() const { return mProfileJS; } - bool ProfileJava() const { return mProfileJava; } - bool ProfileGPU() const { return mProfileGPU; } - bool ProfilePower() const { return mProfilePower; } - bool ProfileThreads() const override { return mProfileThreads; } - bool InPrivacyMode() const { return mPrivacyMode; } - bool AddMainThreadIO() const { return mAddMainThreadIO; } - bool ProfileMemory() const { return mProfileMemory; } - bool TaskTracer() const { return mTaskTracer; } - bool LayersDump() const { return mLayersDump; } - bool DisplayListDump() const { return mDisplayListDump; } - bool ProfileRestyle() const { return mProfileRestyle; } - const ThreadNameFilterList& ThreadNameFilters() { return mThreadNameFilters; } - const FeatureList& Features() { return mFeatures; } - - void GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration); - -protected: - // Called within a signal. This function must be reentrant - virtual void InplaceTick(TickSample* sample); - - // Not implemented on platforms which do not support backtracing - void doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample); - - void StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime); - - // This represent the application's main thread (SAMPLER_INIT) - ThreadProfile* mPrimaryThreadProfile; - RefPtr<ProfileBuffer> mBuffer; - bool mSaveRequested; - bool mAddLeafAddresses; - bool mUseStackWalk; - bool mProfileJS; - bool mProfileGPU; - bool mProfileThreads; - bool mProfileJava; - bool mProfilePower; - bool mLayersDump; - bool mDisplayListDump; - bool mProfileRestyle; - - // Keep the thread filter to check against new thread that - // are started while profiling - ThreadNameFilterList mThreadNameFilters; - FeatureList mFeatures; - bool mPrivacyMode; - bool mAddMainThreadIO; - bool mProfileMemory; - bool mTaskTracer; -#if defined(XP_WIN) - IntelPowerGadget* mIntelPowerGadget; -#endif - -private: - RefPtr<mozilla::ProfileGatherer> mGatherer; -}; - -#endif - diff --git a/tools/profiler/core/IntelPowerGadget.cpp b/tools/profiler/core/IntelPowerGadget.cpp deleted file mode 100644 index fe267b80f..000000000 --- a/tools/profiler/core/IntelPowerGadget.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/*
- * Copyright 2013, Intel Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Author: Joe Olivas <joseph.k.olivas@intel.com>
- */
-
-#include "nsDebug.h"
-#include "nsString.h"
-#include "IntelPowerGadget.h"
-#include "prenv.h"
-
-IntelPowerGadget::IntelPowerGadget() :
- libpowergadget(nullptr),
- Initialize(nullptr),
- GetNumNodes(nullptr),
- GetMsrName(nullptr),
- GetMsrFunc(nullptr),
- ReadMSR(nullptr),
- WriteMSR(nullptr),
- GetIAFrequency(nullptr),
- GetTDP(nullptr),
- GetMaxTemperature(nullptr),
- GetThresholds(nullptr),
- GetTemperature(nullptr),
- ReadSample(nullptr),
- GetSysTime(nullptr),
- GetRDTSC(nullptr),
- GetTimeInterval(nullptr),
- GetBaseFrequency(nullptr),
- GetPowerData(nullptr),
- StartLog(nullptr),
- StopLog(nullptr),
- GetNumMsrs(nullptr),
- packageMSR(-1),
- cpuMSR(-1),
- freqMSR(-1),
- tempMSR(-1)
-{
-}
-
-bool
-IntelPowerGadget::Init()
-{
- bool success = false;
- const char *path = PR_GetEnv("IPG_Dir");
- nsCString ipg_library;
- if (path && *path) {
- ipg_library.Append(path);
- ipg_library.Append('/');
- ipg_library.AppendLiteral(PG_LIBRARY_NAME);
- libpowergadget = PR_LoadLibrary(ipg_library.get());
- }
-
- if(libpowergadget) {
- Initialize = (IPGInitialize) PR_FindFunctionSymbol(libpowergadget, "IntelEnergyLibInitialize");
- GetNumNodes = (IPGGetNumNodes) PR_FindFunctionSymbol(libpowergadget, "GetNumNodes");
- GetMsrName = (IPGGetMsrName) PR_FindFunctionSymbol(libpowergadget, "GetMsrName");
- GetMsrFunc = (IPGGetMsrFunc) PR_FindFunctionSymbol(libpowergadget, "GetMsrFunc");
- ReadMSR = (IPGReadMSR) PR_FindFunctionSymbol(libpowergadget, "ReadMSR");
- WriteMSR = (IPGWriteMSR) PR_FindFunctionSymbol(libpowergadget, "WriteMSR");
- GetIAFrequency = (IPGGetIAFrequency) PR_FindFunctionSymbol(libpowergadget, "GetIAFrequency");
- GetTDP = (IPGGetTDP) PR_FindFunctionSymbol(libpowergadget, "GetTDP");
- GetMaxTemperature = (IPGGetMaxTemperature) PR_FindFunctionSymbol(libpowergadget, "GetMaxTemperature");
- GetThresholds = (IPGGetThresholds) PR_FindFunctionSymbol(libpowergadget, "GetThresholds");
- GetTemperature = (IPGGetTemperature) PR_FindFunctionSymbol(libpowergadget, "GetTemperature");
- ReadSample = (IPGReadSample) PR_FindFunctionSymbol(libpowergadget, "ReadSample");
- GetSysTime = (IPGGetSysTime) PR_FindFunctionSymbol(libpowergadget, "GetSysTime");
- GetRDTSC = (IPGGetRDTSC) PR_FindFunctionSymbol(libpowergadget, "GetRDTSC");
- GetTimeInterval = (IPGGetTimeInterval) PR_FindFunctionSymbol(libpowergadget, "GetTimeInterval");
- GetBaseFrequency = (IPGGetBaseFrequency) PR_FindFunctionSymbol(libpowergadget, "GetBaseFrequency");
- GetPowerData = (IPGGetPowerData) PR_FindFunctionSymbol(libpowergadget, "GetPowerData");
- StartLog = (IPGStartLog) PR_FindFunctionSymbol(libpowergadget, "StartLog");
- StopLog = (IPGStopLog) PR_FindFunctionSymbol(libpowergadget, "StopLog");
- GetNumMsrs = (IPGGetNumMsrs) PR_FindFunctionSymbol(libpowergadget, "GetNumMsrs");
- }
-
- if(Initialize) {
- Initialize();
- int msrCount = GetNumberMsrs();
- wchar_t name[1024] = {0};
- for(int i = 0; i < msrCount; ++i) {
- GetMsrName(i, name);
- int func = 0;
- GetMsrFunc(i, &func);
- // MSR for frequency
- if(wcscmp(name, L"CPU Frequency") == 0 && (func == 0)) {
- this->freqMSR = i;
- }
- // MSR for Package
- else if(wcscmp(name, L"Processor") == 0 && (func == 1)) {
- this->packageMSR = i;
- }
- // MSR for CPU
- else if(wcscmp(name, L"IA") == 0 && (func == 1)) {
- this->cpuMSR = i;
- }
- // MSR for Temperature
- else if(wcscmp(name, L"Package") == 0 && (func == 2)) {
- this->tempMSR = i;
- }
- }
- // Grab one sample at startup for a diff
- TakeSample();
- success = true;
- }
- return success;
-}
-
-IntelPowerGadget::~IntelPowerGadget()
-{
- if(libpowergadget) {
- NS_WARNING("Unloading PowerGadget library!\n");
- PR_UnloadLibrary(libpowergadget);
- libpowergadget = nullptr;
- Initialize = nullptr;
- GetNumNodes = nullptr;
- GetMsrName = nullptr;
- GetMsrFunc = nullptr;
- ReadMSR = nullptr;
- WriteMSR = nullptr;
- GetIAFrequency = nullptr;
- GetTDP = nullptr;
- GetMaxTemperature = nullptr;
- GetThresholds = nullptr;
- GetTemperature = nullptr;
- ReadSample = nullptr;
- GetSysTime = nullptr;
- GetRDTSC = nullptr;
- GetTimeInterval = nullptr;
- GetBaseFrequency = nullptr;
- GetPowerData = nullptr;
- StartLog = nullptr;
- StopLog = nullptr;
- GetNumMsrs = nullptr;
- }
-}
-
-int
-IntelPowerGadget::GetNumberNodes()
-{
- int nodes = 0;
- if(GetNumNodes) {
- int ok = GetNumNodes(&nodes);
- }
- return nodes;
-}
-
-int
-IntelPowerGadget::GetNumberMsrs()
-{
- int msrs = 0;
- if(GetNumMsrs) {
- int ok = GetNumMsrs(&msrs);
- }
- return msrs;
-}
-
-int
-IntelPowerGadget::GetCPUFrequency(int node)
-{
- int frequency = 0;
- if(GetIAFrequency) {
- int ok = GetIAFrequency(node, &frequency);
- }
- return frequency;
-}
-
-double
-IntelPowerGadget::GetTdp(int node)
-{
- double tdp = 0.0;
- if(GetTDP) {
- int ok = GetTDP(node, &tdp);
- }
- return tdp;
-}
-
-int
-IntelPowerGadget::GetMaxTemp(int node)
-{
- int maxTemperatureC = 0;
- if(GetMaxTemperature) {
- int ok = GetMaxTemperature(node, &maxTemperatureC);
- }
- return maxTemperatureC;
-}
-
-int
-IntelPowerGadget::GetTemp(int node)
-{
- int temperatureC = 0;
- if(GetTemperature) {
- int ok = GetTemperature(node, &temperatureC);
- }
- return temperatureC;
-}
-
-int
-IntelPowerGadget::TakeSample()
-{
- int ok = 0;
- if(ReadSample) {
- ok = ReadSample();
- }
- return ok;
-}
-
-uint64_t
-IntelPowerGadget::GetRdtsc()
-{
- uint64_t rdtsc = 0;
- if(GetRDTSC) {
- int ok = GetRDTSC(&rdtsc);
- }
- return rdtsc;
-}
-
-double
-IntelPowerGadget::GetInterval()
-{
- double interval = 0.0;
- if(GetTimeInterval) {
- int ok = GetTimeInterval(&interval);
- }
- return interval;
-}
-
-double
-IntelPowerGadget::GetCPUBaseFrequency(int node)
-{
- double freq = 0.0;
- if(GetBaseFrequency) {
- int ok = GetBaseFrequency(node, &freq);
- }
- return freq;
-}
-
-double
-IntelPowerGadget::GetTotalPackagePowerInWatts()
-{
- int nodes = GetNumberNodes();
- double totalPower = 0.0;
- for(int i = 0; i < nodes; ++i) {
- totalPower += GetPackagePowerInWatts(i);
- }
- return totalPower;
-}
-
-double
-IntelPowerGadget::GetPackagePowerInWatts(int node)
-{
- int numResult = 0;
- double result[] = {0.0, 0.0, 0.0};
- if(GetPowerData && packageMSR != -1) {
- int ok = GetPowerData(node, packageMSR, result, &numResult);
- }
- return result[0];
-}
-
-double
-IntelPowerGadget::GetTotalCPUPowerInWatts()
-{
- int nodes = GetNumberNodes();
- double totalPower = 0.0;
- for(int i = 0; i < nodes; ++i) {
- totalPower += GetCPUPowerInWatts(i);
- }
- return totalPower;
-}
-
-double
-IntelPowerGadget::GetCPUPowerInWatts(int node)
-{
- int numResult = 0;
- double result[] = {0.0, 0.0, 0.0};
- if(GetPowerData && cpuMSR != -1) {
- int ok = GetPowerData(node, cpuMSR, result, &numResult);
- }
- return result[0];
-}
-
-double
-IntelPowerGadget::GetTotalGPUPowerInWatts()
-{
- int nodes = GetNumberNodes();
- double totalPower = 0.0;
- for(int i = 0; i < nodes; ++i) {
- totalPower += GetGPUPowerInWatts(i);
- }
- return totalPower;
-}
-
-double
-IntelPowerGadget::GetGPUPowerInWatts(int node)
-{
- return 0.0;
-}
-
diff --git a/tools/profiler/core/IntelPowerGadget.h b/tools/profiler/core/IntelPowerGadget.h deleted file mode 100644 index 4a24215b6..000000000 --- a/tools/profiler/core/IntelPowerGadget.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2013, Intel Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Author: Joe Olivas <joseph.k.olivas@intel.com> - */ - -#ifndef profiler_IntelPowerGadget_h -#define profiler_IntelPowerGadget_h - -#ifdef _MSC_VER -typedef __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; -#else -#include <stdint.h> -#endif -#include "prlink.h" - -typedef int (*IPGInitialize) (); -typedef int (*IPGGetNumNodes) (int *nNodes); -typedef int (*IPGGetNumMsrs) (int *nMsr); -typedef int (*IPGGetMsrName) (int iMsr, wchar_t *szName); -typedef int (*IPGGetMsrFunc) (int iMsr, int *pFuncID); -typedef int (*IPGReadMSR) (int iNode, unsigned int address, uint64_t *value); -typedef int (*IPGWriteMSR) (int iNode, unsigned int address, uint64_t value); -typedef int (*IPGGetIAFrequency) (int iNode, int *freqInMHz); -typedef int (*IPGGetTDP) (int iNode, double *TDP); -typedef int (*IPGGetMaxTemperature) (int iNode, int *degreeC); -typedef int (*IPGGetThresholds) (int iNode, int *degree1C, int *degree2C); -typedef int (*IPGGetTemperature) (int iNode, int *degreeC); -typedef int (*IPGReadSample) (); -typedef int (*IPGGetSysTime) (void *pSysTime); -typedef int (*IPGGetRDTSC) (uint64_t *pTSC); -typedef int (*IPGGetTimeInterval) (double *pOffset); -typedef int (*IPGGetBaseFrequency) (int iNode, double *pBaseFrequency); -typedef int (*IPGGetPowerData) (int iNode, int iMSR, double *pResult, int *nResult); -typedef int (*IPGStartLog) (wchar_t *szFileName); -typedef int (*IPGStopLog) (); - -#if defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) -#define PG_LIBRARY_NAME "EnergyLib64" -#else -#define PG_LIBRARY_NAME "EnergyLib32" -#endif - - -class IntelPowerGadget -{ -public: - - IntelPowerGadget(); - ~IntelPowerGadget(); - - // Fails if initialization is incomplete - bool Init(); - - // Returns the number of packages on the system - int GetNumberNodes(); - - // Returns the number of MSRs being tracked - int GetNumberMsrs(); - - // Given a node, returns the temperature - int GetCPUFrequency(int); - - // Returns the TDP of the given node - double GetTdp(int); - - // Returns the maximum temperature for the given node - int GetMaxTemp(int); - - // Returns the current temperature in degrees C - // of the given node - int GetTemp(int); - - // Takes a sample of data. Must be called before - // any current data is retrieved. - int TakeSample(); - - // Gets the timestamp of the most recent sample - uint64_t GetRdtsc(); - - // returns number of seconds between the last - // two samples - double GetInterval(); - - // Returns the base frequency for the given node - double GetCPUBaseFrequency(int node); - - // Returns the combined package power for all - // packages on the system for the last sample. - double GetTotalPackagePowerInWatts(); - double GetPackagePowerInWatts(int node); - - // Returns the combined CPU power for all - // packages on the system for the last sample. - // If the reading is not available, returns 0.0 - double GetTotalCPUPowerInWatts(); - double GetCPUPowerInWatts(int node); - - // Returns the combined GPU power for all - // packages on the system for the last sample. - // If the reading is not available, returns 0.0 - double GetTotalGPUPowerInWatts(); - double GetGPUPowerInWatts(int node); - -private: - - PRLibrary *libpowergadget; - IPGInitialize Initialize; - IPGGetNumNodes GetNumNodes; - IPGGetNumMsrs GetNumMsrs; - IPGGetMsrName GetMsrName; - IPGGetMsrFunc GetMsrFunc; - IPGReadMSR ReadMSR; - IPGWriteMSR WriteMSR; - IPGGetIAFrequency GetIAFrequency; - IPGGetTDP GetTDP; - IPGGetMaxTemperature GetMaxTemperature; - IPGGetThresholds GetThresholds; - IPGGetTemperature GetTemperature; - IPGReadSample ReadSample; - IPGGetSysTime GetSysTime; - IPGGetRDTSC GetRDTSC; - IPGGetTimeInterval GetTimeInterval; - IPGGetBaseFrequency GetBaseFrequency; - IPGGetPowerData GetPowerData; - IPGStartLog StartLog; - IPGStopLog StopLog; - - int packageMSR; - int cpuMSR; - int freqMSR; - int tempMSR; -}; - -#endif // profiler_IntelPowerGadget_h diff --git a/tools/profiler/core/ProfileBuffer.cpp b/tools/profiler/core/ProfileBuffer.cpp deleted file mode 100644 index a4b91d8fc..000000000 --- a/tools/profiler/core/ProfileBuffer.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* -*- 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 "ProfileBuffer.h" - -ProfileBuffer::ProfileBuffer(int aEntrySize) - : mEntries(MakeUnique<ProfileEntry[]>(aEntrySize)) - , mWritePos(0) - , mReadPos(0) - , mEntrySize(aEntrySize) - , mGeneration(0) -{ -} - -ProfileBuffer::~ProfileBuffer() -{ - while (mStoredMarkers.peek()) { - delete mStoredMarkers.popHead(); - } -} - -// Called from signal, call only reentrant functions -void ProfileBuffer::addTag(const ProfileEntry& aTag) -{ - mEntries[mWritePos++] = aTag; - if (mWritePos == mEntrySize) { - // Wrapping around may result in things referenced in the buffer (e.g., - // JIT code addresses and markers) being incorrectly collected. - MOZ_ASSERT(mGeneration != UINT32_MAX); - mGeneration++; - mWritePos = 0; - } - if (mWritePos == mReadPos) { - // Keep one slot open. - mEntries[mReadPos] = ProfileEntry(); - mReadPos = (mReadPos + 1) % mEntrySize; - } -} - -void ProfileBuffer::addStoredMarker(ProfilerMarker *aStoredMarker) { - aStoredMarker->SetGeneration(mGeneration); - mStoredMarkers.insert(aStoredMarker); -} - -void ProfileBuffer::deleteExpiredStoredMarkers() { - // Delete markers of samples that have been overwritten due to circular - // buffer wraparound. - uint32_t generation = mGeneration; - while (mStoredMarkers.peek() && - mStoredMarkers.peek()->HasExpired(generation)) { - delete mStoredMarkers.popHead(); - } -} - -void ProfileBuffer::reset() { - mGeneration += 2; - mReadPos = mWritePos = 0; -} - -#define DYNAMIC_MAX_STRING 8192 - -char* ProfileBuffer::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 != mWritePos && !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; -} - - diff --git a/tools/profiler/core/ProfileBuffer.h b/tools/profiler/core/ProfileBuffer.h deleted file mode 100644 index 7d90fe385..000000000 --- a/tools/profiler/core/ProfileBuffer.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -*- 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 MOZ_PROFILE_BUFFER_H -#define MOZ_PROFILE_BUFFER_H - -#include "ProfileEntry.h" -#include "platform.h" -#include "ProfileJSONWriter.h" -#include "mozilla/RefPtr.h" -#include "mozilla/RefCounted.h" - -class ProfileBuffer : public mozilla::RefCounted<ProfileBuffer> { -public: - MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ProfileBuffer) - - explicit ProfileBuffer(int aEntrySize); - - virtual ~ProfileBuffer(); - - void addTag(const ProfileEntry& aTag); - void StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId, double aSinceTime, - JSContext* cx, UniqueStacks& aUniqueStacks); - void StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId, double aSinceTime, - UniqueStacks& aUniqueStacks); - void DuplicateLastSample(int aThreadId); - - void addStoredMarker(ProfilerMarker* aStoredMarker); - - // The following two methods are not signal safe! They delete markers. - void deleteExpiredStoredMarkers(); - void reset(); - -protected: - char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff); - int FindLastSampleOfThread(int aThreadId); - -public: - // Circular buffer 'Keep One Slot Open' implementation for simplicity - mozilla::UniquePtr<ProfileEntry[]> mEntries; - - // Points to the next entry we will write to, which is also the one at which - // we need to stop reading. - int mWritePos; - - // Points to the entry at which we can start reading. - int mReadPos; - - // The number of entries in our buffer. - int mEntrySize; - - // How many times mWritePos has wrapped around. - uint32_t mGeneration; - - // Markers that marker entries in the buffer might refer to. - ProfilerMarkerLinkedList mStoredMarkers; -}; - -#endif diff --git a/tools/profiler/core/ProfileEntry.cpp b/tools/profiler/core/ProfileEntry.cpp deleted file mode 100644 index 22d53a6f3..000000000 --- a/tools/profiler/core/ProfileEntry.cpp +++ /dev/null @@ -1,881 +0,0 @@ -/* -*- 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 <ostream> -#include "platform.h" -#include "mozilla/HashFunctions.h" - -#ifndef SPS_STANDALONE -#include "nsThreadUtils.h" -#include "nsXULAppAPI.h" - -// JS -#include "jsapi.h" -#include "jsfriendapi.h" -#include "js/TrackedOptimizationInfo.h" -#endif - -// Self -#include "ProfileEntry.h" - -using mozilla::MakeUnique; -using mozilla::UniquePtr; -using mozilla::Maybe; -using mozilla::Some; -using mozilla::Nothing; -using mozilla::JSONWriter; - - -//////////////////////////////////////////////////////////////////////// -// BEGIN ProfileEntry - -ProfileEntry::ProfileEntry() - : mTagData(nullptr) - , mTagName(0) -{ } - -// aTagData must not need release (i.e. be a string from the text segment) -ProfileEntry::ProfileEntry(char aTagName, const char *aTagData) - : mTagData(aTagData) - , mTagName(aTagName) -{ } - -ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker) - : mTagMarker(aTagMarker) - , mTagName(aTagName) -{ } - -ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr) - : mTagPtr(aTagPtr) - , mTagName(aTagName) -{ } - -ProfileEntry::ProfileEntry(char aTagName, double aTagDouble) - : mTagDouble(aTagDouble) - , mTagName(aTagName) -{ } - -ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset) - : mTagOffset(aTagOffset) - , mTagName(aTagName) -{ } - -ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress) - : mTagAddress(aTagAddress) - , mTagName(aTagName) -{ } - -ProfileEntry::ProfileEntry(char aTagName, int aTagInt) - : mTagInt(aTagInt) - , mTagName(aTagName) -{ } - -ProfileEntry::ProfileEntry(char aTagName, char aTagChar) - : mTagChar(aTagChar) - , mTagName(aTagName) -{ } - -bool ProfileEntry::is_ent_hint(char hintChar) { - return mTagName == 'h' && mTagChar == hintChar; -} - -bool ProfileEntry::is_ent_hint() { - return mTagName == 'h'; -} - -bool ProfileEntry::is_ent(char tagChar) { - return mTagName == tagChar; -} - -void* ProfileEntry::get_tagPtr() { - // No consistency checking. Oh well. - return mTagPtr; -} - -// END ProfileEntry -//////////////////////////////////////////////////////////////////////// - -class JSONSchemaWriter -{ - JSONWriter& mWriter; - uint32_t mIndex; - -public: - explicit JSONSchemaWriter(JSONWriter& aWriter) - : mWriter(aWriter) - , mIndex(0) - { - aWriter.StartObjectProperty("schema"); - } - - void WriteField(const char* aName) { - mWriter.IntProperty(aName, mIndex++); - } - - ~JSONSchemaWriter() { - mWriter.EndObject(); - } -}; - -#ifndef SPS_STANDALONE -class StreamOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp -{ - JSONWriter& mWriter; - UniqueJSONStrings& mUniqueStrings; - bool mStartedTypeList; - -public: - StreamOptimizationTypeInfoOp(JSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings) - : mWriter(aWriter) - , mUniqueStrings(aUniqueStrings) - , mStartedTypeList(false) - { } - - void readType(const char* keyedBy, const char* name, - const char* location, Maybe<unsigned> lineno) override { - if (!mStartedTypeList) { - mStartedTypeList = true; - mWriter.StartObjectElement(); - mWriter.StartArrayProperty("typeset"); - } - - mWriter.StartObjectElement(); - { - mUniqueStrings.WriteProperty(mWriter, "keyedBy", keyedBy); - if (name) { - mUniqueStrings.WriteProperty(mWriter, "name", name); - } - if (location) { - mUniqueStrings.WriteProperty(mWriter, "location", location); - } - if (lineno.isSome()) { - mWriter.IntProperty("line", *lineno); - } - } - mWriter.EndObject(); - } - - void operator()(JS::TrackedTypeSite site, const char* mirType) override { - if (mStartedTypeList) { - mWriter.EndArray(); - mStartedTypeList = false; - } else { - mWriter.StartObjectElement(); - } - - { - mUniqueStrings.WriteProperty(mWriter, "site", JS::TrackedTypeSiteString(site)); - mUniqueStrings.WriteProperty(mWriter, "mirType", mirType); - } - mWriter.EndObject(); - } -}; - -// As mentioned in ProfileEntry.h, the JSON format contains many arrays whose -// elements are laid out according to various schemas to help -// de-duplication. This RAII class helps write these arrays by keeping track of -// the last non-null element written and adding the appropriate number of null -// elements when writing new non-null elements. It also automatically opens and -// closes an array element on the given JSON writer. -// -// Example usage: -// -// // Define the schema of elements in this type of array: [FOO, BAR, BAZ] -// enum Schema : uint32_t { -// FOO = 0, -// BAR = 1, -// BAZ = 2 -// }; -// -// AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings); -// if (shouldWriteFoo) { -// writer.IntElement(FOO, getFoo()); -// } -// ... etc ... -class MOZ_RAII AutoArraySchemaWriter -{ - friend class AutoObjectWriter; - - SpliceableJSONWriter& mJSONWriter; - UniqueJSONStrings* mStrings; - uint32_t mNextFreeIndex; - -public: - AutoArraySchemaWriter(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aStrings) - : mJSONWriter(aWriter) - , mStrings(&aStrings) - , mNextFreeIndex(0) - { - mJSONWriter.StartArrayElement(); - } - - // If you don't have access to a UniqueStrings, you had better not try and - // write a string element down the line! - explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter) - : mJSONWriter(aWriter) - , mStrings(nullptr) - , mNextFreeIndex(0) - { - mJSONWriter.StartArrayElement(); - } - - ~AutoArraySchemaWriter() { - mJSONWriter.EndArray(); - } - - void FillUpTo(uint32_t aIndex) { - MOZ_ASSERT(aIndex >= mNextFreeIndex); - mJSONWriter.NullElements(aIndex - mNextFreeIndex); - mNextFreeIndex = aIndex + 1; - } - - void IntElement(uint32_t aIndex, uint32_t aValue) { - FillUpTo(aIndex); - mJSONWriter.IntElement(aValue); - } - - void DoubleElement(uint32_t aIndex, double aValue) { - FillUpTo(aIndex); - mJSONWriter.DoubleElement(aValue); - } - - void StringElement(uint32_t aIndex, const char* aValue) { - MOZ_RELEASE_ASSERT(mStrings); - FillUpTo(aIndex); - mStrings->WriteElement(mJSONWriter, aValue); - } -}; - -class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp -{ - SpliceableJSONWriter& mWriter; - UniqueJSONStrings& mUniqueStrings; - -public: - StreamOptimizationAttemptsOp(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings) - : mWriter(aWriter), - mUniqueStrings(aUniqueStrings) - { } - - void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override { - enum Schema : uint32_t { - STRATEGY = 0, - OUTCOME = 1 - }; - - AutoArraySchemaWriter writer(mWriter, mUniqueStrings); - writer.StringElement(STRATEGY, JS::TrackedStrategyString(strategy)); - writer.StringElement(OUTCOME, JS::TrackedOutcomeString(outcome)); - } -}; - -class StreamJSFramesOp : public JS::ForEachProfiledFrameOp -{ - void* mReturnAddress; - UniqueStacks::Stack& mStack; - unsigned mDepth; - -public: - StreamJSFramesOp(void* aReturnAddr, UniqueStacks::Stack& aStack) - : mReturnAddress(aReturnAddr) - , mStack(aStack) - , mDepth(0) - { } - - unsigned depth() const { - MOZ_ASSERT(mDepth > 0); - return mDepth; - } - - void operator()(const JS::ForEachProfiledFrameOp::FrameHandle& aFrameHandle) override { - UniqueStacks::OnStackFrameKey frameKey(mReturnAddress, mDepth, aFrameHandle); - mStack.AppendFrame(frameKey); - mDepth++; - } -}; -#endif - -uint32_t UniqueJSONStrings::GetOrAddIndex(const char* aStr) -{ - uint32_t index; - StringKey key(aStr); - - auto it = mStringToIndexMap.find(key); - - if (it != mStringToIndexMap.end()) { - return it->second; - } - index = mStringToIndexMap.size(); - mStringToIndexMap[key] = index; - mStringTableWriter.StringElement(aStr); - return index; -} - -bool UniqueStacks::FrameKey::operator==(const FrameKey& aOther) const -{ - return mLocation == aOther.mLocation && - mLine == aOther.mLine && - mCategory == aOther.mCategory && - mJITAddress == aOther.mJITAddress && - mJITDepth == aOther.mJITDepth; -} - -bool UniqueStacks::StackKey::operator==(const StackKey& aOther) const -{ - MOZ_ASSERT_IF(mPrefix == aOther.mPrefix, mPrefixHash == aOther.mPrefixHash); - return mPrefix == aOther.mPrefix && mFrame == aOther.mFrame; -} - -UniqueStacks::Stack::Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot) - : mUniqueStacks(aUniqueStacks) - , mStack(aUniqueStacks.GetOrAddFrameIndex(aRoot)) -{ -} - -void UniqueStacks::Stack::AppendFrame(const OnStackFrameKey& aFrame) -{ - // Compute the prefix hash and index before mutating mStack. - uint32_t prefixHash = mStack.Hash(); - uint32_t prefix = mUniqueStacks.GetOrAddStackIndex(mStack); - mStack.UpdateHash(prefixHash, prefix, mUniqueStacks.GetOrAddFrameIndex(aFrame)); -} - -uint32_t UniqueStacks::Stack::GetOrAddIndex() const -{ - return mUniqueStacks.GetOrAddStackIndex(mStack); -} - -uint32_t UniqueStacks::FrameKey::Hash() const -{ - uint32_t hash = 0; - if (!mLocation.IsEmpty()) { -#ifdef SPS_STANDALONE - hash = mozilla::HashString(mLocation.c_str()); -#else - hash = mozilla::HashString(mLocation.get()); -#endif - } - if (mLine.isSome()) { - hash = mozilla::AddToHash(hash, *mLine); - } - if (mCategory.isSome()) { - hash = mozilla::AddToHash(hash, *mCategory); - } - if (mJITAddress.isSome()) { - hash = mozilla::AddToHash(hash, *mJITAddress); - if (mJITDepth.isSome()) { - hash = mozilla::AddToHash(hash, *mJITDepth); - } - } - return hash; -} - -uint32_t UniqueStacks::StackKey::Hash() const -{ - if (mPrefix.isNothing()) { - return mozilla::HashGeneric(mFrame); - } - return mozilla::AddToHash(*mPrefixHash, mFrame); -} - -UniqueStacks::Stack UniqueStacks::BeginStack(const OnStackFrameKey& aRoot) -{ - return Stack(*this, aRoot); -} - -UniqueStacks::UniqueStacks(JSContext* aContext) - : mContext(aContext) - , mFrameCount(0) -{ - mFrameTableWriter.StartBareList(); - mStackTableWriter.StartBareList(); -} - -#ifdef SPS_STANDALONE -uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack) -{ - uint32_t index; - auto it = mStackToIndexMap.find(aStack); - - if (it != mStackToIndexMap.end()) { - return it->second; - } - - index = mStackToIndexMap.size(); - mStackToIndexMap[aStack] = index; - StreamStack(aStack); - return index; -} -#else -uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack) -{ - uint32_t index; - if (mStackToIndexMap.Get(aStack, &index)) { - MOZ_ASSERT(index < mStackToIndexMap.Count()); - return index; - } - - index = mStackToIndexMap.Count(); - mStackToIndexMap.Put(aStack, index); - StreamStack(aStack); - return index; -} -#endif - -#ifdef SPS_STANDALONE -uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame) -{ - uint32_t index; - auto it = mFrameToIndexMap.find(aFrame); - if (it != mFrameToIndexMap.end()) { - MOZ_ASSERT(it->second < mFrameCount); - return it->second; - } - - // A manual count is used instead of mFrameToIndexMap.Count() due to - // forwarding of canonical JIT frames above. - index = mFrameCount++; - mFrameToIndexMap[aFrame] = index; - StreamFrame(aFrame); - return index; -} -#else -uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame) -{ - uint32_t index; - if (mFrameToIndexMap.Get(aFrame, &index)) { - MOZ_ASSERT(index < mFrameCount); - return index; - } - - // If aFrame isn't canonical, forward it to the canonical frame's index. - if (aFrame.mJITFrameHandle) { - void* canonicalAddr = aFrame.mJITFrameHandle->canonicalAddress(); - if (canonicalAddr != *aFrame.mJITAddress) { - OnStackFrameKey canonicalKey(canonicalAddr, *aFrame.mJITDepth, *aFrame.mJITFrameHandle); - uint32_t canonicalIndex = GetOrAddFrameIndex(canonicalKey); - mFrameToIndexMap.Put(aFrame, canonicalIndex); - return canonicalIndex; - } - } - - // A manual count is used instead of mFrameToIndexMap.Count() due to - // forwarding of canonical JIT frames above. - index = mFrameCount++; - mFrameToIndexMap.Put(aFrame, index); - StreamFrame(aFrame); - return index; -} -#endif - -uint32_t UniqueStacks::LookupJITFrameDepth(void* aAddr) -{ - uint32_t depth; - - auto it = mJITFrameDepthMap.find(aAddr); - if (it != mJITFrameDepthMap.end()) { - depth = it->second; - MOZ_ASSERT(depth > 0); - return depth; - } - return 0; -} - -void UniqueStacks::AddJITFrameDepth(void* aAddr, unsigned depth) -{ - mJITFrameDepthMap[aAddr] = depth; -} - -void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter) -{ - mFrameTableWriter.EndBareList(); - aWriter.TakeAndSplice(mFrameTableWriter.WriteFunc()); -} - -void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter) -{ - mStackTableWriter.EndBareList(); - aWriter.TakeAndSplice(mStackTableWriter.WriteFunc()); -} - -void UniqueStacks::StreamStack(const StackKey& aStack) -{ - enum Schema : uint32_t { - PREFIX = 0, - FRAME = 1 - }; - - AutoArraySchemaWriter writer(mStackTableWriter, mUniqueStrings); - if (aStack.mPrefix.isSome()) { - writer.IntElement(PREFIX, *aStack.mPrefix); - } - writer.IntElement(FRAME, aStack.mFrame); -} - -void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame) -{ - enum Schema : uint32_t { - LOCATION = 0, - IMPLEMENTATION = 1, - OPTIMIZATIONS = 2, - LINE = 3, - CATEGORY = 4 - }; - - AutoArraySchemaWriter writer(mFrameTableWriter, mUniqueStrings); - -#ifndef SPS_STANDALONE - if (!aFrame.mJITFrameHandle) { -#else - { -#endif -#ifdef SPS_STANDALONE - writer.StringElement(LOCATION, aFrame.mLocation.c_str()); -#else - writer.StringElement(LOCATION, aFrame.mLocation.get()); -#endif - if (aFrame.mLine.isSome()) { - writer.IntElement(LINE, *aFrame.mLine); - } - if (aFrame.mCategory.isSome()) { - writer.IntElement(CATEGORY, *aFrame.mCategory); - } - } -#ifndef SPS_STANDALONE - else { - const JS::ForEachProfiledFrameOp::FrameHandle& jitFrame = *aFrame.mJITFrameHandle; - - writer.StringElement(LOCATION, jitFrame.label()); - - JS::ProfilingFrameIterator::FrameKind frameKind = jitFrame.frameKind(); - MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion || - frameKind == JS::ProfilingFrameIterator::Frame_Baseline); - writer.StringElement(IMPLEMENTATION, - frameKind == JS::ProfilingFrameIterator::Frame_Ion - ? "ion" - : "baseline"); - - if (jitFrame.hasTrackedOptimizations()) { - writer.FillUpTo(OPTIMIZATIONS); - mFrameTableWriter.StartObjectElement(); - { - mFrameTableWriter.StartArrayProperty("types"); - { - StreamOptimizationTypeInfoOp typeInfoOp(mFrameTableWriter, mUniqueStrings); - jitFrame.forEachOptimizationTypeInfo(typeInfoOp); - } - mFrameTableWriter.EndArray(); - - JS::Rooted<JSScript*> script(mContext); - jsbytecode* pc; - mFrameTableWriter.StartObjectProperty("attempts"); - { - { - JSONSchemaWriter schema(mFrameTableWriter); - schema.WriteField("strategy"); - schema.WriteField("outcome"); - } - - mFrameTableWriter.StartArrayProperty("data"); - { - StreamOptimizationAttemptsOp attemptOp(mFrameTableWriter, mUniqueStrings); - jitFrame.forEachOptimizationAttempt(attemptOp, script.address(), &pc); - } - mFrameTableWriter.EndArray(); - } - mFrameTableWriter.EndObject(); - - if (JSAtom* name = js::GetPropertyNameFromPC(script, pc)) { - char buf[512]; - JS_PutEscapedFlatString(buf, mozilla::ArrayLength(buf), js::AtomToFlatString(name), 0); - mUniqueStrings.WriteProperty(mFrameTableWriter, "propertyName", buf); - } - - unsigned line, column; - line = JS_PCToLineNumber(script, pc, &column); - mFrameTableWriter.IntProperty("line", line); - mFrameTableWriter.IntProperty("column", column); - } - mFrameTableWriter.EndObject(); - } - } -#endif -} - -struct ProfileSample -{ - uint32_t mStack; - Maybe<double> mTime; - Maybe<double> mResponsiveness; - Maybe<double> mRSS; - Maybe<double> mUSS; - Maybe<int> mFrameNumber; - Maybe<double> mPower; -}; - -static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample) -{ - enum Schema : uint32_t { - STACK = 0, - TIME = 1, - RESPONSIVENESS = 2, - RSS = 3, - USS = 4, - FRAME_NUMBER = 5, - POWER = 6 - }; - - AutoArraySchemaWriter writer(aWriter); - - writer.IntElement(STACK, aSample.mStack); - - if (aSample.mTime.isSome()) { - writer.DoubleElement(TIME, *aSample.mTime); - } - - if (aSample.mResponsiveness.isSome()) { - writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness); - } - - if (aSample.mRSS.isSome()) { - writer.DoubleElement(RSS, *aSample.mRSS); - } - - if (aSample.mUSS.isSome()) { - writer.DoubleElement(USS, *aSample.mUSS); - } - - if (aSample.mFrameNumber.isSome()) { - writer.IntElement(FRAME_NUMBER, *aSample.mFrameNumber); - } - - if (aSample.mPower.isSome()) { - writer.DoubleElement(POWER, *aSample.mPower); - } -} - -void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId, - double aSinceTime, JSContext* aContext, - UniqueStacks& aUniqueStacks) -{ - Maybe<ProfileSample> sample; - int readPos = mReadPos; - int currentThreadID = -1; - Maybe<double> currentTime; - UniquePtr<char[]> tagBuff = MakeUnique<char[]>(DYNAMIC_MAX_STRING); - - while (readPos != mWritePos) { - ProfileEntry entry = mEntries[readPos]; - if (entry.mTagName == 'T') { - currentThreadID = entry.mTagInt; - currentTime.reset(); - int readAheadPos = (readPos + 1) % mEntrySize; - if (readAheadPos != mWritePos) { - ProfileEntry readAheadEntry = mEntries[readAheadPos]; - if (readAheadEntry.mTagName == 't') { - currentTime = Some(readAheadEntry.mTagDouble); - } - } - } - if (currentThreadID == aThreadId && (currentTime.isNothing() || *currentTime >= aSinceTime)) { - switch (entry.mTagName) { - case 'r': - if (sample.isSome()) { - sample->mResponsiveness = Some(entry.mTagDouble); - } - break; - case 'p': - if (sample.isSome()) { - sample->mPower = Some(entry.mTagDouble); - } - break; - case 'R': - if (sample.isSome()) { - sample->mRSS = Some(entry.mTagDouble); - } - break; - case 'U': - if (sample.isSome()) { - sample->mUSS = Some(entry.mTagDouble); - } - break; - case 'f': - if (sample.isSome()) { - sample->mFrameNumber = Some(entry.mTagInt); - } - break; - case 's': - { - // end the previous sample if there was one - if (sample.isSome()) { - WriteSample(aWriter, *sample); - sample.reset(); - } - // begin the next sample - sample.emplace(); - sample->mTime = currentTime; - - // Seek forward through the entire sample, looking for frames - // this is an easier approach to reason about than adding more - // control variables and cases to the loop that goes through the buffer once - - UniqueStacks::Stack stack = - aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)")); - - int framePos = (readPos + 1) % mEntrySize; - ProfileEntry frame = mEntries[framePos]; - while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') { - int incBy = 1; - frame = mEntries[framePos]; - - // Read ahead to the next tag, if it's a 'd' tag process it now - const char* tagStringData = frame.mTagData; - int readAheadPos = (framePos + 1) % mEntrySize; - // Make sure the string is always null terminated if it fills up - // DYNAMIC_MAX_STRING-2 - tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; - - if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') { - tagStringData = processDynamicTag(framePos, &incBy, tagBuff.get()); - } - - // Write one frame. It can have either - // 1. only location - 'l' containing a memory address - // 2. location and line number - 'c' followed by 'd's, - // an optional 'n' and an optional 'y' - // 3. a JIT return address - 'j' containing native code address - if (frame.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)frame.mTagPtr; - snprintf(tagBuff.get(), DYNAMIC_MAX_STRING, "%#llx", pc); - stack.AppendFrame(UniqueStacks::OnStackFrameKey(tagBuff.get())); - } else if (frame.mTagName == 'c') { - UniqueStacks::OnStackFrameKey frameKey(tagStringData); - readAheadPos = (framePos + incBy) % mEntrySize; - if (readAheadPos != mWritePos && - mEntries[readAheadPos].mTagName == 'n') { - frameKey.mLine = Some((unsigned) mEntries[readAheadPos].mTagInt); - incBy++; - } - readAheadPos = (framePos + incBy) % mEntrySize; - if (readAheadPos != mWritePos && - mEntries[readAheadPos].mTagName == 'y') { - frameKey.mCategory = Some((unsigned) mEntries[readAheadPos].mTagInt); - incBy++; - } - stack.AppendFrame(frameKey); -#ifndef SPS_STANDALONE - } else if (frame.mTagName == 'J') { - // A JIT frame may expand to multiple frames due to inlining. - void* pc = frame.mTagPtr; - unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc); - if (depth == 0) { - StreamJSFramesOp framesOp(pc, stack); - JS::ForEachProfiledFrame(aContext, pc, framesOp); - aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth()); - } else { - for (unsigned i = 0; i < depth; i++) { - UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i); - stack.AppendFrame(inlineFrameKey); - } - } -#endif - } - framePos = (framePos + incBy) % mEntrySize; - } - - sample->mStack = stack.GetOrAddIndex(); - break; - } - } - } - readPos = (readPos + 1) % mEntrySize; - } - if (sample.isSome()) { - WriteSample(aWriter, *sample); - } -} - -void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId, - double aSinceTime, UniqueStacks& aUniqueStacks) -{ - int readPos = mReadPos; - int currentThreadID = -1; - while (readPos != mWritePos) { - ProfileEntry entry = mEntries[readPos]; - if (entry.mTagName == 'T') { - currentThreadID = entry.mTagInt; - } else if (currentThreadID == aThreadId && entry.mTagName == 'm') { - const ProfilerMarker* marker = entry.getMarker(); - if (marker->GetTime() >= aSinceTime) { - entry.getMarker()->StreamJSON(aWriter, aUniqueStacks); - } - } - readPos = (readPos + 1) % mEntrySize; - } -} - -int ProfileBuffer::FindLastSampleOfThread(int aThreadId) -{ - // We search backwards from mWritePos-1 to mReadPos. - // Adding mEntrySize makes the result of the modulus positive. - for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize; - readPos != (mReadPos + mEntrySize - 1) % mEntrySize; - readPos = (readPos + mEntrySize - 1) % mEntrySize) { - ProfileEntry entry = mEntries[readPos]; - if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) { - return readPos; - } - } - - return -1; -} - -void ProfileBuffer::DuplicateLastSample(int aThreadId) -{ - int lastSampleStartPos = FindLastSampleOfThread(aThreadId); - if (lastSampleStartPos == -1) { - return; - } - - MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T'); - - addTag(mEntries[lastSampleStartPos]); - - // Go through the whole entry and duplicate it, until we find the next one. - for (int readPos = (lastSampleStartPos + 1) % mEntrySize; - readPos != mWritePos; - readPos = (readPos + 1) % mEntrySize) { - switch (mEntries[readPos].mTagName) { - case 'T': - // We're done. - return; - case 't': - // Copy with new time - addTag(ProfileEntry('t', (mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds())); - break; - case 'm': - // Don't copy markers - break; - // Copy anything else we don't know about - // L, B, S, c, s, d, l, f, h, r, t, p - default: - addTag(mEntries[readPos]); - break; - } - } -} - -// END ProfileBuffer -//////////////////////////////////////////////////////////////////////// - - -//////////////////////////////////////////////////////////////////////// -// BEGIN ThreadProfile - -// END ThreadProfile -//////////////////////////////////////////////////////////////////////// diff --git a/tools/profiler/core/ProfileEntry.h b/tools/profiler/core/ProfileEntry.h deleted file mode 100644 index b82a2f271..000000000 --- a/tools/profiler/core/ProfileEntry.h +++ /dev/null @@ -1,407 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef MOZ_PROFILE_ENTRY_H -#define MOZ_PROFILE_ENTRY_H - -#include <ostream> -#include "GeckoProfiler.h" -#include "platform.h" -#include "ProfileJSONWriter.h" -#include "ProfilerBacktrace.h" -#include "mozilla/RefPtr.h" -#include <string> -#include <map> -#ifndef SPS_STANDALONE -#include "js/ProfilingFrameIterator.h" -#include "js/TrackedOptimizationInfo.h" -#include "nsHashKeys.h" -#include "nsDataHashtable.h" -#endif -#include "mozilla/Maybe.h" -#include "mozilla/Vector.h" -#ifndef SPS_STANDALONE -#include "gtest/MozGtestFriend.h" -#else -#define FRIEND_TEST(a, b) // TODO Support standalone gtest -#endif -#include "mozilla/HashFunctions.h" -#include "mozilla/UniquePtr.h" - -class ThreadProfile; - -// NB: Packing this structure has been shown to cause SIGBUS issues on ARM. -#ifndef __arm__ -#pragma pack(push, 1) -#endif - -class ProfileEntry -{ -public: - ProfileEntry(); - - // aTagData must not need release (i.e. be a string from the text segment) - ProfileEntry(char aTagName, const char *aTagData); - ProfileEntry(char aTagName, void *aTagPtr); - ProfileEntry(char aTagName, ProfilerMarker *aTagMarker); - ProfileEntry(char aTagName, double aTagDouble); - ProfileEntry(char aTagName, uintptr_t aTagOffset); - ProfileEntry(char aTagName, Address aTagAddress); - ProfileEntry(char aTagName, int aTagLine); - ProfileEntry(char aTagName, char aTagChar); - bool is_ent_hint(char hintChar); - bool is_ent_hint(); - bool is_ent(char tagName); - void* get_tagPtr(); - const ProfilerMarker* getMarker() { - MOZ_ASSERT(mTagName == 'm'); - return mTagMarker; - } - - char getTagName() const { return mTagName; } - -private: - FRIEND_TEST(ThreadProfile, InsertOneTag); - FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer); - FRIEND_TEST(ThreadProfile, InsertTagsNoWrap); - FRIEND_TEST(ThreadProfile, InsertTagsWrap); - FRIEND_TEST(ThreadProfile, MemoryMeasure); - friend class ProfileBuffer; - union { - const char* mTagData; - char mTagChars[sizeof(void*)]; - void* mTagPtr; - ProfilerMarker* mTagMarker; - double mTagDouble; - Address mTagAddress; - uintptr_t mTagOffset; - int mTagInt; - char mTagChar; - }; - char mTagName; -}; - -#ifndef __arm__ -#pragma pack(pop) -#endif - -class UniqueJSONStrings -{ -public: - UniqueJSONStrings() { - mStringTableWriter.StartBareList(); - } - - void SpliceStringTableElements(SpliceableJSONWriter& aWriter) { - aWriter.TakeAndSplice(mStringTableWriter.WriteFunc()); - } - - void WriteProperty(mozilla::JSONWriter& aWriter, const char* aName, const char* aStr) { - aWriter.IntProperty(aName, GetOrAddIndex(aStr)); - } - - void WriteElement(mozilla::JSONWriter& aWriter, const char* aStr) { - aWriter.IntElement(GetOrAddIndex(aStr)); - } - - uint32_t GetOrAddIndex(const char* aStr); - - struct StringKey { - - explicit StringKey(const char* aStr) - : mStr(strdup(aStr)) - { - mHash = mozilla::HashString(mStr); - } - - StringKey(const StringKey& aOther) - : mStr(strdup(aOther.mStr)) - { - mHash = aOther.mHash; - } - - ~StringKey() { - free(mStr); - } - - uint32_t Hash() const; - bool operator==(const StringKey& aOther) const { - return strcmp(mStr, aOther.mStr) == 0; - } - bool operator<(const StringKey& aOther) const { - return mHash < aOther.mHash; - } - - private: - uint32_t mHash; - char* mStr; - }; -private: - SpliceableChunkedJSONWriter mStringTableWriter; - std::map<StringKey, uint32_t> mStringToIndexMap; -}; - -class UniqueStacks -{ -public: - struct FrameKey { -#ifdef SPS_STANDALONE - std::string mLocation; -#else - // This cannot be a std::string, as it is not memmove compatible, which - // is used by nsHashTable - nsCString mLocation; -#endif - mozilla::Maybe<unsigned> mLine; - mozilla::Maybe<unsigned> mCategory; - mozilla::Maybe<void*> mJITAddress; - mozilla::Maybe<uint32_t> mJITDepth; - - explicit FrameKey(const char* aLocation) - : mLocation(aLocation) - { - mHash = Hash(); - } - - FrameKey(const FrameKey& aToCopy) - : mLocation(aToCopy.mLocation) - , mLine(aToCopy.mLine) - , mCategory(aToCopy.mCategory) - , mJITAddress(aToCopy.mJITAddress) - , mJITDepth(aToCopy.mJITDepth) - { - mHash = Hash(); - } - - FrameKey(void* aJITAddress, uint32_t aJITDepth) - : mJITAddress(mozilla::Some(aJITAddress)) - , mJITDepth(mozilla::Some(aJITDepth)) - { - mHash = Hash(); - } - - uint32_t Hash() const; - bool operator==(const FrameKey& aOther) const; - bool operator<(const FrameKey& aOther) const { - return mHash < aOther.mHash; - } - - private: - uint32_t mHash; - }; - - // A FrameKey that holds a scoped reference to a JIT FrameHandle. - struct MOZ_STACK_CLASS OnStackFrameKey : public FrameKey { - explicit OnStackFrameKey(const char* aLocation) - : FrameKey(aLocation) -#ifndef SPS_STANDALONE - , mJITFrameHandle(nullptr) -#endif - { } - - OnStackFrameKey(const OnStackFrameKey& aToCopy) - : FrameKey(aToCopy) -#ifndef SPS_STANDALONE - , mJITFrameHandle(aToCopy.mJITFrameHandle) -#endif - { } - -#ifndef SPS_STANDALONE - const JS::ForEachProfiledFrameOp::FrameHandle* mJITFrameHandle; - - OnStackFrameKey(void* aJITAddress, unsigned aJITDepth) - : FrameKey(aJITAddress, aJITDepth) - , mJITFrameHandle(nullptr) - { } - - OnStackFrameKey(void* aJITAddress, unsigned aJITDepth, - const JS::ForEachProfiledFrameOp::FrameHandle& aJITFrameHandle) - : FrameKey(aJITAddress, aJITDepth) - , mJITFrameHandle(&aJITFrameHandle) - { } -#endif - }; - - struct StackKey { - mozilla::Maybe<uint32_t> mPrefixHash; - mozilla::Maybe<uint32_t> mPrefix; - uint32_t mFrame; - - explicit StackKey(uint32_t aFrame) - : mFrame(aFrame) - { - mHash = Hash(); - } - - uint32_t Hash() const; - bool operator==(const StackKey& aOther) const; - bool operator<(const StackKey& aOther) const { - return mHash < aOther.mHash; - } - - void UpdateHash(uint32_t aPrefixHash, uint32_t aPrefix, uint32_t aFrame) { - mPrefixHash = mozilla::Some(aPrefixHash); - mPrefix = mozilla::Some(aPrefix); - mFrame = aFrame; - mHash = Hash(); - } - - private: - uint32_t mHash; - }; - - class Stack { - public: - Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot); - - void AppendFrame(const OnStackFrameKey& aFrame); - uint32_t GetOrAddIndex() const; - - private: - UniqueStacks& mUniqueStacks; - StackKey mStack; - }; - - explicit UniqueStacks(JSContext* aContext); - - Stack BeginStack(const OnStackFrameKey& aRoot); - uint32_t LookupJITFrameDepth(void* aAddr); - void AddJITFrameDepth(void* aAddr, unsigned depth); - void SpliceFrameTableElements(SpliceableJSONWriter& aWriter); - void SpliceStackTableElements(SpliceableJSONWriter& aWriter); - -private: - uint32_t GetOrAddFrameIndex(const OnStackFrameKey& aFrame); - uint32_t GetOrAddStackIndex(const StackKey& aStack); - void StreamFrame(const OnStackFrameKey& aFrame); - void StreamStack(const StackKey& aStack); - -public: - UniqueJSONStrings mUniqueStrings; - -private: - JSContext* mContext; - - // To avoid incurring JitcodeGlobalTable lookup costs for every JIT frame, - // we cache the depth of frames keyed by JIT code address. If an address a - // maps to a depth d, then frames keyed by a for depths 0 to d are - // guaranteed to be in mFrameToIndexMap. - std::map<void*, uint32_t> mJITFrameDepthMap; - - uint32_t mFrameCount; - SpliceableChunkedJSONWriter mFrameTableWriter; -#ifdef SPS_STANDALNOE - std::map<FrameKey, uint32_t> mFrameToIndexMap; -#else - nsDataHashtable<nsGenericHashKey<FrameKey>, uint32_t> mFrameToIndexMap; -#endif - - SpliceableChunkedJSONWriter mStackTableWriter; - - // This sucks but this is really performance critical, nsDataHashtable is way faster - // than map/unordered_map but nsDataHashtable is tied to xpcom so we ifdef - // until we can find a better solution. -#ifdef SPS_STANDALONE - std::map<StackKey, uint32_t> mStackToIndexMap; -#else - nsDataHashtable<nsGenericHashKey<StackKey>, uint32_t> mStackToIndexMap; -#endif -}; - -// -// ThreadProfile JSON Format -// ------------------------- -// -// The profile contains much duplicate information. The output JSON of the -// profile attempts to deduplicate strings, frames, and stack prefixes, to cut -// down on size and to increase JSON streaming speed. Deduplicated values are -// streamed as indices into their respective tables. -// -// Further, arrays of objects with the same set of properties (e.g., samples, -// frames) are output as arrays according to a schema instead of an object -// with property names. A property that is not present is represented in the -// array as null or undefined. -// -// The format of the thread profile JSON is shown by the following example -// with 1 sample and 1 marker: -// -// { -// "name": "Foo", -// "tid": 42, -// "samples": -// { -// "schema": -// { -// "stack": 0, /* index into stackTable */ -// "time": 1, /* number */ -// "responsiveness": 2, /* number */ -// "rss": 3, /* number */ -// "uss": 4, /* number */ -// "frameNumber": 5, /* number */ -// "power": 6 /* number */ -// }, -// "data": -// [ -// [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, responsiveness: 0.0 } */ -// ] -// }, -// -// "markers": -// { -// "schema": -// { -// "name": 0, /* index into stringTable */ -// "time": 1, /* number */ -// "data": 2 /* arbitrary JSON */ -// }, -// "data": -// [ -// [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */ -// ] -// }, -// -// "stackTable": -// { -// "schema": -// { -// "prefix": 0, /* index into stackTable */ -// "frame": 1 /* index into frameTable */ -// }, -// "data": -// [ -// [ null, 0 ], /* (root) */ -// [ 0, 1 ] /* (root) > foo.js */ -// ] -// }, -// -// "frameTable": -// { -// "schema": -// { -// "location": 0, /* index into stringTable */ -// "implementation": 1, /* index into stringTable */ -// "optimizations": 2, /* arbitrary JSON */ -// "line": 3, /* number */ -// "category": 4 /* number */ -// }, -// "data": -// [ -// [ 0 ], /* { location: '(root)' } */ -// [ 1, 2 ] /* { location: 'foo.js', implementation: 'baseline' } */ -// ] -// }, -// -// "stringTable": -// [ -// "(root)", -// "foo.js", -// "baseline", -// "example marker" -// ] -// } -// - -#endif /* ndef MOZ_PROFILE_ENTRY_H */ diff --git a/tools/profiler/core/ProfileJSONWriter.cpp b/tools/profiler/core/ProfileJSONWriter.cpp deleted file mode 100644 index 65a9425a3..000000000 --- a/tools/profiler/core/ProfileJSONWriter.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* -*- 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 "mozilla/HashFunctions.h" - -#include "ProfileJSONWriter.h" - -void -ChunkedJSONWriteFunc::Write(const char* aStr) -{ - MOZ_ASSERT(mChunkPtr >= mChunkList.back().get() && mChunkPtr <= mChunkEnd); - MOZ_ASSERT(mChunkEnd >= mChunkList.back().get() + mChunkLengths.back()); - MOZ_ASSERT(*mChunkPtr == '\0'); - - size_t len = strlen(aStr); - - // Most strings to be written are small, but subprocess profiles (e.g., - // from the content process in e10s) may be huge. If the string is larger - // than a chunk, allocate its own chunk. - char* newPtr; - if (len >= kChunkSize) { - AllocChunk(len + 1); - newPtr = mChunkPtr + len; - } else { - newPtr = mChunkPtr + len; - if (newPtr >= mChunkEnd) { - AllocChunk(kChunkSize); - newPtr = mChunkPtr + len; - } - } - - memcpy(mChunkPtr, aStr, len); - *newPtr = '\0'; - mChunkPtr = newPtr; - mChunkLengths.back() += len; -} - -mozilla::UniquePtr<char[]> -ChunkedJSONWriteFunc::CopyData() const -{ - MOZ_ASSERT(mChunkLengths.length() == mChunkList.length()); - size_t totalLen = 1; - for (size_t i = 0; i < mChunkLengths.length(); i++) { - MOZ_ASSERT(strlen(mChunkList[i].get()) == mChunkLengths[i]); - totalLen += mChunkLengths[i]; - } - mozilla::UniquePtr<char[]> c = mozilla::MakeUnique<char[]>(totalLen); - char* ptr = c.get(); - for (size_t i = 0; i < mChunkList.length(); i++) { - size_t len = mChunkLengths[i]; - memcpy(ptr, mChunkList[i].get(), len); - ptr += len; - } - *ptr = '\0'; - return c; -} - -void -ChunkedJSONWriteFunc::Take(ChunkedJSONWriteFunc&& aOther) -{ - for (size_t i = 0; i < aOther.mChunkList.length(); i++) { - MOZ_ALWAYS_TRUE(mChunkLengths.append(aOther.mChunkLengths[i])); - MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(aOther.mChunkList[i]))); - } - mChunkPtr = mChunkList.back().get() + mChunkLengths.back(); - mChunkEnd = mChunkPtr; - aOther.mChunkPtr = nullptr; - aOther.mChunkEnd = nullptr; - aOther.mChunkList.clear(); - aOther.mChunkLengths.clear(); -} - -void -ChunkedJSONWriteFunc::AllocChunk(size_t aChunkSize) -{ - MOZ_ASSERT(mChunkLengths.length() == mChunkList.length()); - mozilla::UniquePtr<char[]> newChunk = mozilla::MakeUnique<char[]>(aChunkSize); - mChunkPtr = newChunk.get(); - mChunkEnd = mChunkPtr + aChunkSize; - *mChunkPtr = '\0'; - MOZ_ALWAYS_TRUE(mChunkLengths.append(0)); - MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(newChunk))); -} - -void -SpliceableJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc) -{ - Separator(); - for (size_t i = 0; i < aFunc->mChunkList.length(); i++) { - WriteFunc()->Write(aFunc->mChunkList[i].get()); - } - aFunc->mChunkPtr = nullptr; - aFunc->mChunkEnd = nullptr; - aFunc->mChunkList.clear(); - aFunc->mChunkLengths.clear(); - mNeedComma[mDepth] = true; -} - -void -SpliceableJSONWriter::Splice(const char* aStr) -{ - Separator(); - WriteFunc()->Write(aStr); - mNeedComma[mDepth] = true; -} - -void -SpliceableChunkedJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc) -{ - Separator(); - WriteFunc()->Take(mozilla::Move(*aFunc)); - mNeedComma[mDepth] = true; -} diff --git a/tools/profiler/core/ProfileJSONWriter.h b/tools/profiler/core/ProfileJSONWriter.h deleted file mode 100644 index d9e2115f9..000000000 --- a/tools/profiler/core/ProfileJSONWriter.h +++ /dev/null @@ -1,126 +0,0 @@ -/* -*- 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 PROFILEJSONWRITER_H -#define PROFILEJSONWRITER_H - -#include <ostream> -#include <string> -#include <string.h> - -#include "mozilla/JSONWriter.h" -#include "mozilla/UniquePtr.h" - -class SpliceableChunkedJSONWriter; - -// On average, profile JSONs are large enough such that we want to avoid -// reallocating its buffer when expanding. Additionally, the contents of the -// profile are not accessed until the profile is entirely written. For these -// reasons we use a chunked writer that keeps an array of chunks, which is -// concatenated together after writing is finished. -class ChunkedJSONWriteFunc : public mozilla::JSONWriteFunc -{ -public: - friend class SpliceableJSONWriter; - - ChunkedJSONWriteFunc() { - AllocChunk(kChunkSize); - } - - bool IsEmpty() const { - MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd && - mChunkList.length() == 0 && - mChunkLengths.length() == 0); - return !mChunkPtr; - } - - void Write(const char* aStr) override; - mozilla::UniquePtr<char[]> CopyData() const; - void Take(ChunkedJSONWriteFunc&& aOther); - -private: - void AllocChunk(size_t aChunkSize); - - static const size_t kChunkSize = 4096 * 512; - - // Pointer for writing inside the current chunk. - // - // The current chunk is always at the back of mChunkList, i.e., - // mChunkList.back() <= mChunkPtr <= mChunkEnd. - char* mChunkPtr; - - // Pointer to the end of the current chunk. - // - // The current chunk is always at the back of mChunkList, i.e., - // mChunkEnd >= mChunkList.back() + mChunkLengths.back(). - char* mChunkEnd; - - // List of chunks and their lengths. - // - // For all i, the length of the string in mChunkList[i] is - // mChunkLengths[i]. - mozilla::Vector<mozilla::UniquePtr<char[]>> mChunkList; - mozilla::Vector<size_t> mChunkLengths; -}; - -struct OStreamJSONWriteFunc : public mozilla::JSONWriteFunc -{ - explicit OStreamJSONWriteFunc(std::ostream& aStream) - : mStream(aStream) - { } - - void Write(const char* aStr) override { - mStream << aStr; - } - - std::ostream& mStream; -}; - -class SpliceableJSONWriter : public mozilla::JSONWriter -{ -public: - explicit SpliceableJSONWriter(mozilla::UniquePtr<mozilla::JSONWriteFunc> aWriter) - : JSONWriter(mozilla::Move(aWriter)) - { } - - void StartBareList(CollectionStyle aStyle = SingleLineStyle) { - StartCollection(nullptr, "", aStyle); - } - - void EndBareList() { - EndCollection(""); - } - - void NullElements(uint32_t aCount) { - for (uint32_t i = 0; i < aCount; i++) { - NullElement(); - } - } - - void Splice(const ChunkedJSONWriteFunc* aFunc); - void Splice(const char* aStr); - - // Takes the chunks from aFunc and write them. If move is not possible - // (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its - // storage cleared. - virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc); -}; - -class SpliceableChunkedJSONWriter : public SpliceableJSONWriter -{ -public: - explicit SpliceableChunkedJSONWriter() - : SpliceableJSONWriter(mozilla::MakeUnique<ChunkedJSONWriteFunc>()) - { } - - ChunkedJSONWriteFunc* WriteFunc() const { - return static_cast<ChunkedJSONWriteFunc*>(JSONWriter::WriteFunc()); - } - - // Adopts the chunks from aFunc without copying. - virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc) override; -}; - -#endif // PROFILEJSONWRITER_H diff --git a/tools/profiler/core/ProfilerBacktrace.cpp b/tools/profiler/core/ProfilerBacktrace.cpp deleted file mode 100644 index 7302dd64c..000000000 --- a/tools/profiler/core/ProfilerBacktrace.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "ProfilerBacktrace.h" - -#include "ProfileJSONWriter.h" -#include "SyncProfile.h" - -ProfilerBacktrace::ProfilerBacktrace(SyncProfile* aProfile) - : mProfile(aProfile) -{ - MOZ_COUNT_CTOR(ProfilerBacktrace); - MOZ_ASSERT(aProfile); -} - -ProfilerBacktrace::~ProfilerBacktrace() -{ - MOZ_COUNT_DTOR(ProfilerBacktrace); - if (mProfile->ShouldDestroy()) { - delete mProfile; - } -} - -void -ProfilerBacktrace::StreamJSON(SpliceableJSONWriter& aWriter, - UniqueStacks& aUniqueStacks) -{ - ::MutexAutoLock lock(mProfile->GetMutex()); - mProfile->StreamJSON(aWriter, aUniqueStacks); -} diff --git a/tools/profiler/core/ProfilerMarkers.cpp b/tools/profiler/core/ProfilerMarkers.cpp deleted file mode 100644 index 3cb47de48..000000000 --- a/tools/profiler/core/ProfilerMarkers.cpp +++ /dev/null @@ -1,210 +0,0 @@ -/* -*- 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 "GeckoProfiler.h" -#include "ProfilerBacktrace.h" -#include "ProfilerMarkers.h" -#include "SyncProfile.h" -#ifndef SPS_STANDALONE -#include "gfxASurface.h" -#include "Layers.h" -#include "mozilla/Sprintf.h" -#endif - -ProfilerMarkerPayload::ProfilerMarkerPayload(ProfilerBacktrace* aStack) - : mStack(aStack) -{} - -ProfilerMarkerPayload::ProfilerMarkerPayload(const mozilla::TimeStamp& aStartTime, - const mozilla::TimeStamp& aEndTime, - ProfilerBacktrace* aStack) - : mStartTime(aStartTime) - , mEndTime(aEndTime) - , mStack(aStack) -{} - -ProfilerMarkerPayload::~ProfilerMarkerPayload() -{ - profiler_free_backtrace(mStack); -} - -void -ProfilerMarkerPayload::streamCommonProps(const char* aMarkerType, - SpliceableJSONWriter& aWriter, - UniqueStacks& aUniqueStacks) -{ - MOZ_ASSERT(aMarkerType); - aWriter.StringProperty("type", aMarkerType); - if (!mStartTime.IsNull()) { - aWriter.DoubleProperty("startTime", profiler_time(mStartTime)); - } - if (!mEndTime.IsNull()) { - aWriter.DoubleProperty("endTime", profiler_time(mEndTime)); - } - if (mStack) { - aWriter.StartObjectProperty("stack"); - { - mStack->StreamJSON(aWriter, aUniqueStacks); - } - aWriter.EndObject(); - } -} - -ProfilerMarkerTracing::ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData) - : mCategory(aCategory) - , mMetaData(aMetaData) -{ - if (aMetaData == TRACING_EVENT_BACKTRACE) { - SetStack(profiler_get_backtrace()); - } -} - -ProfilerMarkerTracing::ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData, - ProfilerBacktrace* aCause) - : mCategory(aCategory) - , mMetaData(aMetaData) -{ - if (aCause) { - SetStack(aCause); - } -} - -void -ProfilerMarkerTracing::StreamPayload(SpliceableJSONWriter& aWriter, - UniqueStacks& aUniqueStacks) -{ - streamCommonProps("tracing", aWriter, aUniqueStacks); - - if (GetCategory()) { - aWriter.StringProperty("category", GetCategory()); - } - if (GetMetaData() != TRACING_DEFAULT) { - if (GetMetaData() == TRACING_INTERVAL_START) { - aWriter.StringProperty("interval", "start"); - } else if (GetMetaData() == TRACING_INTERVAL_END) { - aWriter.StringProperty("interval", "end"); - } - } -} - -#ifndef SPS_STANDALONE -GPUMarkerPayload::GPUMarkerPayload( - const mozilla::TimeStamp& aCpuTimeStart, - const mozilla::TimeStamp& aCpuTimeEnd, - uint64_t aGpuTimeStart, - uint64_t aGpuTimeEnd) - - : ProfilerMarkerPayload(aCpuTimeStart, aCpuTimeEnd) - , mCpuTimeStart(aCpuTimeStart) - , mCpuTimeEnd(aCpuTimeEnd) - , mGpuTimeStart(aGpuTimeStart) - , mGpuTimeEnd(aGpuTimeEnd) -{ } - -void -GPUMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter, - UniqueStacks& aUniqueStacks) -{ - streamCommonProps("gpu_timer_query", aWriter, aUniqueStacks); - - aWriter.DoubleProperty("cpustart", profiler_time(mCpuTimeStart)); - aWriter.DoubleProperty("cpuend", profiler_time(mCpuTimeEnd)); - aWriter.IntProperty("gpustart", (int)mGpuTimeStart); - aWriter.IntProperty("gpuend", (int)mGpuTimeEnd); -} - -ProfilerMarkerImagePayload::ProfilerMarkerImagePayload(gfxASurface *aImg) - : mImg(aImg) -{ } - -void -ProfilerMarkerImagePayload::StreamPayload(SpliceableJSONWriter& aWriter, - UniqueStacks& aUniqueStacks) -{ - streamCommonProps("innerHTML", aWriter, aUniqueStacks); - // TODO: Finish me - //aWriter.NameValue("innerHTML", "<img src=''/>"); -} - -IOMarkerPayload::IOMarkerPayload(const char* aSource, - const char* aFilename, - const mozilla::TimeStamp& aStartTime, - const mozilla::TimeStamp& aEndTime, - ProfilerBacktrace* aStack) - : ProfilerMarkerPayload(aStartTime, aEndTime, aStack), - mSource(aSource) -{ - mFilename = aFilename ? strdup(aFilename) : nullptr; - MOZ_ASSERT(aSource); -} - -IOMarkerPayload::~IOMarkerPayload(){ - free(mFilename); -} - -void -IOMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks) -{ - streamCommonProps("io", aWriter, aUniqueStacks); - aWriter.StringProperty("source", mSource); - if (mFilename != nullptr) { - aWriter.StringProperty("filename", mFilename); - } -} - -void -ProfilerJSEventMarker(const char *event) -{ - PROFILER_MARKER(event); -} - -LayerTranslationPayload::LayerTranslationPayload(mozilla::layers::Layer* aLayer, - mozilla::gfx::Point aPoint) - : ProfilerMarkerPayload(mozilla::TimeStamp::Now(), mozilla::TimeStamp::Now(), nullptr) - , mLayer(aLayer) - , mPoint(aPoint) -{ -} - -void -LayerTranslationPayload::StreamPayload(SpliceableJSONWriter& aWriter, - UniqueStacks& aUniqueStacks) -{ - const size_t bufferSize = 32; - char buffer[bufferSize]; - SprintfLiteral(buffer, "%p", mLayer); - - aWriter.StringProperty("layer", buffer); - aWriter.IntProperty("x", mPoint.x); - aWriter.IntProperty("y", mPoint.y); - aWriter.StringProperty("category", "LayerTranslation"); -} - -TouchDataPayload::TouchDataPayload(const mozilla::ScreenIntPoint& aPoint) - : ProfilerMarkerPayload(mozilla::TimeStamp::Now(), mozilla::TimeStamp::Now(), nullptr) -{ - mPoint = aPoint; -} - -void -TouchDataPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks) -{ - aWriter.IntProperty("x", mPoint.x); - aWriter.IntProperty("y", mPoint.y); -} - -VsyncPayload::VsyncPayload(mozilla::TimeStamp aVsyncTimestamp) - : ProfilerMarkerPayload(aVsyncTimestamp, aVsyncTimestamp, nullptr) - , mVsyncTimestamp(aVsyncTimestamp) -{ -} - -void -VsyncPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks) -{ - aWriter.DoubleProperty("vsync", profiler_time(mVsyncTimestamp)); - aWriter.StringProperty("category", "VsyncTimestamp"); -} -#endif diff --git a/tools/profiler/core/StackTop.cpp b/tools/profiler/core/StackTop.cpp deleted file mode 100644 index 1f7944e5e..000000000 --- a/tools/profiler/core/StackTop.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifdef XP_MACOSX -#include <mach/task.h> -#include <mach/thread_act.h> -#include <pthread.h> -#elif XP_WIN -#include <windows.h> -#endif - -#include "StackTop.h" - -void *GetStackTop(void *guess) { -#if defined(XP_MACOSX) - pthread_t thread = pthread_self(); - return pthread_get_stackaddr_np(thread); -#elif defined(XP_WIN) -#if defined(_MSC_VER) && defined(_M_IX86) - // offset 0x18 from the FS segment register gives a pointer to - // the thread information block for the current thread - NT_TIB* pTib; - __asm { - MOV EAX, FS:[18h] - MOV pTib, EAX - } - return static_cast<void*>(pTib->StackBase); -#elif defined(__GNUC__) && defined(i386) - // offset 0x18 from the FS segment register gives a pointer to - // the thread information block for the current thread - NT_TIB* pTib; - asm ( "movl %%fs:0x18, %0\n" - : "=r" (pTib) - ); - return static_cast<void*>(pTib->StackBase); -#elif defined(_M_X64) || defined(__x86_64) - PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb()); - return reinterpret_cast<void*>(pTib->StackBase); -#else -#error Need a way to get the stack bounds on this platform (Windows) -#endif -#else - return guess; -#endif -} diff --git a/tools/profiler/core/StackTop.h b/tools/profiler/core/StackTop.h deleted file mode 100644 index a933d10b4..000000000 --- a/tools/profiler/core/StackTop.h +++ /dev/null @@ -1,10 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef MOZ_STACK_TOP_H -#define MOZ_STACK_TOP_H -void *GetStackTop(void *guess); -#endif diff --git a/tools/profiler/core/SyncProfile.cpp b/tools/profiler/core/SyncProfile.cpp deleted file mode 100644 index 4c4742f34..000000000 --- a/tools/profiler/core/SyncProfile.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "SyncProfile.h" - -SyncProfile::SyncProfile(ThreadInfo* aInfo, int aEntrySize) - : ThreadProfile(aInfo, new ProfileBuffer(aEntrySize)) - , mOwnerState(REFERENCED) -{ - MOZ_COUNT_CTOR(SyncProfile); -} - -SyncProfile::~SyncProfile() -{ - MOZ_COUNT_DTOR(SyncProfile); - - // SyncProfile owns the ThreadInfo; see NewSyncProfile. - ThreadInfo* info = GetThreadInfo(); - delete info; -} - -bool -SyncProfile::ShouldDestroy() -{ - ::MutexAutoLock lock(GetMutex()); - if (mOwnerState == OWNED) { - mOwnerState = OWNER_DESTROYING; - return true; - } - mOwnerState = ORPHANED; - return false; -} - -void -SyncProfile::EndUnwind() -{ - if (mOwnerState != ORPHANED) { - mOwnerState = OWNED; - } - // Save mOwnerState before we release the mutex - OwnerState ownerState = mOwnerState; - ThreadProfile::EndUnwind(); - if (ownerState == ORPHANED) { - delete this; - } -} - -// SyncProfiles' stacks are deduplicated in the context of the containing -// profile in which the backtrace is as a marker payload. -void -SyncProfile::StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks) -{ - ThreadProfile::StreamSamplesAndMarkers(aWriter, /* aSinceTime = */ 0, aUniqueStacks); -} diff --git a/tools/profiler/core/SyncProfile.h b/tools/profiler/core/SyncProfile.h deleted file mode 100644 index 58f6b0d81..000000000 --- a/tools/profiler/core/SyncProfile.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef __SYNCPROFILE_H -#define __SYNCPROFILE_H - -#include "ProfileEntry.h" -#include "ThreadProfile.h" - -class SyncProfile : public ThreadProfile -{ -public: - SyncProfile(ThreadInfo* aInfo, int aEntrySize); - ~SyncProfile(); - - // SyncProfiles' stacks are deduplicated in the context of the containing - // profile in which the backtrace is as a marker payload. - void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks); - - virtual void EndUnwind(); - virtual SyncProfile* AsSyncProfile() { return this; } - -private: - friend class ProfilerBacktrace; - - enum OwnerState - { - REFERENCED, // ProfilerBacktrace has a pointer to this but doesn't own - OWNED, // ProfilerBacktrace is responsible for destroying this - OWNER_DESTROYING, // ProfilerBacktrace owns this and is destroying - ORPHANED // No owner, we must destroy ourselves - }; - - bool ShouldDestroy(); - - OwnerState mOwnerState; -}; - -#endif // __SYNCPROFILE_H - diff --git a/tools/profiler/core/ThreadInfo.cpp b/tools/profiler/core/ThreadInfo.cpp deleted file mode 100644 index 0e25d2330..000000000 --- a/tools/profiler/core/ThreadInfo.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "ThreadInfo.h" -#include "ThreadProfile.h" - -#include "mozilla/DebugOnly.h" - -ThreadInfo::ThreadInfo(const char* aName, int aThreadId, - bool aIsMainThread, PseudoStack* aPseudoStack, - void* aStackTop) - : mName(strdup(aName)) - , mThreadId(aThreadId) - , mIsMainThread(aIsMainThread) - , mPseudoStack(aPseudoStack) - , mPlatformData(Sampler::AllocPlatformData(aThreadId)) - , mProfile(nullptr) - , mStackTop(aStackTop) - , mPendingDelete(false) -{ - MOZ_COUNT_CTOR(ThreadInfo); -#ifndef SPS_STANDALONE - mThread = NS_GetCurrentThread(); -#endif - - // We don't have to guess on mac -#ifdef XP_MACOSX - pthread_t self = pthread_self(); - mStackTop = pthread_get_stackaddr_np(self); -#endif -} - -ThreadInfo::~ThreadInfo() { - MOZ_COUNT_DTOR(ThreadInfo); - free(mName); - - if (mProfile) - delete mProfile; - - Sampler::FreePlatformData(mPlatformData); -} - -void -ThreadInfo::SetPendingDelete() -{ - mPendingDelete = true; - // We don't own the pseudostack so disconnect it. - mPseudoStack = nullptr; - if (mProfile) { - mProfile->SetPendingDelete(); - } -} - -bool -ThreadInfo::CanInvokeJS() const -{ -#ifdef SPS_STANDALONE - return false; -#else - nsIThread* thread = GetThread(); - if (!thread) { - MOZ_ASSERT(IsMainThread()); - return true; - } - bool result; - mozilla::DebugOnly<nsresult> rv = thread->GetCanInvokeJS(&result); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - return result; -#endif -} diff --git a/tools/profiler/core/ThreadInfo.h b/tools/profiler/core/ThreadInfo.h deleted file mode 100644 index 1cb4e5dc8..000000000 --- a/tools/profiler/core/ThreadInfo.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef MOZ_THREAD_INFO_H -#define MOZ_THREAD_INFO_H - -#include "platform.h" - -class ThreadInfo { - public: - ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack, void* aStackTop); - - virtual ~ThreadInfo(); - - const char* Name() const { return mName; } - int ThreadId() const { return mThreadId; } - - bool IsMainThread() const { return mIsMainThread; } - PseudoStack* Stack() const { return mPseudoStack; } - - void SetProfile(ThreadProfile* aProfile) { mProfile = aProfile; } - ThreadProfile* Profile() const { return mProfile; } - - PlatformData* GetPlatformData() const { return mPlatformData; } - void* StackTop() const { return mStackTop; } - - virtual void SetPendingDelete(); - bool IsPendingDelete() const { return mPendingDelete; } - -#ifndef SPS_STANDALONE - /** - * May be null for the main thread if the profiler was started during startup - */ - nsIThread* GetThread() const { return mThread.get(); } - -#endif - - bool CanInvokeJS() const; - - private: - char* mName; - int mThreadId; - const bool mIsMainThread; - PseudoStack* mPseudoStack; - PlatformData* mPlatformData; - ThreadProfile* mProfile; - void* mStackTop; -#ifndef SPS_STANDALONE - nsCOMPtr<nsIThread> mThread; -#endif - bool mPendingDelete; -}; - -// Just like ThreadInfo, but owns a reference to the PseudoStack. -class StackOwningThreadInfo : public ThreadInfo { - public: - StackOwningThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack, void* aStackTop); - virtual ~StackOwningThreadInfo(); - - virtual void SetPendingDelete(); -}; - -#endif diff --git a/tools/profiler/core/ThreadProfile.cpp b/tools/profiler/core/ThreadProfile.cpp deleted file mode 100644 index 7452a7ee8..000000000 --- a/tools/profiler/core/ThreadProfile.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -ThreadProfile::ThreadProfile(ThreadInfo* aInfo, ProfileBuffer* aBuffer) - : mThreadInfo(aInfo) - , mBuffer(aBuffer) - , mPseudoStack(aInfo->Stack()) - , mMutex(OS::CreateMutex("ThreadProfile::mMutex")) - , mThreadId(int(aInfo->ThreadId())) - , mIsMainThread(aInfo->IsMainThread()) - , mPlatformData(aInfo->GetPlatformData()) - , mStackTop(aInfo->StackTop()) -#ifndef SPS_STANDALONE - , mRespInfo(this) -#endif -#ifdef XP_LINUX - , mRssMemory(0) - , mUssMemory(0) -#endif -{ - MOZ_COUNT_CTOR(ThreadProfile); - MOZ_ASSERT(aBuffer); - - // I don't know if we can assert this. But we should warn. - MOZ_ASSERT(aInfo->ThreadId() >= 0, "native thread ID is < 0"); - MOZ_ASSERT(aInfo->ThreadId() <= INT32_MAX, "native thread ID is > INT32_MAX"); -} - -ThreadProfile::~ThreadProfile() -{ - MOZ_COUNT_DTOR(ThreadProfile); -} - -void ThreadProfile::addTag(const ProfileEntry& aTag) -{ - mBuffer->addTag(aTag); -} - -void ThreadProfile::addStoredMarker(ProfilerMarker *aStoredMarker) { - mBuffer->addStoredMarker(aStoredMarker); -} - -void ThreadProfile::StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime) -{ - // mUniqueStacks may already be emplaced from FlushSamplesAndMarkers. - if (!mUniqueStacks.isSome()) { -#ifndef SPS_STANDALONE - mUniqueStacks.emplace(mPseudoStack->mContext); -#else - mUniqueStacks.emplace(nullptr); -#endif - } - - aWriter.Start(SpliceableJSONWriter::SingleLineStyle); - { - StreamSamplesAndMarkers(aWriter, aSinceTime, *mUniqueStacks); - - aWriter.StartObjectProperty("stackTable"); - { - { - JSONSchemaWriter schema(aWriter); - schema.WriteField("prefix"); - schema.WriteField("frame"); - } - - aWriter.StartArrayProperty("data"); - { - mUniqueStacks->SpliceStackTableElements(aWriter); - } - aWriter.EndArray(); - } - aWriter.EndObject(); - - aWriter.StartObjectProperty("frameTable"); - { - { - JSONSchemaWriter schema(aWriter); - schema.WriteField("location"); - schema.WriteField("implementation"); - schema.WriteField("optimizations"); - schema.WriteField("line"); - schema.WriteField("category"); - } - - aWriter.StartArrayProperty("data"); - { - mUniqueStacks->SpliceFrameTableElements(aWriter); - } - aWriter.EndArray(); - } - aWriter.EndObject(); - - aWriter.StartArrayProperty("stringTable"); - { - mUniqueStacks->mUniqueStrings.SpliceStringTableElements(aWriter); - } - aWriter.EndArray(); - } - aWriter.End(); - - mUniqueStacks.reset(); -} - -void ThreadProfile::StreamSamplesAndMarkers(SpliceableJSONWriter& aWriter, double aSinceTime, - UniqueStacks& aUniqueStacks) -{ -#ifndef SPS_STANDALONE - // Thread meta data - if (XRE_GetProcessType() == GeckoProcessType_Plugin) { - // TODO Add the proper plugin name - aWriter.StringProperty("name", "Plugin"); - } else if (XRE_GetProcessType() == GeckoProcessType_Content) { - // This isn't going to really help once we have multiple content - // processes, but it'll do for now. - aWriter.StringProperty("name", "Content"); - } else { - aWriter.StringProperty("name", Name()); - } -#else - aWriter.StringProperty("name", Name()); -#endif - - aWriter.IntProperty("tid", static_cast<int>(mThreadId)); - - aWriter.StartObjectProperty("samples"); - { - { - JSONSchemaWriter schema(aWriter); - schema.WriteField("stack"); - schema.WriteField("time"); - schema.WriteField("responsiveness"); - schema.WriteField("rss"); - schema.WriteField("uss"); - schema.WriteField("frameNumber"); - schema.WriteField("power"); - } - - aWriter.StartArrayProperty("data"); - { - if (mSavedStreamedSamples) { - // We would only have saved streamed samples during shutdown - // streaming, which cares about dumping the entire buffer, and thus - // should have passed in 0 for aSinceTime. - MOZ_ASSERT(aSinceTime == 0); - aWriter.Splice(mSavedStreamedSamples.get()); - mSavedStreamedSamples.reset(); - } - mBuffer->StreamSamplesToJSON(aWriter, mThreadId, aSinceTime, -#ifndef SPS_STANDALONE - mPseudoStack->mContext, -#else - nullptr, -#endif - aUniqueStacks); - } - aWriter.EndArray(); - } - aWriter.EndObject(); - - aWriter.StartObjectProperty("markers"); - { - { - JSONSchemaWriter schema(aWriter); - schema.WriteField("name"); - schema.WriteField("time"); - schema.WriteField("data"); - } - - aWriter.StartArrayProperty("data"); - { - if (mSavedStreamedMarkers) { - MOZ_ASSERT(aSinceTime == 0); - aWriter.Splice(mSavedStreamedMarkers.get()); - mSavedStreamedMarkers.reset(); - } - mBuffer->StreamMarkersToJSON(aWriter, mThreadId, aSinceTime, aUniqueStacks); - } - aWriter.EndArray(); - } - aWriter.EndObject(); -} - -void ThreadProfile::FlushSamplesAndMarkers() -{ - // This function is used to serialize the current buffer just before - // JSContext destruction. - MOZ_ASSERT(mPseudoStack->mContext); - - // Unlike StreamJSObject, do not surround the samples in brackets by calling - // aWriter.{Start,End}BareList. The result string will be a comma-separated - // list of JSON object literals that will prepended by StreamJSObject into - // an existing array. - // - // Note that the UniqueStacks instance is persisted so that the frame-index - // mapping is stable across JS shutdown. -#ifndef SPS_STANDALONE - mUniqueStacks.emplace(mPseudoStack->mContext); -#else - mUniqueStacks.emplace(nullptr); -#endif - - { - SpliceableChunkedJSONWriter b; - b.StartBareList(); - { - mBuffer->StreamSamplesToJSON(b, mThreadId, /* aSinceTime = */ 0, -#ifndef SPS_STANDALONE - mPseudoStack->mContext, -#else - nullptr, -#endif - *mUniqueStacks); - } - b.EndBareList(); - mSavedStreamedSamples = b.WriteFunc()->CopyData(); - } - - { - SpliceableChunkedJSONWriter b; - b.StartBareList(); - { - mBuffer->StreamMarkersToJSON(b, mThreadId, /* aSinceTime = */ 0, *mUniqueStacks); - } - b.EndBareList(); - mSavedStreamedMarkers = b.WriteFunc()->CopyData(); - } - - // Reset the buffer. Attempting to symbolicate JS samples after mContext has - // gone away will crash. - mBuffer->reset(); -} - -PseudoStack* ThreadProfile::GetPseudoStack() -{ - return mPseudoStack; -} - -void ThreadProfile::BeginUnwind() -{ - mMutex->Lock(); -} - -void ThreadProfile::EndUnwind() -{ - mMutex->Unlock(); -} - -::Mutex& ThreadProfile::GetMutex() -{ - return *mMutex.get(); -} - -void ThreadProfile::DuplicateLastSample() -{ - mBuffer->DuplicateLastSample(mThreadId); -} - diff --git a/tools/profiler/core/ThreadProfile.h b/tools/profiler/core/ThreadProfile.h deleted file mode 100644 index ca2bbfe7a..000000000 --- a/tools/profiler/core/ThreadProfile.h +++ /dev/null @@ -1,107 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef MOZ_THREAD_PROFILE_H -#define MOZ_THREAD_PROFILE_H - -#include "ProfileBuffer.h" -#include "ThreadInfo.h" - -class ThreadProfile -{ -public: - ThreadProfile(ThreadInfo* aThreadInfo, ProfileBuffer* aBuffer); - virtual ~ThreadProfile(); - void addTag(const ProfileEntry& aTag); - - /** - * Track a marker which has been inserted into the ThreadProfile. - * This marker can safely be deleted once the generation has - * expired. - */ - void addStoredMarker(ProfilerMarker *aStoredMarker); - PseudoStack* GetPseudoStack(); - ::Mutex& GetMutex(); - void StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime = 0); - - /** - * Call this method when the JS entries inside the buffer are about to - * become invalid, i.e., just before JS shutdown. - */ - void FlushSamplesAndMarkers(); - - void BeginUnwind(); - virtual void EndUnwind(); - virtual SyncProfile* AsSyncProfile() { return nullptr; } - - bool IsMainThread() const { return mIsMainThread; } - const char* Name() const { return mThreadInfo->Name(); } - int ThreadId() const { return mThreadId; } - - PlatformData* GetPlatformData() const { return mPlatformData; } - void* GetStackTop() const { return mStackTop; } - void DuplicateLastSample(); - - ThreadInfo* GetThreadInfo() const { return mThreadInfo; } -#ifndef SPS_STANDALONE - ThreadResponsiveness* GetThreadResponsiveness() { return &mRespInfo; } -#endif - - bool CanInvokeJS() const { return mThreadInfo->CanInvokeJS(); } - - void SetPendingDelete() - { - mPseudoStack = nullptr; - mPlatformData = nullptr; - } - - uint32_t bufferGeneration() const { - return mBuffer->mGeneration; - } - -protected: - void StreamSamplesAndMarkers(SpliceableJSONWriter& aWriter, double aSinceTime, - UniqueStacks& aUniqueStacks); - -private: - FRIEND_TEST(ThreadProfile, InsertOneTag); - FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer); - FRIEND_TEST(ThreadProfile, InsertTagsNoWrap); - FRIEND_TEST(ThreadProfile, InsertTagsWrap); - FRIEND_TEST(ThreadProfile, MemoryMeasure); - ThreadInfo* mThreadInfo; - - const RefPtr<ProfileBuffer> mBuffer; - - // JS frames in the buffer may require a live JSRuntime to stream (e.g., - // stringifying JIT frames). In the case of JSRuntime destruction, - // FlushSamplesAndMarkers should be called to save them. These are spliced - // into the final stream. - mozilla::UniquePtr<char[]> mSavedStreamedSamples; - mozilla::UniquePtr<char[]> mSavedStreamedMarkers; - mozilla::Maybe<UniqueStacks> mUniqueStacks; - - PseudoStack* mPseudoStack; - mozilla::UniquePtr<Mutex> mMutex; - int mThreadId; - bool mIsMainThread; - PlatformData* mPlatformData; // Platform specific data. - void* const mStackTop; -#ifndef SPS_STANDALONE - ThreadResponsiveness mRespInfo; -#endif - - // Only Linux is using a signal sender, instead of stopping the thread, so we - // need some space to store the data which cannot be collected in the signal - // handler code. -#ifdef XP_LINUX -public: - int64_t mRssMemory; - int64_t mUssMemory; -#endif -}; - -#endif diff --git a/tools/profiler/core/platform-linux.cc b/tools/profiler/core/platform-linux.cc deleted file mode 100644 index 160873c9d..000000000 --- a/tools/profiler/core/platform-linux.cc +++ /dev/null @@ -1,715 +0,0 @@ -// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in -// the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google, Inc. nor the names of its contributors -// may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -// SUCH DAMAGE. - -/* -# vim: sw=2 -*/ -#include <stdio.h> -#include <math.h> - -#include <pthread.h> -#include <semaphore.h> -#include <signal.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/prctl.h> // set name -#include <stdlib.h> -#include <sched.h> -#ifdef ANDROID -#include <android/log.h> -#else -#define __android_log_print(a, ...) -#endif -#include <ucontext.h> -// Ubuntu Dapper requires memory pages to be marked as -// executable. Otherwise, OS raises an exception when executing code -// in that page. -#include <sys/types.h> // mmap & munmap -#include <sys/mman.h> // mmap & munmap -#include <sys/stat.h> // open -#include <fcntl.h> // open -#include <unistd.h> // sysconf -#include <semaphore.h> -#ifdef __GLIBC__ -#include <execinfo.h> // backtrace, backtrace_symbols -#endif // def __GLIBC__ -#include <strings.h> // index -#include <errno.h> -#include <stdarg.h> -#include "prenv.h" -#include "platform.h" -#include "GeckoProfiler.h" -#include "mozilla/Mutex.h" -#include "mozilla/Atomics.h" -#include "mozilla/LinuxSignal.h" -#include "mozilla/TimeStamp.h" -#include "mozilla/DebugOnly.h" -#include "ProfileEntry.h" -#include "nsThreadUtils.h" -#include "GeckoSampler.h" -#include "ThreadResponsiveness.h" - -#if defined(__ARM_EABI__) && defined(ANDROID) - // Should also work on other Android and ARM Linux, but not tested there yet. -# define USE_EHABI_STACKWALK -# include "EHABIStackWalk.h" -#elif defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux) -# define USE_LUL_STACKWALK -# include "lul/LulMain.h" -# include "lul/platform-linux-lul.h" -#endif - -// Memory profile -#include "nsMemoryReporterManager.h" - -#include <string.h> -#include <list> - -#define SIGNAL_SAVE_PROFILE SIGUSR2 - -using namespace mozilla; - -#if defined(USE_LUL_STACKWALK) -// A singleton instance of the library. It is initialised at first -// use. Currently only the main thread can call Sampler::Start, so -// there is no need for a mechanism to ensure that it is only -// created once in a multi-thread-use situation. -lul::LUL* sLUL = nullptr; - -// This is the sLUL initialization routine. -static void sLUL_initialization_routine(void) -{ - MOZ_ASSERT(!sLUL); - MOZ_ASSERT(gettid() == getpid()); /* "this is the main thread" */ - sLUL = new lul::LUL(logging_sink_for_LUL); - // Read all the unwind info currently available. - read_procmaps(sLUL); -} -#endif - -/* static */ Thread::tid_t -Thread::GetCurrentId() -{ - return gettid(); -} - -#if !defined(ANDROID) -// Keep track of when any of our threads calls fork(), so we can -// temporarily disable signal delivery during the fork() call. Not -// doing so appears to cause a kind of race, in which signals keep -// getting delivered to the thread doing fork(), which keeps causing -// it to fail and be restarted; hence forward progress is delayed a -// great deal. A side effect of this is to permanently disable -// sampling in the child process. See bug 837390. - -// Unfortunately this is only doable on non-Android, since Bionic -// doesn't have pthread_atfork. - -// This records the current state at the time we paused it. -static bool was_paused = false; - -// In the parent, just before the fork, record the pausedness state, -// and then pause. -static void paf_prepare(void) { - if (Sampler::GetActiveSampler()) { - was_paused = Sampler::GetActiveSampler()->IsPaused(); - Sampler::GetActiveSampler()->SetPaused(true); - } else { - was_paused = false; - } -} - -// In the parent, just after the fork, return pausedness to the -// pre-fork state. -static void paf_parent(void) { - if (Sampler::GetActiveSampler()) - Sampler::GetActiveSampler()->SetPaused(was_paused); -} - -// Set up the fork handlers. -static void* setup_atfork() { - pthread_atfork(paf_prepare, paf_parent, NULL); - return NULL; -} -#endif /* !defined(ANDROID) */ - -struct SamplerRegistry { - static void AddActiveSampler(Sampler *sampler) { - ASSERT(!SamplerRegistry::sampler); - SamplerRegistry::sampler = sampler; - } - static void RemoveActiveSampler(Sampler *sampler) { - SamplerRegistry::sampler = NULL; - } - static Sampler *sampler; -}; - -Sampler *SamplerRegistry::sampler = NULL; - -static mozilla::Atomic<ThreadProfile*> sCurrentThreadProfile; -static sem_t sSignalHandlingDone; - -static void ProfilerSaveSignalHandler(int signal, siginfo_t* info, void* context) { - Sampler::GetActiveSampler()->RequestSave(); -} - -static void SetSampleContext(TickSample* sample, void* context) -{ - // Extracting the sample from the context is extremely machine dependent. - ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context); - mcontext_t& mcontext = ucontext->uc_mcontext; -#if V8_HOST_ARCH_IA32 - sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_EIP]); - sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_ESP]); - sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_EBP]); -#elif V8_HOST_ARCH_X64 - sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_RIP]); - sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_RSP]); - sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_RBP]); -#elif V8_HOST_ARCH_ARM -// An undefined macro evaluates to 0, so this applies to Android's Bionic also. -#if !defined(ANDROID) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 3)) - sample->pc = reinterpret_cast<Address>(mcontext.gregs[R15]); - sample->sp = reinterpret_cast<Address>(mcontext.gregs[R13]); - sample->fp = reinterpret_cast<Address>(mcontext.gregs[R11]); -#ifdef ENABLE_ARM_LR_SAVING - sample->lr = reinterpret_cast<Address>(mcontext.gregs[R14]); -#endif -#else - sample->pc = reinterpret_cast<Address>(mcontext.arm_pc); - sample->sp = reinterpret_cast<Address>(mcontext.arm_sp); - sample->fp = reinterpret_cast<Address>(mcontext.arm_fp); -#ifdef ENABLE_ARM_LR_SAVING - sample->lr = reinterpret_cast<Address>(mcontext.arm_lr); -#endif -#endif -#elif V8_HOST_ARCH_MIPS - // Implement this on MIPS. - UNIMPLEMENTED(); -#endif -} - -#ifdef ANDROID -#define V8_HOST_ARCH_ARM 1 -#define SYS_gettid __NR_gettid -#define SYS_tgkill __NR_tgkill -#else -#define V8_HOST_ARCH_X64 1 -#endif - -namespace { - -void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) { - // Avoid TSan warning about clobbering errno. - int savedErrno = errno; - - if (!Sampler::GetActiveSampler()) { - sem_post(&sSignalHandlingDone); - errno = savedErrno; - return; - } - - TickSample sample_obj; - TickSample* sample = &sample_obj; - sample->context = context; - - // If profiling, we extract the current pc and sp. - if (Sampler::GetActiveSampler()->IsProfiling()) { - SetSampleContext(sample, context); - } - sample->threadProfile = sCurrentThreadProfile; - sample->timestamp = mozilla::TimeStamp::Now(); - sample->rssMemory = sample->threadProfile->mRssMemory; - sample->ussMemory = sample->threadProfile->mUssMemory; - - Sampler::GetActiveSampler()->Tick(sample); - - sCurrentThreadProfile = NULL; - sem_post(&sSignalHandlingDone); - errno = savedErrno; -} - -} // namespace - -static void ProfilerSignalThread(ThreadProfile *profile, - bool isFirstProfiledThread) -{ - if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) { - profile->mRssMemory = nsMemoryReporterManager::ResidentFast(); - profile->mUssMemory = nsMemoryReporterManager::ResidentUnique(); - } else { - profile->mRssMemory = 0; - profile->mUssMemory = 0; - } -} - -int tgkill(pid_t tgid, pid_t tid, int signalno) { - return syscall(SYS_tgkill, tgid, tid, signalno); -} - -class PlatformData { - public: - PlatformData() - { - MOZ_COUNT_CTOR(PlatformData); - } - - ~PlatformData() - { - MOZ_COUNT_DTOR(PlatformData); - } -}; - -/* static */ PlatformData* -Sampler::AllocPlatformData(int aThreadId) -{ - return new PlatformData; -} - -/* static */ void -Sampler::FreePlatformData(PlatformData* aData) -{ - delete aData; -} - -static void* SignalSender(void* arg) { - // Taken from platform_thread_posix.cc - prctl(PR_SET_NAME, "SamplerThread", 0, 0, 0); - - int vm_tgid_ = getpid(); - DebugOnly<int> my_tid = gettid(); - - unsigned int nSignalsSent = 0; - - TimeDuration lastSleepOverhead = 0; - TimeStamp sampleStart = TimeStamp::Now(); - while (SamplerRegistry::sampler->IsActive()) { - - SamplerRegistry::sampler->HandleSaveRequest(); - SamplerRegistry::sampler->DeleteExpiredMarkers(); - - if (!SamplerRegistry::sampler->IsPaused()) { - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - std::vector<ThreadInfo*> threads = - SamplerRegistry::sampler->GetRegisteredThreads(); - - bool isFirstProfiledThread = true; - for (uint32_t i = 0; i < threads.size(); i++) { - ThreadInfo* info = threads[i]; - - // This will be null if we're not interested in profiling this thread. - if (!info->Profile() || info->IsPendingDelete()) - continue; - - PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); - if (sleeping == PseudoStack::SLEEPING_AGAIN) { - info->Profile()->DuplicateLastSample(); - continue; - } - - info->Profile()->GetThreadResponsiveness()->Update(); - - // We use sCurrentThreadProfile the ThreadProfile for the - // thread we're profiling to the signal handler - sCurrentThreadProfile = info->Profile(); - - int threadId = info->ThreadId(); - MOZ_ASSERT(threadId != my_tid); - - // Profile from the signal sender for information which is not signal - // safe, and will have low variation between the emission of the signal - // and the signal handler catch. - ProfilerSignalThread(sCurrentThreadProfile, isFirstProfiledThread); - - // Profile from the signal handler for information which is signal safe - // and needs to be precise too, such as the stack of the interrupted - // thread. - if (tgkill(vm_tgid_, threadId, SIGPROF) != 0) { - printf_stderr("profiler failed to signal tid=%d\n", threadId); -#ifdef DEBUG - abort(); -#else - continue; -#endif - } - - // Wait for the signal handler to run before moving on to the next one - sem_wait(&sSignalHandlingDone); - isFirstProfiledThread = false; - - // The LUL unwind object accumulates frame statistics. - // Periodically we should poke it to give it a chance to print - // those statistics. This involves doing I/O (fprintf, - // __android_log_print, etc) and so can't safely be done from - // the unwinder threads, which is why it is done here. - if ((++nSignalsSent & 0xF) == 0) { -# if defined(USE_LUL_STACKWALK) - sLUL->MaybeShowStats(); -# endif - } - } - } - - TimeStamp targetSleepEndTime = sampleStart + TimeDuration::FromMicroseconds(SamplerRegistry::sampler->interval() * 1000); - TimeStamp beforeSleep = TimeStamp::Now(); - TimeDuration targetSleepDuration = targetSleepEndTime - beforeSleep; - double sleepTime = std::max(0.0, (targetSleepDuration - lastSleepOverhead).ToMicroseconds()); - OS::SleepMicro(sleepTime); - sampleStart = TimeStamp::Now(); - lastSleepOverhead = sampleStart - (beforeSleep + TimeDuration::FromMicroseconds(sleepTime)); - } - return 0; -} - -Sampler::Sampler(double interval, bool profiling, int entrySize) - : interval_(interval), - profiling_(profiling), - paused_(false), - active_(false), - entrySize_(entrySize) { - MOZ_COUNT_CTOR(Sampler); -} - -Sampler::~Sampler() { - MOZ_COUNT_DTOR(Sampler); - ASSERT(!signal_sender_launched_); -} - - -void Sampler::Start() { - LOG("Sampler started"); - -#if defined(USE_EHABI_STACKWALK) - mozilla::EHABIStackWalkInit(); -#elif defined(USE_LUL_STACKWALK) - // NOTE: this isn't thread-safe. But we expect Sampler::Start to be - // called only from the main thread, so this is OK in general. - if (!sLUL) { - sLUL_initialization_routine(); - } -#endif - - SamplerRegistry::AddActiveSampler(this); - - // Initialize signal handler communication - sCurrentThreadProfile = NULL; - if (sem_init(&sSignalHandlingDone, /* pshared: */ 0, /* value: */ 0) != 0) { - LOG("Error initializing semaphore"); - return; - } - - // Request profiling signals. - LOG("Request signal"); - struct sigaction sa; - sa.sa_sigaction = MOZ_SIGNAL_TRAMPOLINE(ProfilerSignalHandler); - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART | SA_SIGINFO; - if (sigaction(SIGPROF, &sa, &old_sigprof_signal_handler_) != 0) { - LOG("Error installing signal"); - return; - } - - // Request save profile signals - struct sigaction sa2; - sa2.sa_sigaction = ProfilerSaveSignalHandler; - sigemptyset(&sa2.sa_mask); - sa2.sa_flags = SA_RESTART | SA_SIGINFO; - if (sigaction(SIGNAL_SAVE_PROFILE, &sa2, &old_sigsave_signal_handler_) != 0) { - LOG("Error installing start signal"); - return; - } - LOG("Signal installed"); - signal_handler_installed_ = true; - -#if defined(USE_LUL_STACKWALK) - // Switch into unwind mode. After this point, we can't add or - // remove any unwind info to/from this LUL instance. The only thing - // we can do with it is Unwind() calls. - sLUL->EnableUnwinding(); - - // Has a test been requested? - if (PR_GetEnv("MOZ_PROFILER_LUL_TEST")) { - int nTests = 0, nTestsPassed = 0; - RunLulUnitTests(&nTests, &nTestsPassed, sLUL); - } -#endif - - // Start a thread that sends SIGPROF signal to VM thread. - // Sending the signal ourselves instead of relying on itimer provides - // much better accuracy. - SetActive(true); - if (pthread_create( - &signal_sender_thread_, NULL, SignalSender, NULL) == 0) { - signal_sender_launched_ = true; - } - LOG("Profiler thread started"); -} - - -void Sampler::Stop() { - SetActive(false); - - // Wait for signal sender termination (it will exit after setting - // active_ to false). - if (signal_sender_launched_) { - pthread_join(signal_sender_thread_, NULL); - signal_sender_launched_ = false; - } - - SamplerRegistry::RemoveActiveSampler(this); - - // Restore old signal handler - if (signal_handler_installed_) { - sigaction(SIGNAL_SAVE_PROFILE, &old_sigsave_signal_handler_, 0); - sigaction(SIGPROF, &old_sigprof_signal_handler_, 0); - signal_handler_installed_ = false; - } -} - -bool Sampler::RegisterCurrentThread(const char* aName, - PseudoStack* aPseudoStack, - bool aIsMainThread, void* stackTop) -{ - if (!Sampler::sRegisteredThreadsMutex) - return false; - - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - - int id = gettid(); - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - if (info->ThreadId() == id && !info->IsPendingDelete()) { - // Thread already registered. This means the first unregister will be - // too early. - ASSERT(false); - return false; - } - } - - set_tls_stack_top(stackTop); - - ThreadInfo* info = new StackOwningThreadInfo(aName, id, - aIsMainThread, aPseudoStack, stackTop); - - if (sActiveSampler) { - sActiveSampler->RegisterThread(info); - } - - sRegisteredThreads->push_back(info); - - return true; -} - -void Sampler::UnregisterCurrentThread() -{ - if (!Sampler::sRegisteredThreadsMutex) - return; - - tlsStackTop.set(nullptr); - - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - - int id = gettid(); - - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - if (info->ThreadId() == id && !info->IsPendingDelete()) { - if (profiler_is_active()) { - // We still want to show the results of this thread if you - // save the profile shortly after a thread is terminated. - // For now we will defer the delete to profile stop. - info->SetPendingDelete(); - break; - } else { - delete info; - sRegisteredThreads->erase(sRegisteredThreads->begin() + i); - break; - } - } - } -} - -#ifdef ANDROID -static struct sigaction old_sigstart_signal_handler; -const int SIGSTART = SIGUSR2; - -static void freeArray(const char** array, int size) { - for (int i = 0; i < size; i++) { - free((void*) array[i]); - } -} - -static uint32_t readCSVArray(char* csvList, const char** buffer) { - uint32_t count; - char* savePtr; - int newlinePos = strlen(csvList) - 1; - if (csvList[newlinePos] == '\n') { - csvList[newlinePos] = '\0'; - } - - char* item = strtok_r(csvList, ",", &savePtr); - for (count = 0; item; item = strtok_r(NULL, ",", &savePtr)) { - int length = strlen(item) + 1; // Include \0 - char* newBuf = (char*) malloc(sizeof(char) * length); - buffer[count] = newBuf; - strncpy(newBuf, item, length); - count++; - } - - return count; -} - -// Currently support only the env variables -// reported in read_profiler_env -static void ReadProfilerVars(const char* fileName, const char** features, - uint32_t* featureCount, const char** threadNames, uint32_t* threadCount) { - FILE* file = fopen(fileName, "r"); - const int bufferSize = 1024; - char line[bufferSize]; - char* feature; - char* value; - char* savePtr; - - if (file) { - while (fgets(line, bufferSize, file) != NULL) { - feature = strtok_r(line, "=", &savePtr); - value = strtok_r(NULL, "", &savePtr); - - if (strncmp(feature, PROFILER_INTERVAL, bufferSize) == 0) { - set_profiler_interval(value); - } else if (strncmp(feature, PROFILER_ENTRIES, bufferSize) == 0) { - set_profiler_entries(value); - } else if (strncmp(feature, PROFILER_STACK, bufferSize) == 0) { - set_profiler_scan(value); - } else if (strncmp(feature, PROFILER_FEATURES, bufferSize) == 0) { - *featureCount = readCSVArray(value, features); - } else if (strncmp(feature, "threads", bufferSize) == 0) { - *threadCount = readCSVArray(value, threadNames); - } - } - - fclose(file); - } -} - -static void DoStartTask() { - uint32_t featureCount = 0; - uint32_t threadCount = 0; - - // Just allocate 10 features for now - // FIXME: these don't really point to const chars* - // So we free them later, but we don't want to change the const char** - // declaration in profiler_start. Annoying but ok for now. - const char* threadNames[10]; - const char* features[10]; - const char* profilerConfigFile = "/data/local/tmp/profiler.options"; - - ReadProfilerVars(profilerConfigFile, features, &featureCount, threadNames, &threadCount); - MOZ_ASSERT(featureCount < 10); - MOZ_ASSERT(threadCount < 10); - - profiler_start(PROFILE_DEFAULT_ENTRY, 1, - features, featureCount, - threadNames, threadCount); - - freeArray(threadNames, threadCount); - freeArray(features, featureCount); -} - -static void StartSignalHandler(int signal, siginfo_t* info, void* context) { - class StartTask : public Runnable { - public: - NS_IMETHOD Run() override { - DoStartTask(); - return NS_OK; - } - }; - // XXX: technically NS_DispatchToMainThread is NOT async signal safe. We risk - // nasty things like deadlocks, but the probability is very low and we - // typically only do this once so it tends to be ok. See bug 909403. - NS_DispatchToMainThread(new StartTask()); -} - -void OS::Startup() -{ - LOG("Registering start signal"); - struct sigaction sa; - sa.sa_sigaction = StartSignalHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART | SA_SIGINFO; - if (sigaction(SIGSTART, &sa, &old_sigstart_signal_handler) != 0) { - LOG("Error installing signal"); - } -} - -#else - -void OS::Startup() { - // Set up the fork handlers. - setup_atfork(); -} - -#endif - - - -void TickSample::PopulateContext(void* aContext) -{ - MOZ_ASSERT(aContext); - ucontext_t* pContext = reinterpret_cast<ucontext_t*>(aContext); - if (!getcontext(pContext)) { - context = pContext; - SetSampleContext(this, aContext); - } -} - -void OS::SleepMicro(int microseconds) -{ - if (MOZ_UNLIKELY(microseconds >= 1000000)) { - // Use usleep for larger intervals, because the nanosleep - // code below only supports intervals < 1 second. - MOZ_ALWAYS_TRUE(!::usleep(microseconds)); - return; - } - - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = microseconds * 1000UL; - - int rv = ::nanosleep(&ts, &ts); - - while (rv != 0 && errno == EINTR) { - // Keep waiting in case of interrupt. - // nanosleep puts the remaining time back into ts. - rv = ::nanosleep(&ts, &ts); - } - - MOZ_ASSERT(!rv, "nanosleep call failed"); -} diff --git a/tools/profiler/core/platform-macos.cc b/tools/profiler/core/platform-macos.cc deleted file mode 100644 index 9a98d1a26..000000000 --- a/tools/profiler/core/platform-macos.cc +++ /dev/null @@ -1,469 +0,0 @@ -/* 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 <dlfcn.h> -#include <unistd.h> -#include <sys/mman.h> -#include <mach/mach_init.h> -#include <mach-o/dyld.h> -#include <mach-o/getsect.h> - -#include <AvailabilityMacros.h> - -#include <pthread.h> -#include <semaphore.h> -#include <signal.h> -#include <libkern/OSAtomic.h> -#include <mach/mach.h> -#include <mach/semaphore.h> -#include <mach/task.h> -#include <mach/vm_statistics.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <sys/types.h> -#include <sys/sysctl.h> -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <math.h> - -#ifndef SPS_STANDALONE -#include "ThreadResponsiveness.h" -#include "nsThreadUtils.h" - -// Memory profile -#include "nsMemoryReporterManager.h" -#endif - -#include "platform.h" -#include "GeckoSampler.h" -#include "mozilla/TimeStamp.h" - -using mozilla::TimeStamp; -using mozilla::TimeDuration; - -// this port is based off of v8 svn revision 9837 - -// XXX: this is a very stubbed out implementation -// that only supports a single Sampler -struct SamplerRegistry { - static void AddActiveSampler(Sampler *sampler) { - ASSERT(!SamplerRegistry::sampler); - SamplerRegistry::sampler = sampler; - } - static void RemoveActiveSampler(Sampler *sampler) { - SamplerRegistry::sampler = NULL; - } - static Sampler *sampler; -}; - -Sampler *SamplerRegistry::sampler = NULL; - -#ifdef DEBUG -// 0 is never a valid thread id on MacOSX since a pthread_t is a pointer. -static const pthread_t kNoThread = (pthread_t) 0; -#endif - -void OS::Startup() { -} - -void OS::Sleep(int milliseconds) { - usleep(1000 * milliseconds); -} - -void OS::SleepMicro(int microseconds) { - usleep(microseconds); -} - -Thread::Thread(const char* name) - : stack_size_(0) { - set_name(name); -} - - -Thread::~Thread() { -} - - -static void SetThreadName(const char* name) { - // pthread_setname_np is only available in 10.6 or later, so test - // for it at runtime. - int (*dynamic_pthread_setname_np)(const char*); - *reinterpret_cast<void**>(&dynamic_pthread_setname_np) = - dlsym(RTLD_DEFAULT, "pthread_setname_np"); - if (!dynamic_pthread_setname_np) - return; - - // Mac OS X does not expose the length limit of the name, so hardcode it. - static const int kMaxNameLength = 63; - USE(kMaxNameLength); - ASSERT(Thread::kMaxThreadNameLength <= kMaxNameLength); - dynamic_pthread_setname_np(name); -} - - -static void* ThreadEntry(void* arg) { - Thread* thread = reinterpret_cast<Thread*>(arg); - - thread->thread_ = pthread_self(); - SetThreadName(thread->name()); - ASSERT(thread->thread_ != kNoThread); - thread->Run(); - return NULL; -} - - -void Thread::set_name(const char* name) { - strncpy(name_, name, sizeof(name_)); - name_[sizeof(name_) - 1] = '\0'; -} - - -void Thread::Start() { - pthread_attr_t* attr_ptr = NULL; - pthread_attr_t attr; - if (stack_size_ > 0) { - pthread_attr_init(&attr); - pthread_attr_setstacksize(&attr, static_cast<size_t>(stack_size_)); - attr_ptr = &attr; - } - pthread_create(&thread_, attr_ptr, ThreadEntry, this); - ASSERT(thread_ != kNoThread); -} - -void Thread::Join() { - pthread_join(thread_, NULL); -} - -class PlatformData { - public: - PlatformData() : profiled_thread_(mach_thread_self()) - { - profiled_pthread_ = pthread_from_mach_thread_np(profiled_thread_); - } - - ~PlatformData() { - // Deallocate Mach port for thread. - mach_port_deallocate(mach_task_self(), profiled_thread_); - } - - thread_act_t profiled_thread() { return profiled_thread_; } - pthread_t profiled_pthread() { return profiled_pthread_; } - - private: - // Note: for profiled_thread_ Mach primitives are used instead of PThread's - // because the latter doesn't provide thread manipulation primitives required. - // For details, consult "Mac OS X Internals" book, Section 7.3. - thread_act_t profiled_thread_; - // we also store the pthread because Mach threads have no concept of stack - // and we want to be able to get the stack size when we need to unwind the - // stack using frame pointers. - pthread_t profiled_pthread_; -}; - -/* static */ PlatformData* -Sampler::AllocPlatformData(int aThreadId) -{ - return new PlatformData; -} - -/* static */ void -Sampler::FreePlatformData(PlatformData* aData) -{ - delete aData; -} - -class SamplerThread : public Thread { - public: - explicit SamplerThread(double interval) - : Thread("SamplerThread") - , intervalMicro_(floor(interval * 1000 + 0.5)) - { - if (intervalMicro_ <= 0) { - intervalMicro_ = 1; - } - } - - static void AddActiveSampler(Sampler* sampler) { - SamplerRegistry::AddActiveSampler(sampler); - if (instance_ == NULL) { - instance_ = new SamplerThread(sampler->interval()); - instance_->Start(); - } - } - - static void RemoveActiveSampler(Sampler* sampler) { - instance_->Join(); - //XXX: unlike v8 we need to remove the active sampler after doing the Join - // because we drop the sampler immediately - SamplerRegistry::RemoveActiveSampler(sampler); - delete instance_; - instance_ = NULL; - } - - // Implement Thread::Run(). - virtual void Run() { - TimeDuration lastSleepOverhead = 0; - TimeStamp sampleStart = TimeStamp::Now(); - while (SamplerRegistry::sampler->IsActive()) { - SamplerRegistry::sampler->DeleteExpiredMarkers(); - if (!SamplerRegistry::sampler->IsPaused()) { - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - std::vector<ThreadInfo*> threads = - SamplerRegistry::sampler->GetRegisteredThreads(); - bool isFirstProfiledThread = true; - for (uint32_t i = 0; i < threads.size(); i++) { - ThreadInfo* info = threads[i]; - - // This will be null if we're not interested in profiling this thread. - if (!info->Profile() || info->IsPendingDelete()) - continue; - - PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); - if (sleeping == PseudoStack::SLEEPING_AGAIN) { - info->Profile()->DuplicateLastSample(); - continue; - } - -#ifndef SPS_STANDALONE - info->Profile()->GetThreadResponsiveness()->Update(); -#endif - - ThreadProfile* thread_profile = info->Profile(); - - SampleContext(SamplerRegistry::sampler, thread_profile, - isFirstProfiledThread); - isFirstProfiledThread = false; - } - } - - TimeStamp targetSleepEndTime = sampleStart + TimeDuration::FromMicroseconds(intervalMicro_); - TimeStamp beforeSleep = TimeStamp::Now(); - TimeDuration targetSleepDuration = targetSleepEndTime - beforeSleep; - double sleepTime = std::max(0.0, (targetSleepDuration - lastSleepOverhead).ToMicroseconds()); - OS::SleepMicro(sleepTime); - sampleStart = TimeStamp::Now(); - lastSleepOverhead = sampleStart - (beforeSleep + TimeDuration::FromMicroseconds(sleepTime)); - } - } - - void SampleContext(Sampler* sampler, ThreadProfile* thread_profile, - bool isFirstProfiledThread) - { - thread_act_t profiled_thread = - thread_profile->GetPlatformData()->profiled_thread(); - - TickSample sample_obj; - TickSample* sample = &sample_obj; - - // Unique Set Size is not supported on Mac. - sample->ussMemory = 0; - sample->rssMemory = 0; - -#ifndef SPS_STANDALONE - if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) { - sample->rssMemory = nsMemoryReporterManager::ResidentFast(); - } -#endif - - // We're using thread_suspend on OS X because pthread_kill (which is what - // we're using on Linux) has less consistent performance and causes - // strange crashes, see bug 1166778 and bug 1166808. - - if (KERN_SUCCESS != thread_suspend(profiled_thread)) return; - -#if V8_HOST_ARCH_X64 - thread_state_flavor_t flavor = x86_THREAD_STATE64; - x86_thread_state64_t state; - mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; -#if __DARWIN_UNIX03 -#define REGISTER_FIELD(name) __r ## name -#else -#define REGISTER_FIELD(name) r ## name -#endif // __DARWIN_UNIX03 -#elif V8_HOST_ARCH_IA32 - thread_state_flavor_t flavor = i386_THREAD_STATE; - i386_thread_state_t state; - mach_msg_type_number_t count = i386_THREAD_STATE_COUNT; -#if __DARWIN_UNIX03 -#define REGISTER_FIELD(name) __e ## name -#else -#define REGISTER_FIELD(name) e ## name -#endif // __DARWIN_UNIX03 -#else -#error Unsupported Mac OS X host architecture. -#endif // V8_HOST_ARCH - - if (thread_get_state(profiled_thread, - flavor, - reinterpret_cast<natural_t*>(&state), - &count) == KERN_SUCCESS) { - sample->pc = reinterpret_cast<Address>(state.REGISTER_FIELD(ip)); - sample->sp = reinterpret_cast<Address>(state.REGISTER_FIELD(sp)); - sample->fp = reinterpret_cast<Address>(state.REGISTER_FIELD(bp)); - sample->timestamp = mozilla::TimeStamp::Now(); - sample->threadProfile = thread_profile; - - sampler->Tick(sample); - } - thread_resume(profiled_thread); - } - - int intervalMicro_; - //RuntimeProfilerRateLimiter rate_limiter_; - - static SamplerThread* instance_; - - DISALLOW_COPY_AND_ASSIGN(SamplerThread); -}; - -#undef REGISTER_FIELD - -SamplerThread* SamplerThread::instance_ = NULL; - -Sampler::Sampler(double interval, bool profiling, int entrySize) - : // isolate_(isolate), - interval_(interval), - profiling_(profiling), - paused_(false), - active_(false), - entrySize_(entrySize) /*, - samples_taken_(0)*/ { -} - - -Sampler::~Sampler() { - ASSERT(!IsActive()); -} - - -void Sampler::Start() { - ASSERT(!IsActive()); - SetActive(true); - SamplerThread::AddActiveSampler(this); -} - - -void Sampler::Stop() { - ASSERT(IsActive()); - SetActive(false); - SamplerThread::RemoveActiveSampler(this); -} - -pthread_t -Sampler::GetProfiledThread(PlatformData* aData) -{ - return aData->profiled_pthread(); -} - -#include <sys/syscall.h> -pid_t gettid() -{ - return (pid_t) syscall(SYS_thread_selfid); -} - -/* static */ Thread::tid_t -Thread::GetCurrentId() -{ - return gettid(); -} - -bool Sampler::RegisterCurrentThread(const char* aName, - PseudoStack* aPseudoStack, - bool aIsMainThread, void* stackTop) -{ - if (!Sampler::sRegisteredThreadsMutex) - return false; - - - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - - int id = gettid(); - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - if (info->ThreadId() == id && !info->IsPendingDelete()) { - // Thread already registered. This means the first unregister will be - // too early. - ASSERT(false); - return false; - } - } - - set_tls_stack_top(stackTop); - - ThreadInfo* info = new StackOwningThreadInfo(aName, id, - aIsMainThread, aPseudoStack, stackTop); - - if (sActiveSampler) { - sActiveSampler->RegisterThread(info); - } - - sRegisteredThreads->push_back(info); - - return true; -} - -void Sampler::UnregisterCurrentThread() -{ - if (!Sampler::sRegisteredThreadsMutex) - return; - - tlsStackTop.set(nullptr); - - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - - int id = gettid(); - - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - if (info->ThreadId() == id && !info->IsPendingDelete()) { - if (profiler_is_active()) { - // We still want to show the results of this thread if you - // save the profile shortly after a thread is terminated. - // For now we will defer the delete to profile stop. - info->SetPendingDelete(); - break; - } else { - delete info; - sRegisteredThreads->erase(sRegisteredThreads->begin() + i); - break; - } - } - } -} - -void TickSample::PopulateContext(void* aContext) -{ - // Note that this asm changes if PopulateContext's parameter list is altered -#if defined(SPS_PLAT_amd64_darwin) - asm ( - // Compute caller's %rsp by adding to %rbp: - // 8 bytes for previous %rbp, 8 bytes for return address - "leaq 0x10(%%rbp), %0\n\t" - // Dereference %rbp to get previous %rbp - "movq (%%rbp), %1\n\t" - : - "=r"(sp), - "=r"(fp) - ); -#elif defined(SPS_PLAT_x86_darwin) - asm ( - // Compute caller's %esp by adding to %ebp: - // 4 bytes for aContext + 4 bytes for return address + - // 4 bytes for previous %ebp - "leal 0xc(%%ebp), %0\n\t" - // Dereference %ebp to get previous %ebp - "movl (%%ebp), %1\n\t" - : - "=r"(sp), - "=r"(fp) - ); -#else -# error "Unsupported architecture" -#endif - pc = reinterpret_cast<Address>(__builtin_extract_return_addr( - __builtin_return_address(0))); -} - diff --git a/tools/profiler/core/platform-win32.cc b/tools/profiler/core/platform-win32.cc deleted file mode 100644 index 74b311f28..000000000 --- a/tools/profiler/core/platform-win32.cc +++ /dev/null @@ -1,431 +0,0 @@ -// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in -// the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google, Inc. nor the names of its contributors -// may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -// SUCH DAMAGE. - -#include <windows.h> -#include <mmsystem.h> -#include <process.h> -#include "platform.h" -#include "GeckoSampler.h" -#include "ThreadResponsiveness.h" -#include "ProfileEntry.h" - -// Memory profile -#include "nsMemoryReporterManager.h" - -#include "mozilla/StackWalk_windows.h" - - -class PlatformData { - public: - // Get a handle to the calling thread. This is the thread that we are - // going to profile. We need to make a copy of the handle because we are - // going to use it in the sampler thread. Using GetThreadHandle() will - // not work in this case. We're using OpenThread because DuplicateHandle - // for some reason doesn't work in Chrome's sandbox. - PlatformData(int aThreadId) : profiled_thread_(OpenThread(THREAD_GET_CONTEXT | - THREAD_SUSPEND_RESUME | - THREAD_QUERY_INFORMATION, - false, - aThreadId)) {} - - ~PlatformData() { - if (profiled_thread_ != NULL) { - CloseHandle(profiled_thread_); - profiled_thread_ = NULL; - } - } - - HANDLE profiled_thread() { return profiled_thread_; } - - private: - HANDLE profiled_thread_; -}; - -/* static */ PlatformData* -Sampler::AllocPlatformData(int aThreadId) -{ - return new PlatformData(aThreadId); -} - -/* static */ void -Sampler::FreePlatformData(PlatformData* aData) -{ - delete aData; -} - -uintptr_t -Sampler::GetThreadHandle(PlatformData* aData) -{ - return (uintptr_t) aData->profiled_thread(); -} - -class SamplerThread : public Thread { - public: - SamplerThread(double interval, Sampler* sampler) - : Thread("SamplerThread") - , sampler_(sampler) - , interval_(interval) - { - interval_ = floor(interval + 0.5); - if (interval_ <= 0) { - interval_ = 1; - } - } - - static void StartSampler(Sampler* sampler) { - if (instance_ == NULL) { - instance_ = new SamplerThread(sampler->interval(), sampler); - instance_->Start(); - } else { - ASSERT(instance_->interval_ == sampler->interval()); - } - } - - static void StopSampler() { - instance_->Join(); - delete instance_; - instance_ = NULL; - } - - // Implement Thread::Run(). - virtual void Run() { - - // By default we'll not adjust the timer resolution which tends to be around - // 16ms. However, if the requested interval is sufficiently low we'll try to - // adjust the resolution to match. - if (interval_ < 10) - ::timeBeginPeriod(interval_); - - while (sampler_->IsActive()) { - sampler_->DeleteExpiredMarkers(); - - if (!sampler_->IsPaused()) { - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - std::vector<ThreadInfo*> threads = - sampler_->GetRegisteredThreads(); - bool isFirstProfiledThread = true; - for (uint32_t i = 0; i < threads.size(); i++) { - ThreadInfo* info = threads[i]; - - // This will be null if we're not interested in profiling this thread. - if (!info->Profile() || info->IsPendingDelete()) - continue; - - PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); - if (sleeping == PseudoStack::SLEEPING_AGAIN) { - info->Profile()->DuplicateLastSample(); - continue; - } - - info->Profile()->GetThreadResponsiveness()->Update(); - - ThreadProfile* thread_profile = info->Profile(); - - SampleContext(sampler_, thread_profile, isFirstProfiledThread); - isFirstProfiledThread = false; - } - } - OS::Sleep(interval_); - } - - // disable any timer resolution changes we've made - if (interval_ < 10) - ::timeEndPeriod(interval_); - } - - void SampleContext(Sampler* sampler, ThreadProfile* thread_profile, - bool isFirstProfiledThread) - { - uintptr_t thread = Sampler::GetThreadHandle( - thread_profile->GetPlatformData()); - HANDLE profiled_thread = reinterpret_cast<HANDLE>(thread); - if (profiled_thread == NULL) - return; - - // Context used for sampling the register state of the profiled thread. - CONTEXT context; - memset(&context, 0, sizeof(context)); - - TickSample sample_obj; - TickSample* sample = &sample_obj; - - // Grab the timestamp before pausing the thread, to avoid deadlocks. - sample->timestamp = mozilla::TimeStamp::Now(); - sample->threadProfile = thread_profile; - - if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) { - sample->rssMemory = nsMemoryReporterManager::ResidentFast(); - } else { - sample->rssMemory = 0; - } - - // Unique Set Size is not supported on Windows. - sample->ussMemory = 0; - - static const DWORD kSuspendFailed = static_cast<DWORD>(-1); - if (SuspendThread(profiled_thread) == kSuspendFailed) - return; - - // SuspendThread is asynchronous, so the thread may still be running. - // Call GetThreadContext first to ensure the thread is really suspended. - // See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743. - - // Using only CONTEXT_CONTROL is faster but on 64-bit it causes crashes in - // RtlVirtualUnwind (see bug 1120126) so we set all the flags. -#if V8_HOST_ARCH_X64 - context.ContextFlags = CONTEXT_FULL; -#else - context.ContextFlags = CONTEXT_CONTROL; -#endif - if (!GetThreadContext(profiled_thread, &context)) { - ResumeThread(profiled_thread); - return; - } - - // Threads that may invoke JS require extra attention. Since, on windows, - // the jits also need to modify the same dynamic function table that we need - // to get a stack trace, we have to be wary of that to avoid deadlock. - // - // When embedded in Gecko, for threads that aren't the main thread, - // CanInvokeJS consults an unlocked value in the nsIThread, so we must - // consult this after suspending the profiled thread to avoid racing - // against a value change. - if (thread_profile->CanInvokeJS()) { - if (!TryAcquireStackWalkWorkaroundLock()) { - ResumeThread(profiled_thread); - return; - } - - // It is safe to immediately drop the lock. We only need to contend with - // the case in which the profiled thread held needed system resources. - // If the profiled thread had held those resources, the trylock would have - // failed. Anyone else who grabs those resources will continue to make - // progress, since those threads are not suspended. Because of this, - // we cannot deadlock with them, and should let them run as they please. - ReleaseStackWalkWorkaroundLock(); - } - -#if V8_HOST_ARCH_X64 - sample->pc = reinterpret_cast<Address>(context.Rip); - sample->sp = reinterpret_cast<Address>(context.Rsp); - sample->fp = reinterpret_cast<Address>(context.Rbp); -#else - sample->pc = reinterpret_cast<Address>(context.Eip); - sample->sp = reinterpret_cast<Address>(context.Esp); - sample->fp = reinterpret_cast<Address>(context.Ebp); -#endif - - sample->context = &context; - sampler->Tick(sample); - - ResumeThread(profiled_thread); - } - - Sampler* sampler_; - int interval_; // units: ms - - // Protects the process wide state below. - static SamplerThread* instance_; - - DISALLOW_COPY_AND_ASSIGN(SamplerThread); -}; - -SamplerThread* SamplerThread::instance_ = NULL; - - -Sampler::Sampler(double interval, bool profiling, int entrySize) - : interval_(interval), - profiling_(profiling), - paused_(false), - active_(false), - entrySize_(entrySize) { -} - -Sampler::~Sampler() { - ASSERT(!IsActive()); -} - -void Sampler::Start() { - ASSERT(!IsActive()); - SetActive(true); - SamplerThread::StartSampler(this); -} - -void Sampler::Stop() { - ASSERT(IsActive()); - SetActive(false); - SamplerThread::StopSampler(); -} - - -static const HANDLE kNoThread = INVALID_HANDLE_VALUE; - -static unsigned int __stdcall ThreadEntry(void* arg) { - Thread* thread = reinterpret_cast<Thread*>(arg); - thread->Run(); - return 0; -} - -// Initialize a Win32 thread object. The thread has an invalid thread -// handle until it is started. -Thread::Thread(const char* name) - : stack_size_(0) { - thread_ = kNoThread; - set_name(name); -} - -void Thread::set_name(const char* name) { - strncpy(name_, name, sizeof(name_)); - name_[sizeof(name_) - 1] = '\0'; -} - -// Close our own handle for the thread. -Thread::~Thread() { - if (thread_ != kNoThread) CloseHandle(thread_); -} - -// Create a new thread. It is important to use _beginthreadex() instead of -// the Win32 function CreateThread(), because the CreateThread() does not -// initialize thread specific structures in the C runtime library. -void Thread::Start() { - thread_ = reinterpret_cast<HANDLE>( - _beginthreadex(NULL, - static_cast<unsigned>(stack_size_), - ThreadEntry, - this, - 0, - (unsigned int*) &thread_id_)); -} - -// Wait for thread to terminate. -void Thread::Join() { - if (thread_id_ != GetCurrentId()) { - WaitForSingleObject(thread_, INFINITE); - } -} - -/* static */ Thread::tid_t -Thread::GetCurrentId() -{ - return GetCurrentThreadId(); -} - -void OS::Startup() { -} - -void OS::Sleep(int milliseconds) { - ::Sleep(milliseconds); -} - -bool Sampler::RegisterCurrentThread(const char* aName, - PseudoStack* aPseudoStack, - bool aIsMainThread, void* stackTop) -{ - if (!Sampler::sRegisteredThreadsMutex) - return false; - - - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - - int id = GetCurrentThreadId(); - - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - if (info->ThreadId() == id && !info->IsPendingDelete()) { - // Thread already registered. This means the first unregister will be - // too early. - ASSERT(false); - return false; - } - } - - set_tls_stack_top(stackTop); - - ThreadInfo* info = new StackOwningThreadInfo(aName, id, - aIsMainThread, aPseudoStack, stackTop); - - if (sActiveSampler) { - sActiveSampler->RegisterThread(info); - } - - sRegisteredThreads->push_back(info); - - return true; -} - -void Sampler::UnregisterCurrentThread() -{ - if (!Sampler::sRegisteredThreadsMutex) - return; - - tlsStackTop.set(nullptr); - - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - - int id = GetCurrentThreadId(); - - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - if (info->ThreadId() == id && !info->IsPendingDelete()) { - if (profiler_is_active()) { - // We still want to show the results of this thread if you - // save the profile shortly after a thread is terminated. - // For now we will defer the delete to profile stop. - info->SetPendingDelete(); - break; - } else { - delete info; - sRegisteredThreads->erase(sRegisteredThreads->begin() + i); - break; - } - } - } -} - -void TickSample::PopulateContext(void* aContext) -{ - MOZ_ASSERT(aContext); - CONTEXT* pContext = reinterpret_cast<CONTEXT*>(aContext); - context = pContext; - RtlCaptureContext(pContext); - -#if defined(SPS_PLAT_amd64_windows) - - pc = reinterpret_cast<Address>(pContext->Rip); - sp = reinterpret_cast<Address>(pContext->Rsp); - fp = reinterpret_cast<Address>(pContext->Rbp); - -#elif defined(SPS_PLAT_x86_windows) - - pc = reinterpret_cast<Address>(pContext->Eip); - sp = reinterpret_cast<Address>(pContext->Esp); - fp = reinterpret_cast<Address>(pContext->Ebp); - -#endif -} - diff --git a/tools/profiler/core/platform.cpp b/tools/profiler/core/platform.cpp deleted file mode 100644 index a8a09d66f..000000000 --- a/tools/profiler/core/platform.cpp +++ /dev/null @@ -1,1257 +0,0 @@ -/* 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 <ostream> -#include <fstream> -#include <sstream> -#include <errno.h> - -#include "platform.h" -#include "PlatformMacros.h" -#include "mozilla/ArrayUtils.h" -#include "mozilla/UniquePtr.h" -#include "GeckoProfiler.h" -#ifndef SPS_STANDALONE -#include "ProfilerIOInterposeObserver.h" -#include "mozilla/StaticPtr.h" -#endif -#include "mozilla/ThreadLocal.h" -#include "mozilla/TimeStamp.h" -#include "mozilla/Sprintf.h" -#include "PseudoStack.h" -#include "GeckoSampler.h" -#ifndef SPS_STANDALONE -#include "nsIObserverService.h" -#include "nsDirectoryServiceUtils.h" -#include "nsDirectoryServiceDefs.h" -#include "nsXULAppAPI.h" -#include "nsProfilerStartParams.h" -#include "mozilla/Services.h" -#include "nsThreadUtils.h" -#endif -#include "ProfilerMarkers.h" - -#ifdef MOZ_TASK_TRACER -#include "GeckoTaskTracer.h" -#endif - -#if defined(SPS_OS_android) - #include "FennecJNIWrappers.h" -#endif - -#if defined(SPS_OS_android) -#include "FennecJNINatives.h" -#endif - -#ifndef SPS_STANDALONE -#if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux) -# define USE_LUL_STACKWALK -# include "lul/LulMain.h" -# include "lul/platform-linux-lul.h" -#endif -#endif - -#if defined(SPS_OS_android) -class GeckoJavaSampler : public java::GeckoJavaSampler::Natives<GeckoJavaSampler> -{ -private: - GeckoJavaSampler(); - -public: - static double GetProfilerTime() { - if (!profiler_is_active()) { - return 0.0; - } - return profiler_time(); - }; -}; -#endif - -MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack; -MOZ_THREAD_LOCAL(GeckoSampler *) tlsTicker; -MOZ_THREAD_LOCAL(void *) tlsStackTop; -// 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; - -mozilla::TimeStamp sLastTracerEvent; // is raced on -mozilla::TimeStamp sStartTime; -int sFrameNumber = 0; -int sLastFrameNumber = 0; -int sInitCount = 0; // Each init must have a matched shutdown. -static bool sIsProfiling = false; // is raced on -static bool sIsGPUProfiling = false; // is raced on -static bool sIsLayersDump = false; // is raced on -static bool sIsDisplayListDump = false; // is raced on -static bool sIsRestyleProfiling = false; // is raced on - -// Environment variables to control the profiler -const char* PROFILER_HELP = "MOZ_PROFILER_HELP"; -const char* PROFILER_INTERVAL = "MOZ_PROFILER_INTERVAL"; -const char* PROFILER_ENTRIES = "MOZ_PROFILER_ENTRIES"; -const char* PROFILER_STACK = "MOZ_PROFILER_STACK_SCAN"; -const char* PROFILER_FEATURES = "MOZ_PROFILING_FEATURES"; - -/* 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 */ - -// Values harvested from env vars, that control the profiler. -static int sUnwindInterval; /* in milliseconds */ -static int sUnwindStackScan; /* max # of dubious frames allowed */ -static int sProfileEntries; /* how many entries do we store? */ - -std::vector<ThreadInfo*>* Sampler::sRegisteredThreads = nullptr; -mozilla::UniquePtr< ::Mutex> Sampler::sRegisteredThreadsMutex; - -GeckoSampler* Sampler::sActiveSampler; - -#ifndef SPS_STANDALONE -static mozilla::StaticAutoPtr<mozilla::ProfilerIOInterposeObserver> - sInterposeObserver; -#endif - -// The name that identifies the gecko thread for calls to -// profiler_register_thread. -static const char * gGeckoThreadName = "GeckoMain"; - -void Sampler::Startup() { - sRegisteredThreads = new std::vector<ThreadInfo*>(); - sRegisteredThreadsMutex = OS::CreateMutex("sRegisteredThreads mutex"); - - // We could create the sLUL object and read unwind info into it at - // this point. That would match the lifetime implied by destruction - // of it in Sampler::Shutdown just below. However, that gives a big - // delay on startup, even if no profiling is actually to be done. - // So, instead, sLUL is created on demand at the first call to - // Sampler::Start. -} - -void Sampler::Shutdown() { - while (sRegisteredThreads->size() > 0) { - delete sRegisteredThreads->back(); - sRegisteredThreads->pop_back(); - } - - sRegisteredThreadsMutex = nullptr; - delete sRegisteredThreads; - - // UnregisterThread can be called after shutdown in XPCShell. Thus - // we need to point to null to ignore such a call after shutdown. - sRegisteredThreadsMutex = nullptr; - sRegisteredThreads = nullptr; - -#if defined(USE_LUL_STACKWALK) - // Delete the sLUL object, if it actually got created. - if (sLUL) { - delete sLUL; - sLUL = nullptr; - } -#endif -} - -StackOwningThreadInfo::StackOwningThreadInfo(const char* aName, int aThreadId, - bool aIsMainThread, - PseudoStack* aPseudoStack, - void* aStackTop) - : ThreadInfo(aName, aThreadId, aIsMainThread, aPseudoStack, aStackTop) -{ - aPseudoStack->ref(); -} - -StackOwningThreadInfo::~StackOwningThreadInfo() -{ - PseudoStack* stack = Stack(); - if (stack) { - stack->deref(); - } -} - -void -StackOwningThreadInfo::SetPendingDelete() -{ - PseudoStack* stack = Stack(); - if (stack) { - stack->deref(); - } - ThreadInfo::SetPendingDelete(); -} - -ProfilerMarker::ProfilerMarker(const char* aMarkerName, - ProfilerMarkerPayload* aPayload, - double aTime) - : mMarkerName(strdup(aMarkerName)) - , mPayload(aPayload) - , mTime(aTime) -{ -} - -ProfilerMarker::~ProfilerMarker() { - free(mMarkerName); - delete mPayload; -} - -void -ProfilerMarker::SetGeneration(uint32_t aGenID) { - mGenID = aGenID; -} - -double -ProfilerMarker::GetTime() const { - return mTime; -} - -void ProfilerMarker::StreamJSON(SpliceableJSONWriter& aWriter, - UniqueStacks& aUniqueStacks) const -{ - // Schema: - // [name, time, data] - - aWriter.StartArrayElement(); - { - aUniqueStacks.mUniqueStrings.WriteElement(aWriter, GetMarkerName()); - aWriter.DoubleElement(mTime); - // TODO: Store the callsite for this marker if available: - // if have location data - // b.NameValue(marker, "location", ...); - if (mPayload) { - aWriter.StartObjectElement(); - { - mPayload->StreamPayload(aWriter, aUniqueStacks); - } - aWriter.EndObject(); - } - } - aWriter.EndArray(); -} - -/* Has MOZ_PROFILER_VERBOSE been set? */ - -// Verbosity control for the profiler. The aim is to check env var -// MOZ_PROFILER_VERBOSE only once. However, we may need to temporarily -// override that so as to print the profiler's help message. That's -// what moz_profiler_set_verbosity is for. - -enum class ProfilerVerbosity : int8_t { UNCHECKED, NOTVERBOSE, VERBOSE }; - -// Raced on, potentially -static ProfilerVerbosity profiler_verbosity = ProfilerVerbosity::UNCHECKED; - -bool moz_profiler_verbose() -{ - if (profiler_verbosity == ProfilerVerbosity::UNCHECKED) { - if (getenv("MOZ_PROFILER_VERBOSE") != nullptr) - profiler_verbosity = ProfilerVerbosity::VERBOSE; - else - profiler_verbosity = ProfilerVerbosity::NOTVERBOSE; - } - - return profiler_verbosity == ProfilerVerbosity::VERBOSE; -} - -void moz_profiler_set_verbosity(ProfilerVerbosity pv) -{ - MOZ_ASSERT(pv == ProfilerVerbosity::UNCHECKED || - pv == ProfilerVerbosity::VERBOSE); - profiler_verbosity = pv; -} - - -bool set_profiler_interval(const char* interval) { - if (interval) { - errno = 0; - long int n = strtol(interval, (char**)nullptr, 10); - if (errno == 0 && n >= 1 && n <= 1000) { - sUnwindInterval = n; - return true; - } - return false; - } - - return true; -} - -bool set_profiler_entries(const char* entries) { - if (entries) { - errno = 0; - long int n = strtol(entries, (char**)nullptr, 10); - if (errno == 0 && n > 0) { - sProfileEntries = n; - return true; - } - return false; - } - - return true; -} - -bool set_profiler_scan(const char* scanCount) { - if (scanCount) { - errno = 0; - long int n = strtol(scanCount, (char**)nullptr, 10); - if (errno == 0 && n >= 0 && n <= 100) { - sUnwindStackScan = n; - return true; - } - return false; - } - - return true; -} - -bool is_native_unwinding_avail() { -# if defined(HAVE_NATIVE_UNWIND) - return true; -#else - return false; -#endif -} - -// Read env vars at startup, so as to set: -// sUnwindInterval, sProfileEntries, sUnwindStackScan. -void read_profiler_env_vars() -{ - /* Set defaults */ - sUnwindInterval = 0; /* We'll have to look elsewhere */ - sProfileEntries = 0; - - const char* interval = getenv(PROFILER_INTERVAL); - const char* entries = getenv(PROFILER_ENTRIES); - const char* scanCount = getenv(PROFILER_STACK); - - if (getenv(PROFILER_HELP)) { - // Enable verbose output - moz_profiler_set_verbosity(ProfilerVerbosity::VERBOSE); - profiler_usage(); - // Now force the next enquiry of moz_profiler_verbose to re-query - // env var MOZ_PROFILER_VERBOSE. - moz_profiler_set_verbosity(ProfilerVerbosity::UNCHECKED); - } - - if (!set_profiler_interval(interval) || - !set_profiler_entries(entries) || - !set_profiler_scan(scanCount)) { - profiler_usage(); - } else { - LOG( "SPS:"); - LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")", - (int)sUnwindInterval); - LOGF("SPS: Entry store size = %d (zero means \"platform default\")", - (int)sProfileEntries); - LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).", - (int)sUnwindStackScan); - LOG( "SPS:"); - } -} - -void profiler_usage() { - LOG( "SPS: "); - LOG( "SPS: Environment variable usage:"); - LOG( "SPS: "); - LOG( "SPS: MOZ_PROFILER_HELP"); - LOG( "SPS: If set to any value, prints this message."); - LOG( "SPS: "); - LOG( "SPS: MOZ_PROFILER_INTERVAL=<number> (milliseconds, 1 to 1000)"); - LOG( "SPS: If unset, platform default is used."); - LOG( "SPS: "); - LOG( "SPS: MOZ_PROFILER_ENTRIES=<number> (count, minimum of 1)"); - LOG( "SPS: If unset, platform default is used."); - LOG( "SPS: "); - LOG( "SPS: MOZ_PROFILER_VERBOSE"); - LOG( "SPS: If set to any value, increases verbosity (recommended)."); - LOG( "SPS: "); - LOG( "SPS: MOZ_PROFILER_STACK_SCAN=<number> (default is zero)"); - LOG( "SPS: The number of dubious (stack-scanned) frames allowed"); - LOG( "SPS: "); - LOG( "SPS: MOZ_PROFILER_LUL_TEST"); - LOG( "SPS: If set to any value, runs LUL unit tests at startup of"); - LOG( "SPS: the unwinder thread, and prints a short summary of results."); - LOG( "SPS: "); - LOGF("SPS: This platform %s native unwinding.", - is_native_unwinding_avail() ? "supports" : "does not support"); - LOG( "SPS: "); - - /* Re-set defaults */ - sUnwindInterval = 0; /* We'll have to look elsewhere */ - sProfileEntries = 0; - sUnwindStackScan = 0; - - LOG( "SPS:"); - LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")", - (int)sUnwindInterval); - LOGF("SPS: Entry store size = %d (zero means \"platform default\")", - (int)sProfileEntries); - LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).", - (int)sUnwindStackScan); - LOG( "SPS:"); - - return; -} - -void set_tls_stack_top(void* stackTop) -{ - // Round |stackTop| up to the end of the containing page. We may - // as well do this -- there's no danger of a fault, and we might - // get a few more base-of-the-stack frames as a result. This - // assumes that no target has a page size smaller than 4096. - uintptr_t stackTopR = (uintptr_t)stackTop; - if (stackTop) { - stackTopR = (stackTopR & ~(uintptr_t)4095) + (uintptr_t)4095; - } - tlsStackTop.set((void*)stackTopR); -} - -bool is_main_thread_name(const char* aName) { - if (!aName) { - return false; - } - return strcmp(aName, gGeckoThreadName) == 0; -} - -#ifndef SPS_STANDALONE -#ifdef HAVE_VA_COPY -#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar) -#elif defined(HAVE_VA_LIST_AS_ARRAY) -#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] -#else -#define VARARGS_ASSIGN(foo, bar) (foo) = (bar) -#endif - -void -mozilla_sampler_log(const char *fmt, va_list args) -{ - if (profiler_is_active()) { - // nsAutoCString AppendPrintf would be nicer but - // this is mozilla external code - char buf[2048]; - va_list argsCpy; - VARARGS_ASSIGN(argsCpy, args); - int required = VsprintfLiteral(buf, fmt, argsCpy); - va_end(argsCpy); - - if (required < 0) { - return; // silently drop for now - } else if (required < 2048) { - profiler_tracing("log", buf, TRACING_EVENT); - } else { - char* heapBuf = new char[required+1]; - va_list argsCpy; - VARARGS_ASSIGN(argsCpy, args); - vsnprintf(heapBuf, required+1, fmt, argsCpy); - va_end(argsCpy); - // EVENT_BACKTRACE could be used to get a source - // for all log events. This could be a runtime - // flag later. - profiler_tracing("log", heapBuf, TRACING_EVENT); - delete[] heapBuf; - } - } -} -#endif - -//////////////////////////////////////////////////////////////////////// -// BEGIN externally visible functions - -void mozilla_sampler_init(void* stackTop) -{ - sInitCount++; - - if (stack_key_initialized) - return; - -#ifdef MOZ_TASK_TRACER - mozilla::tasktracer::InitTaskTracer(); -#endif - -#ifdef SPS_STANDALONE - mozilla::TimeStamp::Startup(); -#endif - - LOG("BEGIN mozilla_sampler_init"); - if (!tlsPseudoStack.init() || !tlsTicker.init() || !tlsStackTop.init()) { - LOG("Failed to init."); - return; - } - bool ignore; - sStartTime = mozilla::TimeStamp::ProcessCreation(ignore); - - stack_key_initialized = true; - - Sampler::Startup(); - - PseudoStack *stack = PseudoStack::create(); - tlsPseudoStack.set(stack); - - bool isMainThread = true; - Sampler::RegisterCurrentThread(isMainThread ? - gGeckoThreadName : "Application Thread", - stack, isMainThread, stackTop); - - // Read interval settings from MOZ_PROFILER_INTERVAL and stack-scan - // threshhold from MOZ_PROFILER_STACK_SCAN. - read_profiler_env_vars(); - - // platform specific initialization - OS::Startup(); - -#ifndef SPS_STANDALONE - set_stderr_callback(mozilla_sampler_log); -#endif - -#if defined(SPS_OS_android) - if (mozilla::jni::IsFennec()) { - GeckoJavaSampler::Init(); - } -#endif - - // 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 = getenv("MOZ_PROFILER_STARTUP"); - if (!val || !*val) { - return; - } - - const char* features[] = {"js" - , "leaf" - , "threads" -#if defined(XP_WIN) || defined(XP_MACOSX) \ - || (defined(SPS_ARCH_arm) && defined(linux)) \ - || defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux) - , "stackwalk" -#endif -#if defined(SPS_OS_android) - , "java" -#endif - }; - - const char* threadFilters[] = { "GeckoMain", "Compositor" }; - - profiler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL, - features, MOZ_ARRAY_LENGTH(features), - threadFilters, MOZ_ARRAY_LENGTH(threadFilters)); - LOG("END mozilla_sampler_init"); -} - -void mozilla_sampler_shutdown() -{ - sInitCount--; - - if (sInitCount > 0) - return; - - // Save the profile on shutdown if requested. - GeckoSampler *t = tlsTicker.get(); - if (t) { - const char *val = getenv("MOZ_PROFILER_SHUTDOWN"); - if (val) { - std::ofstream stream; - stream.open(val); - if (stream.is_open()) { - t->ToStreamAsJSON(stream); - stream.close(); - } - } - } - - profiler_stop(); - -#ifndef SPS_STANDALONE - set_stderr_callback(nullptr); -#endif - - Sampler::Shutdown(); - -#ifdef SPS_STANDALONE - mozilla::TimeStamp::Shutdown(); -#endif - - PseudoStack *stack = tlsPseudoStack.get(); - stack->deref(); - tlsPseudoStack.set(nullptr); - -#ifdef MOZ_TASK_TRACER - mozilla::tasktracer::ShutdownTaskTracer(); -#endif -} - -void mozilla_sampler_save() -{ - GeckoSampler *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(); -} - -mozilla::UniquePtr<char[]> mozilla_sampler_get_profile(double aSinceTime) -{ - GeckoSampler *t = tlsTicker.get(); - if (!t) { - return nullptr; - } - - return t->ToJSON(aSinceTime); -} - -#ifndef SPS_STANDALONE -JSObject *mozilla_sampler_get_profile_data(JSContext *aCx, double aSinceTime) -{ - GeckoSampler *t = tlsTicker.get(); - if (!t) { - return nullptr; - } - - return t->ToJSObject(aCx, aSinceTime); -} - -void mozilla_sampler_get_profile_data_async(double aSinceTime, - mozilla::dom::Promise* aPromise) -{ - GeckoSampler *t = tlsTicker.get(); - if (NS_WARN_IF(!t)) { - return; - } - - t->ToJSObjectAsync(aSinceTime, aPromise); -} - -void mozilla_sampler_get_profiler_start_params(int* aEntrySize, - double* aInterval, - mozilla::Vector<const char*>* aFilters, - mozilla::Vector<const char*>* aFeatures) -{ - if (NS_WARN_IF(!aEntrySize) || NS_WARN_IF(!aInterval) || - NS_WARN_IF(!aFilters) || NS_WARN_IF(!aFeatures)) { - return; - } - - GeckoSampler *t = tlsTicker.get(); - if (NS_WARN_IF(!t)) { - return; - } - - *aEntrySize = t->EntrySize(); - *aInterval = t->interval(); - - const ThreadNameFilterList& threadNameFilterList = t->ThreadNameFilters(); - MOZ_ALWAYS_TRUE(aFilters->resize(threadNameFilterList.length())); - for (uint32_t i = 0; i < threadNameFilterList.length(); ++i) { - (*aFilters)[i] = threadNameFilterList[i].c_str(); - } - - const FeatureList& featureList = t->Features(); - MOZ_ALWAYS_TRUE(aFeatures->resize(featureList.length())); - for (size_t i = 0; i < featureList.length(); ++i) { - (*aFeatures)[i] = featureList[i].c_str(); - } -} - -void mozilla_sampler_get_gatherer(nsISupports** aRetVal) -{ - if (!aRetVal) { - return; - } - - if (NS_WARN_IF(!profiler_is_active())) { - *aRetVal = nullptr; - return; - } - - GeckoSampler *t = tlsTicker.get(); - if (NS_WARN_IF(!t)) { - *aRetVal = nullptr; - return; - } - - t->GetGatherer(aRetVal); -} - -#endif - -void mozilla_sampler_save_profile_to_file(const char* aFilename) -{ - GeckoSampler *t = tlsTicker.get(); - if (!t) { - return; - } - - std::ofstream stream; - stream.open(aFilename); - if (stream.is_open()) { - t->ToStreamAsJSON(stream); - stream.close(); - LOGF("Saved to %s", aFilename); - } else { - LOG("Fail to open profile log file."); - } -} - - -const char** mozilla_sampler_get_features() -{ - static const char* features[] = { -#if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND) - // Walk the C++ stack. - "stackwalk", -#endif -#if defined(ENABLE_SPS_LEAF_DATA) - // Include the C++ leaf node if not stackwalking. DevTools - // profiler doesn't want the native addresses. - "leaf", -#endif -#if !defined(SPS_OS_windows) - // Use a seperate thread of walking the stack. - "unwinder", -#endif - "java", - // Only record samples during periods of bad responsiveness - "jank", - // Tell the JS engine to emmit pseudostack entries in the - // pro/epilogue. - "js", - // GPU Profiling (may not be supported by the GL) - "gpu", - // Profile the registered secondary threads. - "threads", - // Do not include user-identifiable information - "privacy", - // Dump the layer tree with the textures. - "layersdump", - // Dump the display list with the textures. - "displaylistdump", - // Add main thread I/O to the profile - "mainthreadio", - // Add RSS collection - "memory", -#ifdef MOZ_TASK_TRACER - // Start profiling with feature TaskTracer. - "tasktracer", -#endif -#if defined(XP_WIN) - // Add power collection - "power", -#endif - nullptr - }; - - return features; -} - -void mozilla_sampler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize, - uint32_t *aGeneration) -{ - *aCurrentPosition = 0; - *aTotalSize = 0; - *aGeneration = 0; - - if (!stack_key_initialized) - return; - - GeckoSampler *t = tlsTicker.get(); - if (!t) - return; - - t->GetBufferInfo(aCurrentPosition, aTotalSize, aGeneration); -} - -// Values are only honored on the first start -void mozilla_sampler_start(int aProfileEntries, double aInterval, - const char** aFeatures, uint32_t aFeatureCount, - const char** aThreadNameFilters, uint32_t aFilterCount) - -{ - LOG("BEGIN mozilla_sampler_start"); - - if (!stack_key_initialized) - profiler_init(nullptr); - - /* If the sampling interval was set using env vars, use that - in preference to anything else. */ - if (sUnwindInterval > 0) - aInterval = sUnwindInterval; - - /* If the entry count was set using env vars, use that, too: */ - if (sProfileEntries > 0) - aProfileEntries = sProfileEntries; - - // Reset the current state if the profiler is running - profiler_stop(); - - GeckoSampler* t; - t = new GeckoSampler(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL, - aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY, - aFeatures, aFeatureCount, - aThreadNameFilters, aFilterCount); - - tlsTicker.set(t); - t->Start(); - if (t->ProfileJS() || t->InPrivacyMode()) { - ::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); - std::vector<ThreadInfo*> threads = t->GetRegisteredThreads(); - - for (uint32_t i = 0; i < threads.size(); i++) { - ThreadInfo* info = threads[i]; - if (info->IsPendingDelete()) { - continue; - } - ThreadProfile* thread_profile = info->Profile(); - if (!thread_profile) { - continue; - } - thread_profile->GetPseudoStack()->reinitializeOnResume(); -#ifndef SPS_STANDALONE - if (t->ProfileJS()) { - thread_profile->GetPseudoStack()->enableJSSampling(); - } - if (t->InPrivacyMode()) { - thread_profile->GetPseudoStack()->mPrivacyMode = true; - } -#endif - } - } - -#if defined(SPS_OS_android) - if (t->ProfileJava()) { - int javaInterval = aInterval; - // Java sampling doesn't accuratly keep up with 1ms sampling - if (javaInterval < 10) { - aInterval = 10; - } - java::GeckoJavaSampler::Start(javaInterval, 1000); - } -#endif - -#ifndef SPS_STANDALONE - if (t->AddMainThreadIO()) { - if (!sInterposeObserver) { - // Lazily create IO interposer observer - sInterposeObserver = new mozilla::ProfilerIOInterposeObserver(); - } - mozilla::IOInterposer::Register(mozilla::IOInterposeObserver::OpAll, - sInterposeObserver); - } -#endif - - sIsProfiling = true; -#ifndef SPS_STANDALONE - sIsGPUProfiling = t->ProfileGPU(); - sIsLayersDump = t->LayersDump(); - sIsDisplayListDump = t->DisplayListDump(); - sIsRestyleProfiling = t->ProfileRestyle(); - - if (Sampler::CanNotifyObservers()) { - nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - if (os) { - nsTArray<nsCString> featuresArray; - nsTArray<nsCString> threadNameFiltersArray; - - for (size_t i = 0; i < aFeatureCount; ++i) { - featuresArray.AppendElement(aFeatures[i]); - } - - for (size_t i = 0; i < aFilterCount; ++i) { - threadNameFiltersArray.AppendElement(aThreadNameFilters[i]); - } - - nsCOMPtr<nsIProfilerStartParams> params = - new nsProfilerStartParams(aProfileEntries, aInterval, featuresArray, - threadNameFiltersArray); - - os->NotifyObservers(params, "profiler-started", nullptr); - } - } -#endif - - LOG("END mozilla_sampler_start"); -} - -void mozilla_sampler_stop() -{ - LOG("BEGIN mozilla_sampler_stop"); - - if (!stack_key_initialized) - return; - - GeckoSampler *t = tlsTicker.get(); - if (!t) { - LOG("END mozilla_sampler_stop-early"); - return; - } - - bool disableJS = t->ProfileJS(); - - t->Stop(); - delete t; - tlsTicker.set(nullptr); - -#ifndef SPS_STANDALONE - if (disableJS) { - PseudoStack *stack = tlsPseudoStack.get(); - ASSERT(stack != nullptr); - stack->disableJSSampling(); - } - - mozilla::IOInterposer::Unregister(mozilla::IOInterposeObserver::OpAll, - sInterposeObserver); - sInterposeObserver = nullptr; -#endif - - sIsProfiling = false; -#ifndef SPS_STANDALONE - sIsGPUProfiling = false; - sIsLayersDump = false; - sIsDisplayListDump = false; - sIsRestyleProfiling = false; - - if (Sampler::CanNotifyObservers()) { - nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(nullptr, "profiler-stopped", nullptr); - } -#endif - - LOG("END mozilla_sampler_stop"); -} - -bool mozilla_sampler_is_paused() { - if (Sampler::GetActiveSampler()) { - return Sampler::GetActiveSampler()->IsPaused(); - } else { - return false; - } -} - -void mozilla_sampler_pause() { - if (Sampler::GetActiveSampler()) { - Sampler::GetActiveSampler()->SetPaused(true); -#ifndef SPS_STANDALONE - if (Sampler::CanNotifyObservers()) { - nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(nullptr, "profiler-paused", nullptr); - } -#endif - } -} - -void mozilla_sampler_resume() { - if (Sampler::GetActiveSampler()) { - Sampler::GetActiveSampler()->SetPaused(false); -#ifndef SPS_STANDALONE - if (Sampler::CanNotifyObservers()) { - nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(nullptr, "profiler-resumed", nullptr); - } -#endif - } -} - -bool mozilla_sampler_feature_active(const char* aName) -{ - if (!profiler_is_active()) { - return false; - } - - if (strcmp(aName, "gpu") == 0) { - return sIsGPUProfiling; - } - - if (strcmp(aName, "layersdump") == 0) { - return sIsLayersDump; - } - - if (strcmp(aName, "displaylistdump") == 0) { - return sIsDisplayListDump; - } - - if (strcmp(aName, "restyle") == 0) { - return sIsRestyleProfiling; - } - - return false; -} - -bool mozilla_sampler_is_active() -{ - return sIsProfiling; -} - -void mozilla_sampler_responsiveness(const mozilla::TimeStamp& aTime) -{ - sLastTracerEvent = aTime; -} - -void mozilla_sampler_frame_number(int frameNumber) -{ - sFrameNumber = frameNumber; -} - -void mozilla_sampler_lock() -{ - profiler_stop(); -#ifndef SPS_STANDALONE - nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(nullptr, "profiler-locked", nullptr); -#endif -} - -void mozilla_sampler_unlock() -{ -#ifndef SPS_STANDALONE - nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(nullptr, "profiler-unlocked", nullptr); -#endif -} - -bool mozilla_sampler_register_thread(const char* aName, void* aGuessStackTop) -{ - if (sInitCount == 0) { - return false; - } - - MOZ_ASSERT(tlsPseudoStack.get() == nullptr); - PseudoStack* stack = PseudoStack::create(); - tlsPseudoStack.set(stack); - bool isMainThread = is_main_thread_name(aName); - void* stackTop = GetStackTop(aGuessStackTop); - return Sampler::RegisterCurrentThread(aName, stack, isMainThread, stackTop); -} - -void mozilla_sampler_unregister_thread() -{ - // Don't check sInitCount count here -- we may be unregistering the - // thread after the sampler was shut down. - if (!stack_key_initialized) { - return; - } - - PseudoStack *stack = tlsPseudoStack.get(); - if (!stack) { - return; - } - stack->deref(); - tlsPseudoStack.set(nullptr); - - Sampler::UnregisterCurrentThread(); -} - -void mozilla_sampler_sleep_start() { - if (sInitCount == 0) { - return; - } - - PseudoStack *stack = tlsPseudoStack.get(); - if (stack == nullptr) { - return; - } - stack->setSleeping(1); -} - -void mozilla_sampler_sleep_end() { - if (sInitCount == 0) { - return; - } - - PseudoStack *stack = tlsPseudoStack.get(); - if (stack == nullptr) { - return; - } - stack->setSleeping(0); -} - -bool mozilla_sampler_is_sleeping() { - if (sInitCount == 0) { - return false; - } - PseudoStack *stack = tlsPseudoStack.get(); - if (stack == nullptr) { - return false; - } - return stack->isSleeping(); -} - -double mozilla_sampler_time(const mozilla::TimeStamp& aTime) -{ - mozilla::TimeDuration delta = aTime - sStartTime; - return delta.ToMilliseconds(); -} - -double mozilla_sampler_time() -{ - return mozilla_sampler_time(mozilla::TimeStamp::Now()); -} - -ProfilerBacktrace* mozilla_sampler_get_backtrace() -{ - if (!stack_key_initialized) - return nullptr; - - // Don't capture a stack if we're not profiling - if (!profiler_is_active()) { - return nullptr; - } - - // Don't capture a stack if we don't want to include personal information - if (profiler_in_privacy_mode()) { - return nullptr; - } - - GeckoSampler* t = tlsTicker.get(); - if (!t) { - return nullptr; - } - - return new ProfilerBacktrace(t->GetBacktrace()); -} - -void mozilla_sampler_free_backtrace(ProfilerBacktrace* aBacktrace) -{ - delete aBacktrace; -} - -// Fill the output buffer with the following pattern: -// "Lable 1" "\0" "Label 2" "\0" ... "Label N" "\0" "\0" -// TODO: use the unwinder instead of pseudo stack. -void mozilla_sampler_get_backtrace_noalloc(char *output, size_t outputSize) -{ - MOZ_ASSERT(outputSize >= 2); - char *bound = output + outputSize - 2; - output[0] = output[1] = '\0'; - PseudoStack *pseudoStack = tlsPseudoStack.get(); - if (!pseudoStack) { - return; - } - - volatile StackEntry *pseudoFrames = pseudoStack->mStack; - uint32_t pseudoCount = pseudoStack->stackSize(); - - for (uint32_t i = 0; i < pseudoCount; i++) { - size_t len = strlen(pseudoFrames[i].label()); - if (output + len >= bound) - break; - strcpy(output, pseudoFrames[i].label()); - output += len; - *output++ = '\0'; - *output = '\0'; - } -} - -void mozilla_sampler_tracing(const char* aCategory, const char* aInfo, - TracingMetadata aMetaData) -{ - mozilla_sampler_add_marker(aInfo, new ProfilerMarkerTracing(aCategory, aMetaData)); -} - -void mozilla_sampler_tracing(const char* aCategory, const char* aInfo, - ProfilerBacktrace* aCause, - TracingMetadata aMetaData) -{ - mozilla_sampler_add_marker(aInfo, new ProfilerMarkerTracing(aCategory, aMetaData, aCause)); -} - -void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload) -{ - // Note that aPayload may be allocated by the caller, so we need to make sure - // that we free it at some point. - mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload); - - if (!stack_key_initialized) - return; - - // Don't insert a marker if we're not profiling to avoid - // the heap copy (malloc). - if (!profiler_is_active()) { - return; - } - - // Don't add a marker if we don't want to include personal information - if (profiler_in_privacy_mode()) { - return; - } - - PseudoStack *stack = tlsPseudoStack.get(); - if (!stack) { - return; - } - - mozilla::TimeStamp origin = (aPayload && !aPayload->GetStartTime().IsNull()) ? - aPayload->GetStartTime() : mozilla::TimeStamp::Now(); - mozilla::TimeDuration delta = origin - sStartTime; - stack->addMarker(aMarker, payload.release(), delta.ToMilliseconds()); -} - -#ifndef SPS_STANDALONE -#include "mozilla/Mutex.h" - -class GeckoMutex : public ::Mutex { - public: - explicit GeckoMutex(const char* aDesc) : - mMutex(aDesc) - {} - - virtual ~GeckoMutex() {} - - virtual int Lock() { - mMutex.Lock(); - return 0; - } - - virtual int Unlock() { - mMutex.Unlock(); - return 0; - } - - private: - mozilla::Mutex mMutex; -}; - -mozilla::UniquePtr< ::Mutex> OS::CreateMutex(const char* aDesc) { - return mozilla::MakeUnique<GeckoMutex>(aDesc); -} - -#else -// Otherwise use c++11 Mutex -#include <mutex> - -class OSXMutex : public ::Mutex { - public: - OSXMutex(const char* aDesc) : - mMutex() - {} - - virtual ~OSXMutex() {} - - virtual int Lock() { - mMutex.lock(); - return 0; - } - - virtual int Unlock() { - mMutex.unlock(); - return 0; - } - - private: - std::mutex mMutex; -}; - -mozilla::UniquePtr< ::Mutex> OS::CreateMutex(const char* aDesc) { - return mozilla::MakeUnique<GeckoMutex>(aDesc); -} - -#endif - -// END externally visible functions -//////////////////////////////////////////////////////////////////////// diff --git a/tools/profiler/core/platform.h b/tools/profiler/core/platform.h deleted file mode 100644 index 17c8dd25c..000000000 --- a/tools/profiler/core/platform.h +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in -// the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google, Inc. nor the names of its contributors -// may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -// SUCH DAMAGE. - -#ifndef TOOLS_PLATFORM_H_ -#define TOOLS_PLATFORM_H_ - -#ifdef SPS_STANDALONE -#define MOZ_COUNT_CTOR(name) -#define MOZ_COUNT_DTOR(name) -#endif - -#ifdef ANDROID -#include <android/log.h> -#else -#define __android_log_print(a, ...) -#endif - -#ifdef XP_UNIX -#include <pthread.h> -#endif - -#include <stdint.h> -#include <math.h> -#ifndef SPS_STANDALONE -#include "MainThreadUtils.h" -#include "mozilla/Mutex.h" -#include "ThreadResponsiveness.h" -#endif -#include "mozilla/TimeStamp.h" -#include "mozilla/UniquePtr.h" -#include "mozilla/Unused.h" -#include "PlatformMacros.h" -#include "v8-support.h" -#include <vector> -#include "StackTop.h" - -// We need a definition of gettid(), but Linux libc implementations don't -// provide a wrapper for it (except for Bionic) -#if defined(__linux__) -#include <unistd.h> -#if !defined(__BIONIC__) -#include <sys/syscall.h> -static inline pid_t gettid() -{ - return (pid_t) syscall(SYS_gettid); -} -#endif -#endif - -#ifdef XP_WIN -#include <windows.h> -#endif - -#define ASSERT(a) MOZ_ASSERT(a) - -bool moz_profiler_verbose(); - -#ifdef ANDROID -# if defined(__arm__) || defined(__thumb__) -# define ENABLE_SPS_LEAF_DATA -# define ENABLE_ARM_LR_SAVING -# endif -# define LOG(text) \ - do { if (moz_profiler_verbose()) \ - __android_log_write(ANDROID_LOG_ERROR, "Profiler", text); \ - } while (0) -# define LOGF(format, ...) \ - do { if (moz_profiler_verbose()) \ - __android_log_print(ANDROID_LOG_ERROR, "Profiler", format, \ - __VA_ARGS__); \ - } while (0) - -#else -# define LOG(text) \ - do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: %s\n", text); \ - } while (0) -# define LOGF(format, ...) \ - do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: " format \ - "\n", __VA_ARGS__); \ - } while (0) - -#endif - -#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX) -#define ENABLE_SPS_LEAF_DATA -#endif - -typedef int32_t Atomic32; - -extern mozilla::TimeStamp sStartTime; - -typedef uint8_t* Address; - -// ---------------------------------------------------------------------------- -// Mutex -// -// Mutexes are used for serializing access to non-reentrant sections of code. -// The implementations of mutex should allow for nested/recursive locking. - -class Mutex { - public: - virtual ~Mutex() {} - - // Locks the given mutex. If the mutex is currently unlocked, it becomes - // locked and owned by the calling thread, and immediately. If the mutex - // is already locked by another thread, suspends the calling thread until - // the mutex is unlocked. - virtual int Lock() = 0; - - // Unlocks the given mutex. The mutex is assumed to be locked and owned by - // the calling thread on entrance. - virtual int Unlock() = 0; -}; - -class MutexAutoLock { - public: - explicit MutexAutoLock(::Mutex& aMutex) - : mMutex(&aMutex) - { - mMutex->Lock(); - } - - ~MutexAutoLock() { - mMutex->Unlock(); - } - - private: - Mutex* mMutex; -}; - -// ---------------------------------------------------------------------------- -// OS -// -// This class has static methods for the different platform specific -// functions. Add methods here to cope with differences between the -// supported platforms. - -class OS { - public: - - // Sleep for a number of milliseconds. - static void Sleep(const int milliseconds); - - // Sleep for a number of microseconds. - static void SleepMicro(const int microseconds); - - // Called on startup to initialize platform specific things - static void Startup(); - - static mozilla::UniquePtr< ::Mutex> CreateMutex(const char* aDesc); - - private: - static const int msPerSecond = 1000; - -}; - - - - -// ---------------------------------------------------------------------------- -// Thread -// -// Thread objects are used for creating and running threads. When the start() -// method is called the new thread starts running the run() method in the new -// thread. The Thread object should not be deallocated before the thread has -// terminated. - -class Thread { - public: - // Create new thread. - explicit Thread(const char* name); - virtual ~Thread(); - - // Start new thread by calling the Run() method in the new thread. - void Start(); - - void Join(); - - inline const char* name() const { - return name_; - } - - // Abstract method for run handler. - virtual void Run() = 0; - - // The thread name length is limited to 16 based on Linux's implementation of - // prctl(). - static const int kMaxThreadNameLength = 16; - -#ifdef XP_WIN - HANDLE thread_; - typedef DWORD tid_t; - tid_t thread_id_; -#else - typedef ::pid_t tid_t; -#endif -#if defined(XP_MACOSX) - pthread_t thread_; -#endif - - static tid_t GetCurrentId(); - - private: - void set_name(const char *name); - - char name_[kMaxThreadNameLength]; - int stack_size_; - - 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. - -#undef HAVE_NATIVE_UNWIND -#if defined(MOZ_PROFILING) \ - && (defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \ - || (defined(MOZ_WIDGET_ANDROID) && defined(__arm__)) \ - || defined(SPS_PLAT_x86_linux) \ - || defined(SPS_OS_windows) \ - || defined(SPS_OS_darwin)) -# define HAVE_NATIVE_UNWIND -#endif - -/* Some values extracted at startup from environment variables, that - control the behaviour of the breakpad unwinder. */ -extern const char* PROFILER_INTERVAL; -extern const char* PROFILER_ENTRIES; -extern const char* PROFILER_STACK; -extern const char* PROFILER_FEATURES; - -void read_profiler_env_vars(); -void profiler_usage(); - -// Helper methods to expose modifying profiler behavior -bool set_profiler_interval(const char*); -bool set_profiler_entries(const char*); -bool set_profiler_scan(const char*); -bool is_native_unwinding_avail(); - -void set_tls_stack_top(void* stackTop); - -// ---------------------------------------------------------------------------- -// Sampler -// -// A sampler periodically samples the state of the VM and optionally -// (if used for profiling) the program counter and stack pointer for -// the thread that created it. - -struct PseudoStack; -class ThreadProfile; - -// TickSample captures the information collected for each sample. -class TickSample { - public: - TickSample() - : pc(NULL) - , sp(NULL) - , fp(NULL) -#ifdef ENABLE_ARM_LR_SAVING - , lr(NULL) -#endif - , context(NULL) - , isSamplingCurrentThread(false) - , threadProfile(nullptr) - , rssMemory(0) - , ussMemory(0) - {} - - void PopulateContext(void* aContext); - - Address pc; // Instruction pointer. - Address sp; // Stack pointer. - Address fp; // Frame pointer. -#ifdef ENABLE_ARM_LR_SAVING - Address lr; // ARM link register -#endif - void* context; // The context from the signal handler, if available. On - // Win32 this may contain the windows thread context. - bool isSamplingCurrentThread; - ThreadProfile* threadProfile; - mozilla::TimeStamp timestamp; - int64_t rssMemory; - int64_t ussMemory; -}; - -class ThreadInfo; -class PlatformData; -class GeckoSampler; -class SyncProfile; -class Sampler { - public: - // Initialize sampler. - explicit Sampler(double interval, bool profiling, int entrySize); - virtual ~Sampler(); - - double interval() const { return interval_; } - - // This method is called for each sampling period with the current - // program counter. - virtual void Tick(TickSample* sample) = 0; - - // Immediately captures the calling thread's call stack and returns it. - virtual SyncProfile* GetBacktrace() = 0; - - // Request a save from a signal handler - virtual void RequestSave() = 0; - // Process any outstanding request outside a signal handler. - virtual void HandleSaveRequest() = 0; - // Delete markers which are no longer part of the profile due to buffer wraparound. - virtual void DeleteExpiredMarkers() = 0; - - // Start and stop sampler. - void Start(); - void Stop(); - - // Is the sampler used for profiling? - bool IsProfiling() const { return profiling_; } - - // Whether the sampler is running (that is, consumes resources). - bool IsActive() const { return active_; } - - // Low overhead way to stop the sampler from ticking - bool IsPaused() const { return paused_; } - void SetPaused(bool value) { NoBarrier_Store(&paused_, value); } - - virtual bool ProfileThreads() const = 0; - - int EntrySize() { return entrySize_; } - - // We can't new/delete the type safely without defining it - // (-Wdelete-incomplete). Use these Alloc/Free functions instead. - static PlatformData* AllocPlatformData(int aThreadId); - static void FreePlatformData(PlatformData*); - - // If we move the backtracing code into the platform files we won't - // need to have these hacks -#ifdef XP_WIN - // xxxehsan sucky hack :( - static uintptr_t GetThreadHandle(PlatformData*); -#endif -#ifdef XP_MACOSX - static pthread_t GetProfiledThread(PlatformData*); -#endif - - static std::vector<ThreadInfo*> GetRegisteredThreads() { - return *sRegisteredThreads; - } - - static bool RegisterCurrentThread(const char* aName, - PseudoStack* aPseudoStack, - bool aIsMainThread, void* stackTop); - static void UnregisterCurrentThread(); - - static void Startup(); - // Should only be called on shutdown - static void Shutdown(); - - static GeckoSampler* GetActiveSampler() { return sActiveSampler; } - static void SetActiveSampler(GeckoSampler* sampler) { sActiveSampler = sampler; } - - static mozilla::UniquePtr<Mutex> sRegisteredThreadsMutex; - - static bool CanNotifyObservers() { -#if defined(SPS_OS_android) - // Android ANR reporter uses the profiler off the main thread - return NS_IsMainThread(); -#else - MOZ_ASSERT(NS_IsMainThread()); - return true; -#endif - } - - protected: - static std::vector<ThreadInfo*>* sRegisteredThreads; - static GeckoSampler* sActiveSampler; - - private: - void SetActive(bool value) { NoBarrier_Store(&active_, value); } - - const double interval_; - const bool profiling_; - Atomic32 paused_; - Atomic32 active_; - const int entrySize_; - - // Refactor me! -#if defined(SPS_OS_linux) || defined(SPS_OS_android) - bool signal_handler_installed_; - struct sigaction old_sigprof_signal_handler_; - struct sigaction old_sigsave_signal_handler_; - bool signal_sender_launched_; - pthread_t signal_sender_thread_; -#endif -}; - -#endif /* ndef TOOLS_PLATFORM_H_ */ diff --git a/tools/profiler/core/shared-libraries-linux.cc b/tools/profiler/core/shared-libraries-linux.cc deleted file mode 100644 index 553039fb7..000000000 --- a/tools/profiler/core/shared-libraries-linux.cc +++ /dev/null @@ -1,145 +0,0 @@ -/* 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 "shared-libraries.h" - -#define PATH_MAX_TOSTRING(x) #x -#define PATH_MAX_STRING(x) PATH_MAX_TOSTRING(x) -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <limits.h> -#include <unistd.h> -#include <fstream> -#include "platform.h" -#include "shared-libraries.h" - -#include "common/linux/file_id.h" -#include <algorithm> - -#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) - -// Get the breakpad Id for the binary file pointed by bin_name -static std::string getId(const char *bin_name) -{ - using namespace google_breakpad; - using namespace std; - - PageAllocator allocator; - auto_wasteful_vector<uint8_t, sizeof(MDGUID)> identifier(&allocator); - - FileID file_id(bin_name); - if (file_id.ElfFileIdentifier(identifier)) { - return FileID::ConvertIdentifierToUUIDString(identifier) + "0"; - } - - return ""; -} - -// TODO fix me with proper include -#include "nsDebug.h" -#ifdef ANDROID -#include "ElfLoader.h" // dl_phdr_info -#else -#include <link.h> // dl_phdr_info -#endif -#include <features.h> -#include <dlfcn.h> -#include <sys/types.h> - -#ifdef ANDROID -extern "C" MOZ_EXPORT __attribute__((weak)) -int dl_iterate_phdr( - int (*callback) (struct dl_phdr_info *info, - size_t size, void *data), - void *data); -#endif - -static int -dl_iterate_callback(struct dl_phdr_info *dl_info, size_t size, void *data) -{ - SharedLibraryInfo& info = *reinterpret_cast<SharedLibraryInfo*>(data); - - if (dl_info->dlpi_phnum <= 0) - return 0; - - unsigned long libStart = -1; - unsigned long libEnd = 0; - - for (size_t i = 0; i < dl_info->dlpi_phnum; i++) { - if (dl_info->dlpi_phdr[i].p_type != PT_LOAD) { - continue; - } - unsigned long start = dl_info->dlpi_addr + dl_info->dlpi_phdr[i].p_vaddr; - unsigned long end = start + dl_info->dlpi_phdr[i].p_memsz; - if (start < libStart) - libStart = start; - if (end > libEnd) - libEnd = end; - } - const char *name = dl_info->dlpi_name; - SharedLibrary shlib(libStart, libEnd, 0, getId(name), name); - info.AddSharedLibrary(shlib); - - return 0; -} - -SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() -{ - SharedLibraryInfo info; - -#ifdef ANDROID - if (!dl_iterate_phdr) { - // On ARM Android, dl_iterate_phdr is provided by the custom linker. - // So if libxul was loaded by the system linker (e.g. as part of - // xpcshell when running tests), it won't be available and we should - // not call it. - return info; - } -#endif // ANDROID - - dl_iterate_phdr(dl_iterate_callback, &info); - -#if defined(ANDROID) - pid_t pid = getpid(); - char path[PATH_MAX]; - snprintf(path, PATH_MAX, "/proc/%d/maps", pid); - std::ifstream maps(path); - std::string line; - int count = 0; - while (std::getline(maps, line)) { - int ret; - //XXX: needs input sanitizing - unsigned long start; - unsigned long end; - char perm[6] = ""; - unsigned long offset; - char name[PATH_MAX] = ""; - ret = sscanf(line.c_str(), - "%lx-%lx %6s %lx %*s %*x %" PATH_MAX_STRING(PATH_MAX) "s\n", - &start, &end, perm, &offset, name); - if (!strchr(perm, 'x')) { - // Ignore non executable entries - continue; - } - if (ret != 5 && ret != 4) { - LOG("Get maps line failed"); - continue; - } - // Use proc/pid/maps to get the dalvik-jit section since it has - // no associated phdrs - if (strcmp(name, "/dev/ashmem/dalvik-jit-code-cache") != 0) - continue; - SharedLibrary shlib(start, end, offset, getId(name), name); - info.AddSharedLibrary(shlib); - if (count > 10000) { - LOG("Get maps failed"); - break; - } - count++; - } -#endif // ANDROID - - return info; -} diff --git a/tools/profiler/core/shared-libraries-macos.cc b/tools/profiler/core/shared-libraries-macos.cc deleted file mode 100644 index e218d2280..000000000 --- a/tools/profiler/core/shared-libraries-macos.cc +++ /dev/null @@ -1,132 +0,0 @@ -/* -*- 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 <AvailabilityMacros.h> -#include <mach-o/loader.h> -#include <mach-o/dyld_images.h> -#include <mach/task_info.h> -#include <mach/task.h> -#include <mach/mach_init.h> -#include <mach/mach_traps.h> -#include <string.h> -#include <stdlib.h> -#include <vector> -#include <sstream> - -#include "shared-libraries.h" - -#ifndef MAC_OS_X_VERSION_10_6 -#define MAC_OS_X_VERSION_10_6 1060 -#endif - -#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 -// borrowed from Breakpad -// Fallback declarations for TASK_DYLD_INFO and friends, introduced in -// <mach/task_info.h> in the Mac OS X 10.6 SDK. -#define TASK_DYLD_INFO 17 -struct task_dyld_info { - mach_vm_address_t all_image_info_addr; - mach_vm_size_t all_image_info_size; - }; -typedef struct task_dyld_info task_dyld_info_data_t; -typedef struct task_dyld_info *task_dyld_info_t; -#define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t)) - -#endif - -// Architecture specific abstraction. -#ifdef __i386__ -typedef mach_header platform_mach_header; -typedef segment_command mach_segment_command_type; -#define MACHO_MAGIC_NUMBER MH_MAGIC -#define CMD_SEGMENT LC_SEGMENT -#define seg_size uint32_t -#else -typedef mach_header_64 platform_mach_header; -typedef segment_command_64 mach_segment_command_type; -#define MACHO_MAGIC_NUMBER MH_MAGIC_64 -#define CMD_SEGMENT LC_SEGMENT_64 -#define seg_size uint64_t -#endif - -static -void addSharedLibrary(const platform_mach_header* header, char *name, SharedLibraryInfo &info) { - const struct load_command *cmd = - reinterpret_cast<const struct load_command *>(header + 1); - - seg_size size = 0; - unsigned long long start = reinterpret_cast<unsigned long long>(header); - // Find the cmd segment in the macho image. It will contain the offset we care about. - const uint8_t *uuid_bytes = nullptr; - for (unsigned int i = 0; - cmd && (i < header->ncmds) && (uuid_bytes == nullptr || size == 0); - ++i) { - if (cmd->cmd == CMD_SEGMENT) { - const mach_segment_command_type *seg = - reinterpret_cast<const mach_segment_command_type *>(cmd); - - if (!strcmp(seg->segname, "__TEXT")) { - size = seg->vmsize; - } - } else if (cmd->cmd == LC_UUID) { - const uuid_command *ucmd = reinterpret_cast<const uuid_command *>(cmd); - uuid_bytes = ucmd->uuid; - } - - cmd = reinterpret_cast<const struct load_command *> - (reinterpret_cast<const char *>(cmd) + cmd->cmdsize); - } - - std::stringstream uuid; - uuid << std::hex << std::uppercase; - if (uuid_bytes != nullptr) { - for (int i = 0; i < 16; ++i) { - uuid << ((uuid_bytes[i] & 0xf0) >> 4); - uuid << (uuid_bytes[i] & 0xf); - } - uuid << '0'; - } - - info.AddSharedLibrary(SharedLibrary(start, start + size, 0, uuid.str(), - name)); -} - -// Use dyld to inspect the macho image information. We can build the SharedLibraryEntry structure -// giving us roughtly the same info as /proc/PID/maps in Linux. -SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() -{ - SharedLibraryInfo sharedLibraryInfo; - - task_dyld_info_data_t task_dyld_info; - mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; - if (task_info(mach_task_self (), TASK_DYLD_INFO, (task_info_t)&task_dyld_info, - &count) != KERN_SUCCESS) { - return sharedLibraryInfo; - } - - struct dyld_all_image_infos* aii = (struct dyld_all_image_infos*)task_dyld_info.all_image_info_addr; - size_t infoCount = aii->infoArrayCount; - - // Iterate through all dyld images (loaded libraries) to get their names - // and offests. - for (size_t i = 0; i < infoCount; ++i) { - const dyld_image_info *info = &aii->infoArray[i]; - - // If the magic number doesn't match then go no further - // since we're not pointing to where we think we are. - if (info->imageLoadAddress->magic != MACHO_MAGIC_NUMBER) { - continue; - } - - const platform_mach_header* header = - reinterpret_cast<const platform_mach_header*>(info->imageLoadAddress); - - // Add the entry for this image. - addSharedLibrary(header, (char*)info->imageFilePath, sharedLibraryInfo); - - } - return sharedLibraryInfo; -} - diff --git a/tools/profiler/core/shared-libraries-win32.cc b/tools/profiler/core/shared-libraries-win32.cc deleted file mode 100644 index e2db2579b..000000000 --- a/tools/profiler/core/shared-libraries-win32.cc +++ /dev/null @@ -1,137 +0,0 @@ -/* -*- 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 <windows.h> -#include <tlhelp32.h> -#include <dbghelp.h> -#include <sstream> - -#include "shared-libraries.h" -#include "nsWindowsHelpers.h" - -#define CV_SIGNATURE 0x53445352 // 'SDSR' - -struct CodeViewRecord70 -{ - uint32_t signature; - GUID pdbSignature; - uint32_t pdbAge; - char pdbFileName[1]; -}; - -static bool GetPdbInfo(uintptr_t aStart, nsID& aSignature, uint32_t& aAge, char** aPdbName) -{ - if (!aStart) { - return false; - } - - PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(aStart); - if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { - return false; - } - - PIMAGE_NT_HEADERS ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>( - aStart + dosHeader->e_lfanew); - if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { - return false; - } - - uint32_t relativeVirtualAddress = - ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; - if (!relativeVirtualAddress) { - return false; - } - - PIMAGE_DEBUG_DIRECTORY debugDirectory = - reinterpret_cast<PIMAGE_DEBUG_DIRECTORY>(aStart + relativeVirtualAddress); - if (!debugDirectory || debugDirectory->Type != IMAGE_DEBUG_TYPE_CODEVIEW) { - return false; - } - - CodeViewRecord70 *debugInfo = reinterpret_cast<CodeViewRecord70 *>( - aStart + debugDirectory->AddressOfRawData); - if (!debugInfo || debugInfo->signature != CV_SIGNATURE) { - return false; - } - - aAge = debugInfo->pdbAge; - GUID& pdbSignature = debugInfo->pdbSignature; - aSignature.m0 = pdbSignature.Data1; - aSignature.m1 = pdbSignature.Data2; - aSignature.m2 = pdbSignature.Data3; - memcpy(aSignature.m3, pdbSignature.Data4, sizeof(pdbSignature.Data4)); - - // The PDB file name could be different from module filename, so report both - // e.g. The PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb - char * leafName = strrchr(debugInfo->pdbFileName, '\\'); - if (leafName) { - // Only report the file portion of the path - *aPdbName = leafName + 1; - } else { - *aPdbName = debugInfo->pdbFileName; - } - - return true; -} - -static bool IsDashOrBraces(char c) -{ - return c == '-' || c == '{' || c == '}'; -} - -SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() -{ - SharedLibraryInfo sharedLibraryInfo; - - nsAutoHandle snap(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId())); - - MODULEENTRY32 module = {0}; - module.dwSize = sizeof(MODULEENTRY32); - if (Module32First(snap, &module)) { - do { - nsID pdbSig; - uint32_t pdbAge; - char *pdbName = NULL; - - // Load the module again to make sure that its handle will remain remain - // valid as we attempt to read the PDB information from it. We load the - // DLL as a datafile so that if the module actually gets unloaded between - // the call to Module32Next and the following LoadLibraryEx, we don't end - // up running the now newly loaded module's DllMain function. If the - // module is already loaded, LoadLibraryEx just increments its refcount. - // - // Note that because of the race condition above, merely loading the DLL - // again is not safe enough, therefore we also need to make sure that we - // can read the memory mapped at the base address before we can safely - // proceed to actually access those pages. - HMODULE handleLock = LoadLibraryEx(module.szExePath, NULL, LOAD_LIBRARY_AS_DATAFILE); - MEMORY_BASIC_INFORMATION vmemInfo = {0}; - if (handleLock && - sizeof(vmemInfo) == VirtualQuery(module.modBaseAddr, &vmemInfo, sizeof(vmemInfo)) && - vmemInfo.State == MEM_COMMIT && - GetPdbInfo((uintptr_t)module.modBaseAddr, pdbSig, pdbAge, &pdbName)) { - std::ostringstream stream; - stream << pdbSig.ToString() << std::hex << pdbAge; - std::string breakpadId = stream.str(); - std::string::iterator end = - std::remove_if(breakpadId.begin(), breakpadId.end(), IsDashOrBraces); - breakpadId.erase(end, breakpadId.end()); - std::transform(breakpadId.begin(), breakpadId.end(), - breakpadId.begin(), toupper); - - SharedLibrary shlib((uintptr_t)module.modBaseAddr, - (uintptr_t)module.modBaseAddr+module.modBaseSize, - 0, // DLLs are always mapped at offset 0 on Windows - breakpadId, - pdbName); - sharedLibraryInfo.AddSharedLibrary(shlib); - } - FreeLibrary(handleLock); // ok to free null handles - } while (Module32Next(snap, &module)); - } - - return sharedLibraryInfo; -} - |