mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-05 12:05:22 +00:00
481 lines
14 KiB
C++
481 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include <ostream>
|
|
#include "platform.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
// JSON
|
|
#include "JSObjectBuilder.h"
|
|
#include "JSCustomObjectBuilder.h"
|
|
|
|
// Self
|
|
#include "ProfileEntry.h"
|
|
|
|
#if _MSC_VER
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// 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 aTagFloat)
|
|
: mTagFloat(aTagFloat)
|
|
, 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 aTagLine)
|
|
: mTagLine(aTagLine)
|
|
, 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;
|
|
}
|
|
|
|
void ProfileEntry::log()
|
|
{
|
|
// There is no compiler enforced mapping between tag chars
|
|
// and union variant fields, so the following was derived
|
|
// by looking through all the use points of TableTicker.cpp.
|
|
// mTagMarker (ProfilerMarker*) m
|
|
// mTagData (const char*) c,s
|
|
// mTagPtr (void*) d,l,L,B (immediate backtrace), S(start-of-stack)
|
|
// mTagLine (int) n,f
|
|
// mTagChar (char) h
|
|
// mTagFloat (double) r,t
|
|
switch (mTagName) {
|
|
case 'm':
|
|
LOGF("%c \"%s\"", mTagName, mTagMarker->GetMarkerName()); break;
|
|
case 'c': case 's':
|
|
LOGF("%c \"%s\"", mTagName, mTagData); break;
|
|
case 'd': case 'l': case 'L': case 'B': case 'S':
|
|
LOGF("%c %p", mTagName, mTagPtr); break;
|
|
case 'n': case 'f':
|
|
LOGF("%c %d", mTagName, mTagLine); break;
|
|
case 'h':
|
|
LOGF("%c \'%c\'", mTagName, mTagChar); break;
|
|
case 'r': case 't':
|
|
LOGF("%c %f", mTagName, mTagFloat); break;
|
|
default:
|
|
LOGF("'%c' unknown_tag", mTagName); break;
|
|
}
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry)
|
|
{
|
|
if (entry.mTagName == 'r' || entry.mTagName == 't') {
|
|
stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
|
|
} else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
|
|
// Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
|
|
// Additionally, stringstream seemed to be ignoring formatter flags.
|
|
char tagBuff[1024];
|
|
unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
|
|
snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
|
|
stream << tagBuff;
|
|
} else if (entry.mTagName == 'd') {
|
|
// TODO implement 'd' tag for text profile
|
|
} else {
|
|
stream << entry.mTagName << "-" << entry.mTagData << "\n";
|
|
}
|
|
return stream;
|
|
}
|
|
|
|
// END ProfileEntry
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// BEGIN ThreadProfile
|
|
|
|
#define DYNAMIC_MAX_STRING 512
|
|
|
|
ThreadProfile::ThreadProfile(const char* aName, int aEntrySize,
|
|
PseudoStack *aStack, Thread::tid_t aThreadId,
|
|
PlatformData* aPlatform,
|
|
bool aIsMainThread, void *aStackTop)
|
|
: mWritePos(0)
|
|
, mLastFlushPos(0)
|
|
, mReadPos(0)
|
|
, mEntrySize(aEntrySize)
|
|
, mPseudoStack(aStack)
|
|
, mMutex("ThreadProfile::mMutex")
|
|
, mName(strdup(aName))
|
|
, mThreadId(aThreadId)
|
|
, mIsMainThread(aIsMainThread)
|
|
, mPlatformData(aPlatform)
|
|
, mGeneration(0)
|
|
, mPendingGenerationFlush(0)
|
|
, mStackTop(aStackTop)
|
|
{
|
|
mEntries = new ProfileEntry[mEntrySize];
|
|
}
|
|
|
|
ThreadProfile::~ThreadProfile()
|
|
{
|
|
free(mName);
|
|
delete[] mEntries;
|
|
}
|
|
|
|
void ThreadProfile::addTag(ProfileEntry aTag)
|
|
{
|
|
// Called from signal, call only reentrant functions
|
|
mEntries[mWritePos] = aTag;
|
|
mWritePos = mWritePos + 1;
|
|
if (mWritePos >= mEntrySize) {
|
|
mPendingGenerationFlush++;
|
|
mWritePos = mWritePos % mEntrySize;
|
|
}
|
|
if (mWritePos == mReadPos) {
|
|
// Keep one slot open
|
|
mEntries[mReadPos] = ProfileEntry();
|
|
mReadPos = (mReadPos + 1) % mEntrySize;
|
|
}
|
|
// we also need to move the flush pos to ensure we
|
|
// do not pass it
|
|
if (mWritePos == mLastFlushPos) {
|
|
mLastFlushPos = (mLastFlushPos + 1) % mEntrySize;
|
|
}
|
|
}
|
|
|
|
// flush the new entries
|
|
void ThreadProfile::flush()
|
|
{
|
|
mLastFlushPos = mWritePos;
|
|
mGeneration += mPendingGenerationFlush;
|
|
mPendingGenerationFlush = 0;
|
|
}
|
|
|
|
// discards all of the entries since the last flush()
|
|
// NOTE: that if mWritePos happens to wrap around past
|
|
// mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries
|
|
//
|
|
// r = mReadPos
|
|
// w = mWritePos
|
|
// f = mLastFlushPos
|
|
//
|
|
// r f w
|
|
// |-----------------------------|
|
|
// | abcdefghijklmnopq | -> 'abcdefghijklmnopq'
|
|
// |-----------------------------|
|
|
//
|
|
//
|
|
// mWritePos and mReadPos have passed mLastFlushPos
|
|
// f
|
|
// w r
|
|
// |-----------------------------|
|
|
// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
|
|
// |-----------------------------|
|
|
// w
|
|
// r
|
|
// |-----------------------------|
|
|
// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
|
|
// |-----------------------------|
|
|
//
|
|
//
|
|
// mWritePos will end up the same as mReadPos
|
|
// r
|
|
// w f
|
|
// |-----------------------------|
|
|
// |ABCDEFGHIJKLMklmnopqrstuvwxyz|
|
|
// |-----------------------------|
|
|
// r
|
|
// w
|
|
// |-----------------------------|
|
|
// |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> ''
|
|
// |-----------------------------|
|
|
//
|
|
//
|
|
// mWritePos has moved past mReadPos
|
|
// w r f
|
|
// |-----------------------------|
|
|
// |ABCDEFdefghijklmnopqrstuvwxyz|
|
|
// |-----------------------------|
|
|
// r w
|
|
// |-----------------------------|
|
|
// |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
|
|
// |-----------------------------|
|
|
|
|
void ThreadProfile::erase()
|
|
{
|
|
mWritePos = mLastFlushPos;
|
|
mPendingGenerationFlush = 0;
|
|
}
|
|
|
|
char* ThreadProfile::processDynamicTag(int readPos,
|
|
int* tagsConsumed, char* tagBuff)
|
|
{
|
|
int readAheadPos = (readPos + 1) % mEntrySize;
|
|
int tagBuffPos = 0;
|
|
|
|
// Read the string stored in mTagData until the null character is seen
|
|
bool seenNullByte = false;
|
|
while (readAheadPos != mLastFlushPos && !seenNullByte) {
|
|
(*tagsConsumed)++;
|
|
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
|
for (size_t pos = 0; pos < sizeof(void*); pos++) {
|
|
tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
|
|
if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
|
|
seenNullByte = true;
|
|
break;
|
|
}
|
|
tagBuffPos++;
|
|
}
|
|
if (!seenNullByte)
|
|
readAheadPos = (readAheadPos + 1) % mEntrySize;
|
|
}
|
|
return tagBuff;
|
|
}
|
|
|
|
void ThreadProfile::IterateTags(IterateTagsCallback aCallback)
|
|
{
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
int readPos = mReadPos;
|
|
while (readPos != mLastFlushPos) {
|
|
// Number of tag consumed
|
|
int incBy = 1;
|
|
const ProfileEntry& entry = mEntries[readPos];
|
|
|
|
// Read ahead to the next tag, if it's a 'd' tag process it now
|
|
const char* tagStringData = entry.mTagData;
|
|
int readAheadPos = (readPos + 1) % mEntrySize;
|
|
char tagBuff[DYNAMIC_MAX_STRING];
|
|
// Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
|
|
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
|
|
|
if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
|
|
tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
|
|
}
|
|
|
|
aCallback(entry, tagStringData);
|
|
|
|
readPos = (readPos + incBy) % mEntrySize;
|
|
}
|
|
}
|
|
|
|
void ThreadProfile::ToStreamAsJSON(std::ostream& stream)
|
|
{
|
|
JSCustomObjectBuilder b;
|
|
JSCustomObject *profile = b.CreateObject();
|
|
BuildJSObject(b, profile);
|
|
b.Serialize(profile, stream);
|
|
b.DeleteObject(profile);
|
|
}
|
|
|
|
JSObject* ThreadProfile::ToJSObject(JSContext *aCx)
|
|
{
|
|
JSObjectBuilder b(aCx);
|
|
JS::RootedObject profile(aCx, b.CreateObject());
|
|
BuildJSObject(b, profile);
|
|
return profile;
|
|
}
|
|
|
|
template <typename Builder>
|
|
void ThreadProfile::BuildJSObject(Builder& b,
|
|
typename Builder::ObjectHandle profile)
|
|
{
|
|
// Thread meta data
|
|
if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
|
|
// TODO Add the proper plugin name
|
|
b.DefineProperty(profile, "name", "Plugin");
|
|
} else {
|
|
b.DefineProperty(profile, "name", mName);
|
|
}
|
|
|
|
b.DefineProperty(profile, "tid", static_cast<int>(mThreadId));
|
|
|
|
typename Builder::RootedArray samples(b.context(), b.CreateArray());
|
|
b.DefineProperty(profile, "samples", samples);
|
|
|
|
typename Builder::RootedObject sample(b.context());
|
|
typename Builder::RootedArray frames(b.context());
|
|
typename Builder::RootedArray markers(b.context());
|
|
|
|
int readPos = mReadPos;
|
|
while (readPos != mLastFlushPos) {
|
|
// Number of tag consumed
|
|
int incBy = 1;
|
|
ProfileEntry entry = mEntries[readPos];
|
|
|
|
// Read ahead to the next tag, if it's a 'd' tag process it now
|
|
const char* tagStringData = entry.mTagData;
|
|
int readAheadPos = (readPos + 1) % mEntrySize;
|
|
char tagBuff[DYNAMIC_MAX_STRING];
|
|
// Make sure the string is always null terminated if it fills up
|
|
// DYNAMIC_MAX_STRING-2
|
|
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
|
|
|
if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
|
|
tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
|
|
}
|
|
|
|
switch (entry.mTagName) {
|
|
case 's':
|
|
sample = b.CreateObject();
|
|
b.DefineProperty(sample, "name", tagStringData);
|
|
frames = b.CreateArray();
|
|
b.DefineProperty(sample, "frames", frames);
|
|
b.ArrayPush(samples, sample);
|
|
// Created lazily
|
|
markers = nullptr;
|
|
break;
|
|
case 'm':
|
|
{
|
|
if (sample) {
|
|
if (!markers) {
|
|
markers = b.CreateArray();
|
|
b.DefineProperty(sample, "marker", markers);
|
|
}
|
|
entry.getMarker()->BuildJSObject(b, markers);
|
|
}
|
|
}
|
|
break;
|
|
case 'r':
|
|
{
|
|
if (sample) {
|
|
b.DefineProperty(sample, "responsiveness", entry.mTagFloat);
|
|
}
|
|
}
|
|
break;
|
|
case 'p':
|
|
{
|
|
if (sample) {
|
|
b.DefineProperty(sample, "power", entry.mTagFloat);
|
|
}
|
|
}
|
|
break;
|
|
case 'f':
|
|
{
|
|
if (sample) {
|
|
b.DefineProperty(sample, "frameNumber", entry.mTagLine);
|
|
}
|
|
}
|
|
break;
|
|
case 't':
|
|
{
|
|
if (sample) {
|
|
b.DefineProperty(sample, "time", entry.mTagFloat);
|
|
}
|
|
}
|
|
break;
|
|
case 'c':
|
|
case 'l':
|
|
{
|
|
if (sample) {
|
|
typename Builder::RootedObject frame(b.context(), b.CreateObject());
|
|
if (entry.mTagName == 'l') {
|
|
// Bug 753041
|
|
// We need a double cast here to tell GCC that we don't want to sign
|
|
// extend 32-bit addresses starting with 0xFXXXXXX.
|
|
unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
|
|
snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
|
|
b.DefineProperty(frame, "location", tagBuff);
|
|
} else {
|
|
b.DefineProperty(frame, "location", tagStringData);
|
|
readAheadPos = (readPos + incBy) % mEntrySize;
|
|
if (readAheadPos != mLastFlushPos &&
|
|
mEntries[readAheadPos].mTagName == 'n') {
|
|
b.DefineProperty(frame, "line",
|
|
mEntries[readAheadPos].mTagLine);
|
|
incBy++;
|
|
}
|
|
}
|
|
b.ArrayPush(frames, frame);
|
|
}
|
|
}
|
|
}
|
|
readPos = (readPos + incBy) % mEntrySize;
|
|
}
|
|
}
|
|
|
|
template void ThreadProfile::BuildJSObject<JSObjectBuilder>(JSObjectBuilder& b,
|
|
JS::HandleObject profile);
|
|
template void ThreadProfile::BuildJSObject<JSCustomObjectBuilder>(JSCustomObjectBuilder& b,
|
|
JSCustomObject *profile);
|
|
|
|
PseudoStack* ThreadProfile::GetPseudoStack()
|
|
{
|
|
return mPseudoStack;
|
|
}
|
|
|
|
void ThreadProfile::BeginUnwind()
|
|
{
|
|
mMutex.Lock();
|
|
}
|
|
|
|
void ThreadProfile::EndUnwind()
|
|
{
|
|
mMutex.Unlock();
|
|
}
|
|
|
|
mozilla::Mutex* ThreadProfile::GetMutex()
|
|
{
|
|
return &mMutex;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile)
|
|
{
|
|
int readPos = profile.mReadPos;
|
|
while (readPos != profile.mLastFlushPos) {
|
|
stream << profile.mEntries[readPos];
|
|
readPos = (readPos + 1) % profile.mEntrySize;
|
|
}
|
|
return stream;
|
|
}
|
|
|
|
// END ThreadProfile
|
|
////////////////////////////////////////////////////////////////////////
|