mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1008 lines
29 KiB
C++
1008 lines
29 KiB
C++
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
|
||
|
// System
|
||
|
#include <string>
|
||
|
#include <stdio.h>
|
||
|
#include <errno.h>
|
||
|
#include <iostream>
|
||
|
#include <fstream>
|
||
|
#include <sstream>
|
||
|
#if defined(ANDROID)
|
||
|
# include "android-signal-defs.h"
|
||
|
#endif
|
||
|
|
||
|
// Profiler
|
||
|
#include "PlatformMacros.h"
|
||
|
#include "sps_sampler.h"
|
||
|
#include "platform.h"
|
||
|
#include "nsXULAppAPI.h"
|
||
|
#include "nsThreadUtils.h"
|
||
|
#include "prenv.h"
|
||
|
#include "shared-libraries.h"
|
||
|
#include "mozilla/StackWalk.h"
|
||
|
#include "ProfileEntry2.h"
|
||
|
#include "UnwinderThread2.h"
|
||
|
|
||
|
// JSON
|
||
|
#include "JSObjectBuilder.h"
|
||
|
#include "nsIJSRuntimeService.h"
|
||
|
|
||
|
// Meta
|
||
|
#include "nsXPCOM.h"
|
||
|
#include "nsXPCOMCID.h"
|
||
|
#include "nsIHttpProtocolHandler.h"
|
||
|
#include "nsServiceManagerUtils.h"
|
||
|
#include "nsIXULRuntime.h"
|
||
|
#include "nsIXULAppInfo.h"
|
||
|
#include "nsDirectoryServiceUtils.h"
|
||
|
#include "nsDirectoryServiceDefs.h"
|
||
|
#include "nsIObserverService.h"
|
||
|
#include "mozilla/Services.h"
|
||
|
|
||
|
// JS
|
||
|
#include "jsdbgapi.h"
|
||
|
|
||
|
// This file's exports are listed in sps_sampler.h.
|
||
|
|
||
|
// 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. The actual unwind mode currently in use is stored in
|
||
|
// sUnwindMode.
|
||
|
|
||
|
#undef HAVE_NATIVE_UNWIND
|
||
|
#if defined(MOZ_PROFILING) \
|
||
|
&& (defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||
|
|| defined(SPS_PLAT_x86_linux))
|
||
|
# define HAVE_NATIVE_UNWIND
|
||
|
#endif
|
||
|
|
||
|
#if 0 && defined(MOZ_PROFILING) && !defined(SPS_PLAT_x86_windows)
|
||
|
# warning MOZ_PROFILING
|
||
|
#endif
|
||
|
|
||
|
#if 0 && defined(HAVE_NATIVE_UNWIND)
|
||
|
# warning HAVE_NATIVE_UNWIND
|
||
|
#endif
|
||
|
|
||
|
typedef enum { UnwINVALID, UnwNATIVE, UnwPSEUDO, UnwCOMBINED } UnwMode;
|
||
|
|
||
|
/* These will be set to something sensible before we take the first
|
||
|
sample. */
|
||
|
static UnwMode sUnwindMode = UnwINVALID;
|
||
|
static int sUnwindInterval = 0;
|
||
|
|
||
|
|
||
|
using std::string;
|
||
|
using namespace mozilla;
|
||
|
|
||
|
#ifdef XP_WIN
|
||
|
#include <windows.h>
|
||
|
#define getpid GetCurrentProcessId
|
||
|
#else
|
||
|
#include <unistd.h>
|
||
|
#endif
|
||
|
|
||
|
#ifndef MAXPATHLEN
|
||
|
#ifdef PATH_MAX
|
||
|
#define MAXPATHLEN PATH_MAX
|
||
|
#elif defined(MAX_PATH)
|
||
|
#define MAXPATHLEN MAX_PATH
|
||
|
#elif defined(_MAX_PATH)
|
||
|
#define MAXPATHLEN _MAX_PATH
|
||
|
#elif defined(CCHMAXPATH)
|
||
|
#define MAXPATHLEN CCHMAXPATH
|
||
|
#else
|
||
|
#define MAXPATHLEN 1024
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#if _MSC_VER
|
||
|
#define snprintf _snprintf
|
||
|
#endif
|
||
|
|
||
|
class TableTicker2;
|
||
|
|
||
|
mozilla::ThreadLocal<PseudoStack *> tlsPseudoStack;
|
||
|
static mozilla::ThreadLocal<TableTicker2 *> tlsTicker;
|
||
|
// We need to track whether we've been initialized otherwise
|
||
|
// we end up using tlsStack without initializing it.
|
||
|
// Because tlsStack is totally opaque to us we can't reuse
|
||
|
// it as the flag itself.
|
||
|
bool stack_key_initialized;
|
||
|
|
||
|
static TimeStamp sLastTracerEvent; // is raced on
|
||
|
static int sFrameNumber = 0;
|
||
|
static int sLastFrameNumber = 0;
|
||
|
|
||
|
|
||
|
static bool
|
||
|
hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature)
|
||
|
{
|
||
|
for (size_t i = 0; i < aFeatureCount; i++) {
|
||
|
if (0) LOGF("hasFeature %s: %lu %s", aFeature, (unsigned long)i, aFeatures[i]);
|
||
|
if (strcmp(aFeatures[i], aFeature) == 0)
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
class TableTicker2: public Sampler {
|
||
|
public:
|
||
|
TableTicker2(int aInterval, int aEntrySize, PseudoStack *aStack,
|
||
|
const char** aFeatures, uint32_t aFeatureCount)
|
||
|
: Sampler(aInterval, true)
|
||
|
, mPrimaryThreadProfile(aEntrySize, aStack)
|
||
|
, mStartTime(TimeStamp::Now())
|
||
|
, mSaveRequested(false)
|
||
|
{
|
||
|
mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
|
||
|
|
||
|
//XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
|
||
|
mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
|
||
|
mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
|
||
|
mPrimaryThreadProfile.addTag(ProfileEntry2('m', "Start"));
|
||
|
}
|
||
|
|
||
|
~TableTicker2() { if (IsActive()) Stop(); }
|
||
|
|
||
|
virtual void SampleStack(TickSample* sample) {}
|
||
|
|
||
|
// Called within a signal. This function must be reentrant
|
||
|
virtual void Tick(TickSample* sample);
|
||
|
|
||
|
// Called within a signal. This function must be reentrant
|
||
|
virtual void RequestSave()
|
||
|
{
|
||
|
mSaveRequested = true;
|
||
|
}
|
||
|
|
||
|
virtual void HandleSaveRequest();
|
||
|
|
||
|
ThreadProfile2* GetPrimaryThreadProfile()
|
||
|
{
|
||
|
return &mPrimaryThreadProfile;
|
||
|
}
|
||
|
|
||
|
JSObject *ToJSObject(JSContext *aCx);
|
||
|
JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b);
|
||
|
|
||
|
const bool ProfileJS() { return mProfileJS; }
|
||
|
|
||
|
private:
|
||
|
// Unwind, using a "native" backtrace scheme (non-PseudoStack).
|
||
|
// Not implemented on platforms which do not support native backtraces
|
||
|
void doNativeBacktrace(ThreadProfile2 &aProfile, TickSample* aSample);
|
||
|
|
||
|
private:
|
||
|
// This represent the application's main thread (SAMPLER_INIT)
|
||
|
ThreadProfile2 mPrimaryThreadProfile;
|
||
|
TimeStamp mStartTime;
|
||
|
bool mSaveRequested;
|
||
|
bool mUseStackWalk;
|
||
|
bool mJankOnly;
|
||
|
bool mProfileJS;
|
||
|
};
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// BEGIN SaveProfileTask et al
|
||
|
|
||
|
std::string GetSharedLibraryInfoString();
|
||
|
|
||
|
static JSBool
|
||
|
WriteCallback(const jschar *buf, uint32_t len, void *data)
|
||
|
{
|
||
|
std::ofstream& stream = *static_cast<std::ofstream*>(data);
|
||
|
nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
|
||
|
stream << profile.Data();
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is an event used to save the profile on the main thread
|
||
|
* to be sure that it is not being modified while saving.
|
||
|
*/
|
||
|
class SaveProfileTask : public nsRunnable {
|
||
|
public:
|
||
|
SaveProfileTask() {}
|
||
|
|
||
|
NS_IMETHOD Run() {
|
||
|
TableTicker2 *t = tlsTicker.get();
|
||
|
|
||
|
// Pause the profiler during saving.
|
||
|
// This will prevent us from recording sampling
|
||
|
// regarding profile saving. This will also
|
||
|
// prevent bugs caused by the circular buffer not
|
||
|
// being thread safe. Bug 750989.
|
||
|
t->SetPaused(true);
|
||
|
|
||
|
// Get file path
|
||
|
# if defined(SPS_PLAT_arm_android)
|
||
|
nsCString tmpPath;
|
||
|
tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||
|
# else
|
||
|
nsCOMPtr<nsIFile> tmpFile;
|
||
|
nsAutoCString tmpPath;
|
||
|
if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
|
||
|
LOG("Failed to find temporary directory.");
|
||
|
return NS_ERROR_FAILURE;
|
||
|
}
|
||
|
tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||
|
|
||
|
nsresult rv = tmpFile->AppendNative(tmpPath);
|
||
|
if (NS_FAILED(rv))
|
||
|
return rv;
|
||
|
|
||
|
rv = tmpFile->GetNativePath(tmpPath);
|
||
|
if (NS_FAILED(rv))
|
||
|
return rv;
|
||
|
# endif
|
||
|
|
||
|
// Create a JSContext to run a JSObjectBuilder :(
|
||
|
// Based on XPCShellEnvironment
|
||
|
JSRuntime *rt;
|
||
|
JSContext *cx;
|
||
|
nsCOMPtr<nsIJSRuntimeService> rtsvc
|
||
|
= do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
|
||
|
if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
|
||
|
LOG("failed to get RuntimeService");
|
||
|
return NS_ERROR_FAILURE;;
|
||
|
}
|
||
|
|
||
|
cx = JS_NewContext(rt, 8192);
|
||
|
if (!cx) {
|
||
|
LOG("Failed to get context");
|
||
|
return NS_ERROR_FAILURE;
|
||
|
}
|
||
|
|
||
|
{
|
||
|
JSAutoRequest ar(cx);
|
||
|
static JSClass c = {
|
||
|
"global", JSCLASS_GLOBAL_FLAGS,
|
||
|
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
|
||
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
|
||
|
};
|
||
|
JSObject *obj = JS_NewGlobalObject(cx, &c, NULL);
|
||
|
|
||
|
std::ofstream stream;
|
||
|
stream.open(tmpPath.get());
|
||
|
// Pause the profiler during saving.
|
||
|
// This will prevent us from recording sampling
|
||
|
// regarding profile saving. This will also
|
||
|
// prevent bugs caused by the circular buffer not
|
||
|
// being thread safe. Bug 750989.
|
||
|
t->SetPaused(true);
|
||
|
if (stream.is_open()) {
|
||
|
JSAutoCompartment autoComp(cx, obj);
|
||
|
JSObject* profileObj = mozilla_sampler_get_profile_data2(cx);
|
||
|
jsval val = OBJECT_TO_JSVAL(profileObj);
|
||
|
JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
|
||
|
stream.close();
|
||
|
LOGF("Saved to %s", tmpPath.get());
|
||
|
} else {
|
||
|
LOG("Fail to open profile log file.");
|
||
|
}
|
||
|
}
|
||
|
JS_EndRequest(cx);
|
||
|
JS_DestroyContext(cx);
|
||
|
|
||
|
t->SetPaused(false);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void TableTicker2::HandleSaveRequest()
|
||
|
{
|
||
|
if (!mSaveRequested)
|
||
|
return;
|
||
|
mSaveRequested = false;
|
||
|
|
||
|
// TODO: Use use the ipc/chromium Tasks here to support processes
|
||
|
// without XPCOM.
|
||
|
nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask();
|
||
|
NS_DispatchToMainThread(runnable);
|
||
|
}
|
||
|
|
||
|
JSCustomObject* TableTicker2::GetMetaJSCustomObject(JSAObjectBuilder& b)
|
||
|
{
|
||
|
JSCustomObject *meta = b.CreateObject();
|
||
|
|
||
|
b.DefineProperty(meta, "version", 2);
|
||
|
b.DefineProperty(meta, "interval", interval());
|
||
|
b.DefineProperty(meta, "stackwalk", mUseStackWalk);
|
||
|
b.DefineProperty(meta, "jank", mJankOnly);
|
||
|
b.DefineProperty(meta, "processType", XRE_GetProcessType());
|
||
|
|
||
|
nsresult res;
|
||
|
nsCOMPtr<nsIHttpProtocolHandler> http
|
||
|
= do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
|
||
|
if (!NS_FAILED(res)) {
|
||
|
nsAutoCString string;
|
||
|
|
||
|
res = http->GetPlatform(string);
|
||
|
if (!NS_FAILED(res))
|
||
|
b.DefineProperty(meta, "platform", string.Data());
|
||
|
|
||
|
res = http->GetOscpu(string);
|
||
|
if (!NS_FAILED(res))
|
||
|
b.DefineProperty(meta, "oscpu", string.Data());
|
||
|
|
||
|
res = http->GetMisc(string);
|
||
|
if (!NS_FAILED(res))
|
||
|
b.DefineProperty(meta, "misc", string.Data());
|
||
|
}
|
||
|
|
||
|
nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
|
||
|
if (runtime) {
|
||
|
nsAutoCString string;
|
||
|
|
||
|
res = runtime->GetXPCOMABI(string);
|
||
|
if (!NS_FAILED(res))
|
||
|
b.DefineProperty(meta, "abi", string.Data());
|
||
|
|
||
|
res = runtime->GetWidgetToolkit(string);
|
||
|
if (!NS_FAILED(res))
|
||
|
b.DefineProperty(meta, "toolkit", string.Data());
|
||
|
}
|
||
|
|
||
|
nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
|
||
|
if (appInfo) {
|
||
|
nsAutoCString string;
|
||
|
|
||
|
res = appInfo->GetName(string);
|
||
|
if (!NS_FAILED(res))
|
||
|
b.DefineProperty(meta, "product", string.Data());
|
||
|
}
|
||
|
|
||
|
return meta;
|
||
|
}
|
||
|
|
||
|
JSObject* TableTicker2::ToJSObject(JSContext *aCx)
|
||
|
{
|
||
|
JSObjectBuilder b(aCx);
|
||
|
|
||
|
JSCustomObject *profile = b.CreateObject();
|
||
|
|
||
|
// Put shared library info
|
||
|
b.DefineProperty(profile, "libs", GetSharedLibraryInfoString().c_str());
|
||
|
|
||
|
// Put meta data
|
||
|
JSCustomObject *meta = GetMetaJSCustomObject(b);
|
||
|
b.DefineProperty(profile, "meta", meta);
|
||
|
|
||
|
// Lists the samples for each ThreadProfile2
|
||
|
JSCustomArray *threads = b.CreateArray();
|
||
|
b.DefineProperty(profile, "threads", threads);
|
||
|
|
||
|
// For now we only have one thread
|
||
|
SetPaused(true);
|
||
|
ThreadProfile2* prof = GetPrimaryThreadProfile();
|
||
|
prof->GetMutex()->Lock();
|
||
|
JSCustomObject* threadSamples = prof->ToJSObject(aCx);
|
||
|
b.ArrayPush(threads, threadSamples);
|
||
|
prof->GetMutex()->Unlock();
|
||
|
SetPaused(false);
|
||
|
|
||
|
return b.GetJSObject(profile);
|
||
|
}
|
||
|
|
||
|
// END SaveProfileTask et al
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// BEGIN take samples.
|
||
|
// Everything in this section RUNS IN SIGHANDLER CONTEXT
|
||
|
|
||
|
// RUNS IN SIGHANDLER CONTEXT
|
||
|
static
|
||
|
void genProfileEntry2(/*MODIFIED*/UnwinderThreadBuffer* utb,
|
||
|
volatile StackEntry &entry,
|
||
|
PseudoStack *stack, void *lastpc)
|
||
|
{
|
||
|
int lineno = -1;
|
||
|
|
||
|
// Add a pseudostack-entry start label
|
||
|
utb__addEntry( utb, ProfileEntry2('h', 'P') );
|
||
|
// And the SP value, if it is non-zero
|
||
|
if (entry.stackAddress() != 0) {
|
||
|
utb__addEntry( utb, ProfileEntry2('S', entry.stackAddress()) );
|
||
|
}
|
||
|
|
||
|
// First entry has tagName 's' (start)
|
||
|
// Check for magic pointer bit 1 to indicate copy
|
||
|
const char* sampleLabel = entry.label();
|
||
|
if (entry.isCopyLabel()) {
|
||
|
// Store the string using 1 or more 'd' (dynamic) tags
|
||
|
// that will happen to the preceding tag
|
||
|
|
||
|
utb__addEntry( utb, ProfileEntry2('c', "") );
|
||
|
// Add one to store the null termination
|
||
|
size_t strLen = strlen(sampleLabel) + 1;
|
||
|
for (size_t j = 0; j < strLen;) {
|
||
|
// Store as many characters in the void* as the platform allows
|
||
|
char text[sizeof(void*)];
|
||
|
for (size_t pos = 0; pos < sizeof(void*) && j+pos < strLen; pos++) {
|
||
|
text[pos] = sampleLabel[j+pos];
|
||
|
}
|
||
|
j += sizeof(void*)/sizeof(char);
|
||
|
// Cast to *((void**) to pass the text data to a void*
|
||
|
utb__addEntry( utb, ProfileEntry2('d', *((void**)(&text[0]))) );
|
||
|
}
|
||
|
if (entry.js()) {
|
||
|
if (!entry.pc()) {
|
||
|
// The JIT only allows the top-most entry to have a NULL pc
|
||
|
MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]);
|
||
|
// If stack-walking was disabled, then that's just unfortunate
|
||
|
if (lastpc) {
|
||
|
jsbytecode *jspc = js::ProfilingGetPC(stack->mRuntime, entry.script(),
|
||
|
lastpc);
|
||
|
if (jspc) {
|
||
|
lineno = JS_PCToLineNumber(NULL, entry.script(), jspc);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
lineno = JS_PCToLineNumber(NULL, entry.script(), entry.pc());
|
||
|
}
|
||
|
} else {
|
||
|
lineno = entry.line();
|
||
|
}
|
||
|
} else {
|
||
|
utb__addEntry( utb, ProfileEntry2('c', sampleLabel) );
|
||
|
lineno = entry.line();
|
||
|
}
|
||
|
if (lineno != -1) {
|
||
|
utb__addEntry( utb, ProfileEntry2('n', lineno) );
|
||
|
}
|
||
|
|
||
|
// Add a pseudostack-entry end label
|
||
|
utb__addEntry( utb, ProfileEntry2('h', 'Q') );
|
||
|
}
|
||
|
|
||
|
// RUNS IN SIGHANDLER CONTEXT
|
||
|
// Generate pseudo-backtrace entries and put them in |utb|, with
|
||
|
// the order outermost frame first.
|
||
|
void genPseudoBacktraceEntries(/*MODIFIED*/UnwinderThreadBuffer* utb,
|
||
|
PseudoStack *aStack, TickSample *sample)
|
||
|
{
|
||
|
// Call genProfileEntry2 to generate tags for each profile
|
||
|
// entry. Each entry will be bounded by a 'h' 'P' tag to
|
||
|
// mark the start and a 'h' 'Q' tag to mark the end.
|
||
|
uint32_t nInStack = aStack->stackSize();
|
||
|
for (uint32_t i = 0; i < nInStack; i++) {
|
||
|
genProfileEntry2(utb, aStack->mStack[i], aStack, nullptr);
|
||
|
}
|
||
|
# ifdef ENABLE_SPS_LEAF_DATA
|
||
|
if (sample) {
|
||
|
utb__addEntry( utb, ProfileEntry2('l', (void*)sample->pc) );
|
||
|
# ifdef ENABLE_ARM_LR_SAVING
|
||
|
utb__addEntry( utb, ProfileEntry2('L', (void*)sample->lr) );
|
||
|
# endif
|
||
|
}
|
||
|
# endif
|
||
|
}
|
||
|
|
||
|
/* used to keep track of the last event that we sampled during */
|
||
|
static unsigned int sLastSampledEventGeneration = 0;
|
||
|
|
||
|
/* a counter that's incremented everytime we get responsiveness event
|
||
|
* note: it might also be worth tracking everytime we go around
|
||
|
* the event loop */
|
||
|
static unsigned int sCurrentEventGeneration = 0;
|
||
|
/* we don't need to worry about overflow because we only treat the
|
||
|
* case of them being the same as special. i.e. we only run into
|
||
|
* a problem if 2^32 events happen between samples that we need
|
||
|
* to know are associated with different events */
|
||
|
|
||
|
// RUNS IN SIGHANDLER CONTEXT
|
||
|
void TableTicker2::Tick(TickSample* sample)
|
||
|
{
|
||
|
/* Get hold of an empty inter-thread buffer into which to park
|
||
|
the ProfileEntries for this sample. */
|
||
|
UnwinderThreadBuffer* utb = uwt__acquire_empty_buffer();
|
||
|
|
||
|
/* This could fail, if no buffers are currently available, in which
|
||
|
case we must give up right away. We cannot wait for a buffer to
|
||
|
become available, as that risks deadlock. */
|
||
|
if (!utb)
|
||
|
return;
|
||
|
|
||
|
/* Manufacture the ProfileEntries that we will give to the unwinder
|
||
|
thread, and park them in |utb|. */
|
||
|
|
||
|
// Marker(s) come before the sample
|
||
|
PseudoStack* stack = mPrimaryThreadProfile.GetPseudoStack();
|
||
|
for (int i = 0; stack->getMarker(i) != NULL; i++) {
|
||
|
utb__addEntry( utb, ProfileEntry2('m', stack->getMarker(i)) );
|
||
|
}
|
||
|
stack->mQueueClearMarker = true;
|
||
|
|
||
|
bool recordSample = true;
|
||
|
if (mJankOnly) {
|
||
|
// if we are on a different event we can discard any temporary samples
|
||
|
// we've kept around
|
||
|
if (sLastSampledEventGeneration != sCurrentEventGeneration) {
|
||
|
// XXX: we also probably want to add an entry to the profile to help
|
||
|
// distinguish which samples are part of the same event. That, or record
|
||
|
// the event generation in each sample
|
||
|
mPrimaryThreadProfile.erase();
|
||
|
}
|
||
|
sLastSampledEventGeneration = sCurrentEventGeneration;
|
||
|
|
||
|
recordSample = false;
|
||
|
// only record the events when we have a we haven't seen a tracer
|
||
|
// event for 100ms
|
||
|
if (!sLastTracerEvent.IsNull()) {
|
||
|
TimeDuration delta = sample->timestamp - sLastTracerEvent;
|
||
|
if (delta.ToMilliseconds() > 100.0) {
|
||
|
recordSample = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// JRS 2012-Sept-27: this logic used to involve mUseStackWalk.
|
||
|
// That should be reinstated, but for the moment, use the
|
||
|
// settings in sUnwindMode and sUnwindInterval.
|
||
|
// Add a native-backtrace request, or add pseudo backtrace entries,
|
||
|
// or both.
|
||
|
switch (sUnwindMode) {
|
||
|
case UnwNATIVE: /* Native only */
|
||
|
// add a "do native stack trace now" hint. This will be actioned
|
||
|
// by the unwinder thread as it processes the entries in this
|
||
|
// sample.
|
||
|
utb__addEntry( utb, ProfileEntry2('h'/*hint*/, 'N'/*native-trace*/) );
|
||
|
break;
|
||
|
case UnwPSEUDO: /* Pseudo only */
|
||
|
/* Add into |utb|, the pseudo backtrace entries */
|
||
|
genPseudoBacktraceEntries(utb, stack, sample);
|
||
|
break;
|
||
|
case UnwCOMBINED: /* Both Native and Pseudo */
|
||
|
utb__addEntry( utb, ProfileEntry2('h'/*hint*/, 'N'/*native-trace*/) );
|
||
|
genPseudoBacktraceEntries(utb, stack, sample);
|
||
|
break;
|
||
|
case UnwINVALID:
|
||
|
default:
|
||
|
MOZ_CRASH();
|
||
|
}
|
||
|
|
||
|
if (recordSample) {
|
||
|
// add a "flush now" hint
|
||
|
utb__addEntry( utb, ProfileEntry2('h'/*hint*/, 'F'/*flush*/) );
|
||
|
}
|
||
|
|
||
|
// Add any extras
|
||
|
if (!sLastTracerEvent.IsNull() && sample) {
|
||
|
TimeDuration delta = sample->timestamp - sLastTracerEvent;
|
||
|
utb__addEntry( utb, ProfileEntry2('r', delta.ToMilliseconds()) );
|
||
|
}
|
||
|
|
||
|
if (sample) {
|
||
|
TimeDuration delta = sample->timestamp - mStartTime;
|
||
|
utb__addEntry( utb, ProfileEntry2('t', delta.ToMilliseconds()) );
|
||
|
}
|
||
|
|
||
|
if (sLastFrameNumber != sFrameNumber) {
|
||
|
utb__addEntry( utb, ProfileEntry2('f', sFrameNumber) );
|
||
|
sLastFrameNumber = sFrameNumber;
|
||
|
}
|
||
|
|
||
|
/* So now we have, in |utb|, the complete set of entries we want to
|
||
|
push into the circular buffer. This may also include a 'h' 'F'
|
||
|
entry, which is "flush now" hint, and/or a 'h' 'N' entry, which
|
||
|
is a "generate a native backtrace and add it to the buffer right
|
||
|
now" hint. Hand them off to the helper thread, together with
|
||
|
stack and register context needed to do a native unwind, if that
|
||
|
is currently enabled. */
|
||
|
|
||
|
/* If a native unwind has been requested, we'll start it off using
|
||
|
the context obtained from the signal handler, to avoid the
|
||
|
problem of having to unwind through the signal frame itself. */
|
||
|
|
||
|
/* On Linux and Android, the initial register state is in the
|
||
|
supplied sample->context. But on MacOS it's not, so we have to
|
||
|
fake it up here (sigh). */
|
||
|
if (sUnwindMode == UnwNATIVE || sUnwindMode == UnwCOMBINED) {
|
||
|
# if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||
|
|| defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
|
||
|
void* ucV = (void*)sample->context;
|
||
|
# elif defined(SPS_PLAT_amd64_darwin)
|
||
|
struct __darwin_mcontext64 mc;
|
||
|
memset(&mc, 0, sizeof(mc));
|
||
|
ucontext_t uc;
|
||
|
memset(&uc, 0, sizeof(uc));
|
||
|
uc.uc_mcontext = &mc;
|
||
|
mc.__ss.__rip = (uint64_t)sample->pc;
|
||
|
mc.__ss.__rsp = (uint64_t)sample->sp;
|
||
|
mc.__ss.__rbp = (uint64_t)sample->fp;
|
||
|
void* ucV = (void*)&uc;
|
||
|
# elif defined(SPS_PLAT_x86_darwin)
|
||
|
struct __darwin_mcontext32 mc;
|
||
|
memset(&mc, 0, sizeof(mc));
|
||
|
ucontext_t uc;
|
||
|
memset(&uc, 0, sizeof(uc));
|
||
|
uc.uc_mcontext = &mc;
|
||
|
mc.__ss.__eip = (uint32_t)sample->pc;
|
||
|
mc.__ss.__esp = (uint32_t)sample->sp;
|
||
|
mc.__ss.__ebp = (uint32_t)sample->fp;
|
||
|
void* ucV = (void*)&uc;
|
||
|
# elif defined(SPS_OS_windows)
|
||
|
/* Totally fake this up so it at least builds. No idea if we can
|
||
|
even ever get here on Windows. */
|
||
|
void* ucV = NULL;
|
||
|
# else
|
||
|
# error "Unsupported platform"
|
||
|
# endif
|
||
|
uwt__release_full_buffer(&mPrimaryThreadProfile, utb, ucV);
|
||
|
} else {
|
||
|
uwt__release_full_buffer(&mPrimaryThreadProfile, utb, NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// END take samples
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
std::ostream& operator<<(std::ostream& stream, const ThreadProfile2& profile)
|
||
|
{
|
||
|
int readPos = profile.mReadPos;
|
||
|
while (readPos != profile.mLastFlushPos) {
|
||
|
stream << profile.mEntries[readPos];
|
||
|
readPos = (readPos + 1) % profile.mEntrySize;
|
||
|
}
|
||
|
return stream;
|
||
|
}
|
||
|
|
||
|
std::ostream& operator<<(std::ostream& stream, const ProfileEntry2& 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;
|
||
|
}
|
||
|
|
||
|
static const char* name_UnwMode(UnwMode m)
|
||
|
{
|
||
|
switch (m) {
|
||
|
case UnwINVALID: return "invalid";
|
||
|
case UnwNATIVE: return "native";
|
||
|
case UnwPSEUDO: return "pseudo";
|
||
|
case UnwCOMBINED: return "combined";
|
||
|
default: return "??name_UnwMode??";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Read env vars at startup, so as to set sUnwindMode and sInterval.
|
||
|
static void read_env_vars()
|
||
|
{
|
||
|
bool nativeAvail = false;
|
||
|
# if defined(HAVE_NATIVE_UNWIND)
|
||
|
nativeAvail = true;
|
||
|
# endif
|
||
|
|
||
|
MOZ_ASSERT(sUnwindMode == UnwINVALID);
|
||
|
MOZ_ASSERT(sUnwindInterval == 0);
|
||
|
|
||
|
/* Set defaults */
|
||
|
sUnwindMode = nativeAvail ? UnwCOMBINED : UnwPSEUDO;
|
||
|
sUnwindInterval = 0; /* We'll have to look elsewhere */
|
||
|
|
||
|
const char* strM = PR_GetEnv("MOZ_PROFILER_MODE");
|
||
|
const char* strI = PR_GetEnv("MOZ_PROFILER_INTERVAL");
|
||
|
|
||
|
if (strM) {
|
||
|
if (0 == strcmp(strM, "pseudo"))
|
||
|
sUnwindMode = UnwPSEUDO;
|
||
|
else if (0 == strcmp(strM, "native") && nativeAvail)
|
||
|
sUnwindMode = UnwNATIVE;
|
||
|
else if (0 == strcmp(strM, "combined") && nativeAvail)
|
||
|
sUnwindMode = UnwCOMBINED;
|
||
|
else goto usage;
|
||
|
}
|
||
|
|
||
|
if (strI) {
|
||
|
errno = 0;
|
||
|
long int n = strtol(strI, (char**)NULL, 10);
|
||
|
if (errno == 0 && n >= 1 && n <= 1000) {
|
||
|
sUnwindInterval = n;
|
||
|
}
|
||
|
else goto usage;
|
||
|
}
|
||
|
|
||
|
goto out;
|
||
|
|
||
|
usage:
|
||
|
LOG( "SPS: ");
|
||
|
LOG( "SPS: Environment variable usage:");
|
||
|
LOG( "SPS: ");
|
||
|
LOG( "SPS: MOZ_PROFILER_MODE=native for native unwind only");
|
||
|
LOG( "SPS: MOZ_PROFILER_MODE=pseudo for pseudo unwind only");
|
||
|
LOG( "SPS: MOZ_PROFILER_MODE=combined for combined native & pseudo unwind");
|
||
|
LOG( "SPS: If unset, default is 'combined' on native-capable");
|
||
|
LOG( "SPS: platforms, 'pseudo' on others.");
|
||
|
LOG( "SPS: ");
|
||
|
LOG( "SPS: MOZ_PROFILER_INTERVAL=<number> (milliseconds, 1 to 1000)");
|
||
|
LOG( "SPS: If unset, platform default is used.");
|
||
|
LOG( "SPS: ");
|
||
|
LOGF("SPS: This platform %s native unwinding.",
|
||
|
nativeAvail ? "supports" : "does not support");
|
||
|
LOG( "SPS: ");
|
||
|
/* Re-set defaults */
|
||
|
sUnwindMode = nativeAvail ? UnwCOMBINED : UnwPSEUDO;
|
||
|
sUnwindInterval = 0; /* We'll have to look elsewhere */
|
||
|
|
||
|
out:
|
||
|
LOG( "SPS:");
|
||
|
LOGF("SPS: Unwind mode = %s", name_UnwMode(sUnwindMode));
|
||
|
LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")",
|
||
|
(int)sUnwindInterval);
|
||
|
LOG( "SPS: Use env var MOZ_PROFILER_MODE=help for further information.");
|
||
|
LOG( "SPS:");
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// BEGIN externally visible functions
|
||
|
|
||
|
void mozilla_sampler_init2()
|
||
|
{
|
||
|
if (stack_key_initialized)
|
||
|
return;
|
||
|
|
||
|
LOG("BEGIN mozilla_sampler_init2");
|
||
|
if (!tlsPseudoStack.init() || !tlsTicker.init()) {
|
||
|
LOG("Failed to init.");
|
||
|
return;
|
||
|
}
|
||
|
stack_key_initialized = true;
|
||
|
|
||
|
PseudoStack *stack = new PseudoStack();
|
||
|
tlsPseudoStack.set(stack);
|
||
|
|
||
|
// Read mode settings from MOZ_PROFILER_MODE and interval
|
||
|
// settings from MOZ_PROFILER_INTERVAL.
|
||
|
read_env_vars();
|
||
|
|
||
|
// Create the unwinder thread. ATM there is only one.
|
||
|
uwt__init();
|
||
|
|
||
|
# if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||
|
|| defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android) \
|
||
|
|| defined(SPS_PLAT_x86_windows) || defined(SPS_PLAT_amd64_windows) /* no idea if windows is correct */
|
||
|
// On Linuxes, register this thread (temporarily) for profiling
|
||
|
int aLocal;
|
||
|
uwt__register_thread_for_profiling( &aLocal );
|
||
|
# elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin)
|
||
|
// Registration is done in platform-macos.cc
|
||
|
# else
|
||
|
# error "Unknown plat"
|
||
|
# endif
|
||
|
|
||
|
// Allow the profiler to be started using signals
|
||
|
OS::RegisterStartHandler();
|
||
|
|
||
|
// We can't open pref so we use an environment variable
|
||
|
// to know if we should trigger the profiler on startup
|
||
|
// NOTE: Default
|
||
|
const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP");
|
||
|
if (!val || !*val) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const char* features[] = {"js"
|
||
|
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||
|
, "stackwalk"
|
||
|
#endif
|
||
|
};
|
||
|
mozilla_sampler_start2(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
|
||
|
features, sizeof(features)/sizeof(const char*));
|
||
|
LOG("END mozilla_sampler_init2");
|
||
|
}
|
||
|
|
||
|
void mozilla_sampler_shutdown2()
|
||
|
{
|
||
|
// Shut down and reap the unwinder thread. We have to do this
|
||
|
// before stopping the sampler, so as to guarantee that the unwinder
|
||
|
// thread doesn't try to access memory that the subsequent call to
|
||
|
// mozilla_sampler_stop2 causes to be freed.
|
||
|
uwt__deinit();
|
||
|
|
||
|
mozilla_sampler_stop2();
|
||
|
// We can't delete the Stack because we can be between a
|
||
|
// sampler call_enter/call_exit point.
|
||
|
// TODO Need to find a safe time to delete Stack
|
||
|
}
|
||
|
|
||
|
void mozilla_sampler_save2()
|
||
|
{
|
||
|
TableTicker2 *t = tlsTicker.get();
|
||
|
if (!t) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
t->RequestSave();
|
||
|
// We're on the main thread already so we don't
|
||
|
// have to wait to handle the save request.
|
||
|
t->HandleSaveRequest();
|
||
|
}
|
||
|
|
||
|
char* mozilla_sampler_get_profile2()
|
||
|
{
|
||
|
TableTicker2 *t = tlsTicker.get();
|
||
|
if (!t) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
std::stringstream profile;
|
||
|
t->SetPaused(true);
|
||
|
profile << *(t->GetPrimaryThreadProfile());
|
||
|
t->SetPaused(false);
|
||
|
|
||
|
std::string profileString = profile.str();
|
||
|
char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) );
|
||
|
strcpy(rtn, profileString.c_str());
|
||
|
return rtn;
|
||
|
}
|
||
|
|
||
|
JSObject *mozilla_sampler_get_profile_data2(JSContext *aCx)
|
||
|
{
|
||
|
TableTicker2 *t = tlsTicker.get();
|
||
|
if (!t) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return t->ToJSObject(aCx);
|
||
|
}
|
||
|
|
||
|
|
||
|
const char** mozilla_sampler_get_features2()
|
||
|
{
|
||
|
static const char* features[] = {
|
||
|
#if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND)
|
||
|
"stackwalk",
|
||
|
#endif
|
||
|
"jank",
|
||
|
"js",
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
return features;
|
||
|
}
|
||
|
|
||
|
// Values are only honored on the first start
|
||
|
void mozilla_sampler_start2(int aProfileEntries, int aInterval,
|
||
|
const char** aFeatures, uint32_t aFeatureCount)
|
||
|
{
|
||
|
if (!stack_key_initialized)
|
||
|
mozilla_sampler_init2();
|
||
|
|
||
|
/* If the sampling interval was set using env vars, use that
|
||
|
in preference to anything else. */
|
||
|
if (sUnwindInterval > 0)
|
||
|
aInterval = sUnwindInterval;
|
||
|
|
||
|
PseudoStack *stack = tlsPseudoStack.get();
|
||
|
if (!stack) {
|
||
|
ASSERT(false);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mozilla_sampler_stop2();
|
||
|
TableTicker2 *t
|
||
|
= new TableTicker2(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
|
||
|
aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
|
||
|
stack, aFeatures, aFeatureCount);
|
||
|
tlsTicker.set(t);
|
||
|
t->Start();
|
||
|
if (t->ProfileJS())
|
||
|
stack->enableJSSampling();
|
||
|
|
||
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||
|
if (os)
|
||
|
os->NotifyObservers(nullptr, "profiler-started", nullptr);
|
||
|
}
|
||
|
|
||
|
void mozilla_sampler_stop2()
|
||
|
{
|
||
|
if (!stack_key_initialized)
|
||
|
mozilla_sampler_init2();
|
||
|
|
||
|
TableTicker2 *t = tlsTicker.get();
|
||
|
if (!t) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool disableJS = t->ProfileJS();
|
||
|
|
||
|
t->Stop();
|
||
|
delete t;
|
||
|
tlsTicker.set(NULL);
|
||
|
PseudoStack *stack = tlsPseudoStack.get();
|
||
|
ASSERT(stack != NULL);
|
||
|
|
||
|
if (disableJS)
|
||
|
stack->disableJSSampling();
|
||
|
|
||
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||
|
if (os)
|
||
|
os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
|
||
|
}
|
||
|
|
||
|
bool mozilla_sampler_is_active2()
|
||
|
{
|
||
|
if (!stack_key_initialized)
|
||
|
mozilla_sampler_init2();
|
||
|
|
||
|
TableTicker2 *t = tlsTicker.get();
|
||
|
if (!t) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return t->IsActive();
|
||
|
}
|
||
|
|
||
|
static double sResponsivenessTimes[100];
|
||
|
static unsigned int sResponsivenessLoc = 0;
|
||
|
void mozilla_sampler_responsiveness2(TimeStamp aTime)
|
||
|
{
|
||
|
if (!sLastTracerEvent.IsNull()) {
|
||
|
if (sResponsivenessLoc == 100) {
|
||
|
for(size_t i = 0; i < 100-1; i++) {
|
||
|
sResponsivenessTimes[i] = sResponsivenessTimes[i+1];
|
||
|
}
|
||
|
sResponsivenessLoc--;
|
||
|
}
|
||
|
TimeDuration delta = aTime - sLastTracerEvent;
|
||
|
sResponsivenessTimes[sResponsivenessLoc++] = delta.ToMilliseconds();
|
||
|
}
|
||
|
sCurrentEventGeneration++;
|
||
|
|
||
|
sLastTracerEvent = aTime;
|
||
|
}
|
||
|
|
||
|
const double* mozilla_sampler_get_responsiveness2()
|
||
|
{
|
||
|
return sResponsivenessTimes;
|
||
|
}
|
||
|
|
||
|
void mozilla_sampler_frame_number2(int frameNumber)
|
||
|
{
|
||
|
sFrameNumber = frameNumber;
|
||
|
}
|
||
|
|
||
|
void mozilla_sampler_print_location2()
|
||
|
{
|
||
|
// FIXME
|
||
|
}
|
||
|
|
||
|
void mozilla_sampler_lock2()
|
||
|
{
|
||
|
// FIXME!
|
||
|
}
|
||
|
|
||
|
void mozilla_sampler_unlock2()
|
||
|
{
|
||
|
// FIXME!
|
||
|
}
|
||
|
|
||
|
// END externally visible functions
|
||
|
////////////////////////////////////////////////////////////////////////
|