mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-18 13:17:29 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
678
tools/profiler/core/EHABIStackWalk.cpp
Normal file
678
tools/profiler/core/EHABIStackWalk.cpp
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
/* -*- 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
|
||||
|
||||
28
tools/profiler/core/EHABIStackWalk.h
Normal file
28
tools/profiler/core/EHABIStackWalk.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- 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
|
||||
1306
tools/profiler/core/GeckoSampler.cpp
Normal file
1306
tools/profiler/core/GeckoSampler.cpp
Normal file
File diff suppressed because it is too large
Load diff
181
tools/profiler/core/GeckoSampler.h
Normal file
181
tools/profiler/core/GeckoSampler.h
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/* -*- 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
|
||||
|
||||
310
tools/profiler/core/IntelPowerGadget.cpp
Normal file
310
tools/profiler/core/IntelPowerGadget.cpp
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
150
tools/profiler/core/IntelPowerGadget.h
Normal file
150
tools/profiler/core/IntelPowerGadget.h
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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
|
||||
76
tools/profiler/core/PlatformMacros.h
Normal file
76
tools/profiler/core/PlatformMacros.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* -*- 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 SPS_PLATFORM_MACROS_H
|
||||
#define SPS_PLATFORM_MACROS_H
|
||||
|
||||
/* Define platform selection macros in a consistent way. Don't add
|
||||
anything else to this file, so it can remain freestanding. The
|
||||
primary factorisation is on (ARCH,OS) pairs ("PLATforms") but ARCH_
|
||||
and OS_ macros are defined too, since they are sometimes
|
||||
convenient. */
|
||||
|
||||
#undef SPS_PLAT_arm_android
|
||||
#undef SPS_PLAT_amd64_linux
|
||||
#undef SPS_PLAT_x86_linux
|
||||
#undef SPS_PLAT_amd64_darwin
|
||||
#undef SPS_PLAT_x86_darwin
|
||||
#undef SPS_PLAT_x86_windows
|
||||
#undef SPS_PLAT_amd64_windows
|
||||
|
||||
#undef SPS_ARCH_arm
|
||||
#undef SPS_ARCH_x86
|
||||
#undef SPS_ARCH_amd64
|
||||
|
||||
#undef SPS_OS_android
|
||||
#undef SPS_OS_linux
|
||||
#undef SPS_OS_darwin
|
||||
#undef SPS_OS_windows
|
||||
|
||||
#if defined(__linux__) && defined(__x86_64__)
|
||||
# define SPS_PLAT_amd64_linux 1
|
||||
# define SPS_ARCH_amd64 1
|
||||
# define SPS_OS_linux 1
|
||||
|
||||
#elif defined(__ANDROID__) && defined(__arm__)
|
||||
# define SPS_PLAT_arm_android 1
|
||||
# define SPS_ARCH_arm 1
|
||||
# define SPS_OS_android 1
|
||||
|
||||
#elif defined(__ANDROID__) && defined(__i386__)
|
||||
# define SPS_PLAT_x86_android 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_android 1
|
||||
|
||||
#elif defined(__linux__) && defined(__i386__)
|
||||
# define SPS_PLAT_x86_linux 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_linux 1
|
||||
|
||||
#elif defined(__APPLE__) && defined(__x86_64__)
|
||||
# define SPS_PLAT_amd64_darwin 1
|
||||
# define SPS_ARCH_amd64 1
|
||||
# define SPS_OS_darwin 1
|
||||
|
||||
#elif defined(__APPLE__) && defined(__i386__)
|
||||
# define SPS_PLAT_x86_darwin 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_darwin 1
|
||||
|
||||
#elif (defined(_MSC_VER) || defined(__MINGW32__)) && (defined(_M_IX86) || defined(__i386__))
|
||||
# define SPS_PLAT_x86_windows 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_windows 1
|
||||
|
||||
#elif (defined(_MSC_VER) || defined(__MINGW32__)) && (defined(_M_X64) || defined(__x86_64__))
|
||||
# define SPS_PLAT_amd64_windows 1
|
||||
# define SPS_ARCH_amd64 1
|
||||
# define SPS_OS_windows 1
|
||||
|
||||
#else
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
#endif /* ndef SPS_PLATFORM_MACROS_H */
|
||||
89
tools/profiler/core/ProfileBuffer.cpp
Normal file
89
tools/profiler/core/ProfileBuffer.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/* -*- 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;
|
||||
}
|
||||
|
||||
|
||||
61
tools/profiler/core/ProfileBuffer.h
Normal file
61
tools/profiler/core/ProfileBuffer.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/* -*- 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
|
||||
881
tools/profiler/core/ProfileEntry.cpp
Normal file
881
tools/profiler/core/ProfileEntry.cpp
Normal file
|
|
@ -0,0 +1,881 @@
|
|||
/* -*- 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
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
407
tools/profiler/core/ProfileEntry.h
Normal file
407
tools/profiler/core/ProfileEntry.h
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/* -*- 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 */
|
||||
115
tools/profiler/core/ProfileJSONWriter.cpp
Normal file
115
tools/profiler/core/ProfileJSONWriter.cpp
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/* -*- 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;
|
||||
}
|
||||
126
tools/profiler/core/ProfileJSONWriter.h
Normal file
126
tools/profiler/core/ProfileJSONWriter.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/* -*- 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
|
||||
33
tools/profiler/core/ProfilerBacktrace.cpp
Normal file
33
tools/profiler/core/ProfilerBacktrace.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- 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);
|
||||
}
|
||||
210
tools/profiler/core/ProfilerMarkers.cpp
Normal file
210
tools/profiler/core/ProfilerMarkers.cpp
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/* -*- 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
|
||||
48
tools/profiler/core/StackTop.cpp
Normal file
48
tools/profiler/core/StackTop.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- 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
|
||||
}
|
||||
10
tools/profiler/core/StackTop.h
Normal file
10
tools/profiler/core/StackTop.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* -*- 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
|
||||
57
tools/profiler/core/SyncProfile.cpp
Normal file
57
tools/profiler/core/SyncProfile.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* -*- 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);
|
||||
}
|
||||
43
tools/profiler/core/SyncProfile.h
Normal file
43
tools/profiler/core/SyncProfile.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* -*- 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
|
||||
|
||||
73
tools/profiler/core/ThreadInfo.cpp
Normal file
73
tools/profiler/core/ThreadInfo.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- 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
|
||||
}
|
||||
66
tools/profiler/core/ThreadInfo.h
Normal file
66
tools/profiler/core/ThreadInfo.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* -*- 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
|
||||
260
tools/profiler/core/ThreadProfile.cpp
Normal file
260
tools/profiler/core/ThreadProfile.cpp
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/* -*- 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);
|
||||
}
|
||||
|
||||
107
tools/profiler/core/ThreadProfile.h
Normal file
107
tools/profiler/core/ThreadProfile.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/* -*- 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
|
||||
715
tools/profiler/core/platform-linux.cc
Normal file
715
tools/profiler/core/platform-linux.cc
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
// 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");
|
||||
}
|
||||
469
tools/profiler/core/platform-macos.cc
Normal file
469
tools/profiler/core/platform-macos.cc
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/* 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)));
|
||||
}
|
||||
|
||||
431
tools/profiler/core/platform-win32.cc
Normal file
431
tools/profiler/core/platform-win32.cc
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// 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
|
||||
}
|
||||
|
||||
1266
tools/profiler/core/platform.cpp
Normal file
1266
tools/profiler/core/platform.cpp
Normal file
File diff suppressed because it is too large
Load diff
431
tools/profiler/core/platform.h
Normal file
431
tools/profiler/core/platform.h
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// 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() {
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// We use profile.sh on b2g to manually select threads and options per process.
|
||||
return false;
|
||||
#elif defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
// 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_ */
|
||||
159
tools/profiler/core/shared-libraries-linux.cc
Normal file
159
tools/profiler/core/shared-libraries-linux.cc
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/* 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 "";
|
||||
}
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK)
|
||||
// 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;
|
||||
}
|
||||
|
||||
#endif // !MOZ_WIDGET_GONK
|
||||
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo info;
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK)
|
||||
#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);
|
||||
#endif // !MOZ_WIDGET_GONK
|
||||
|
||||
#if defined(ANDROID) || defined(MOZ_WIDGET_GONK)
|
||||
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;
|
||||
}
|
||||
#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
|
||||
// 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;
|
||||
#else
|
||||
if (strcmp(perm, "r-xp") != 0) {
|
||||
// Ignore entries that are writable and/or shared.
|
||||
// At least one graphics driver uses short-lived "rwxs" mappings
|
||||
// (see bug 926734 comment 5), so just checking for 'x' isn't enough.
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
SharedLibrary shlib(start, end, offset, getId(name), name);
|
||||
info.AddSharedLibrary(shlib);
|
||||
if (count > 10000) {
|
||||
LOG("Get maps failed");
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
#endif // ANDROID || MOZ_WIDGET_GONK
|
||||
|
||||
return info;
|
||||
}
|
||||
132
tools/profiler/core/shared-libraries-macos.cc
Normal file
132
tools/profiler/core/shared-libraries-macos.cc
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/* -*- 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;
|
||||
}
|
||||
|
||||
137
tools/profiler/core/shared-libraries-win32.cc
Normal file
137
tools/profiler/core/shared-libraries-win32.cc
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/* -*- 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;
|
||||
}
|
||||
|
||||
48
tools/profiler/core/v8-support.h
Normal file
48
tools/profiler/core/v8-support.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/* This contains stubs and infrastructure to support code from v8 */
|
||||
|
||||
#ifndef V8_SUPPORT_H_
|
||||
#define V8_SUPPORT_H_
|
||||
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
#define V8_HOST_ARCH_X64 1
|
||||
#elif defined(_M_IX86) || defined(__i386__) || defined(__i386)
|
||||
#define V8_HOST_ARCH_IA32 1
|
||||
#elif defined(__ARMEL__)
|
||||
#define V8_HOST_ARCH_ARM 1
|
||||
#else
|
||||
#warning Please add support for your architecture in chromium_types.h
|
||||
#endif
|
||||
|
||||
typedef int32_t Atomic32;
|
||||
|
||||
#if defined(V8_HOST_ARCH_X64) || defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_ARM)
|
||||
inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
const int kMaxInt = 0x7FFFFFFF;
|
||||
const int kMinInt = -kMaxInt - 1;
|
||||
|
||||
// A macro to disallow the evil copy constructor and operator= functions
|
||||
// This should be used in the private: declarations for a class
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
|
||||
// The USE(x) template is used to silence C++ compiler warnings
|
||||
// issued for (yet) unused variables (typically parameters).
|
||||
template <typename T>
|
||||
static inline void USE(T) { }
|
||||
|
||||
class Malloced {
|
||||
};
|
||||
|
||||
#endif // V8_SUPPORT_H_
|
||||
Loading…
Add table
Add a link
Reference in a new issue