2011-08-26 17:05:37 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
2012-05-21 04:12:37 -07:00
|
|
|
/* 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/. */
|
2011-08-26 17:05:37 -07:00
|
|
|
|
2011-12-07 11:48:15 -08:00
|
|
|
#include <string>
|
2011-08-26 17:05:37 -07:00
|
|
|
#include <stdio.h>
|
2012-03-23 12:09:27 -07:00
|
|
|
#include <fstream>
|
|
|
|
#include <sstream>
|
2013-09-27 09:08:45 -07:00
|
|
|
#include "GeckoProfiler.h"
|
2013-03-25 14:57:28 -07:00
|
|
|
#include "SaveProfileTask.h"
|
|
|
|
#include "ProfileEntry.h"
|
2013-09-25 08:28:34 -07:00
|
|
|
#include "SyncProfile.h"
|
2011-08-26 17:05:37 -07:00
|
|
|
#include "platform.h"
|
|
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include "prenv.h"
|
2013-09-23 10:29:27 -07:00
|
|
|
#include "prtime.h"
|
2011-12-15 04:31:41 -08:00
|
|
|
#include "shared-libraries.h"
|
2012-02-03 12:19:18 -08:00
|
|
|
#include "mozilla/StackWalk.h"
|
2013-03-25 14:57:28 -07:00
|
|
|
#include "TableTicker.h"
|
2013-03-21 02:17:23 -07:00
|
|
|
#include "nsXULAppAPI.h"
|
2012-07-11 12:36:04 -07:00
|
|
|
|
|
|
|
// JSON
|
2014-04-21 13:48:47 -07:00
|
|
|
#include "JSStreamWriter.h"
|
2011-08-26 17:05:37 -07:00
|
|
|
|
2012-06-26 20:43:28 -07:00
|
|
|
// Meta
|
|
|
|
#include "nsXPCOM.h"
|
|
|
|
#include "nsXPCOMCID.h"
|
|
|
|
#include "nsIHttpProtocolHandler.h"
|
|
|
|
#include "nsServiceManagerUtils.h"
|
|
|
|
#include "nsIXULRuntime.h"
|
|
|
|
#include "nsIXULAppInfo.h"
|
2012-07-10 00:26:57 -07:00
|
|
|
#include "nsDirectoryServiceUtils.h"
|
|
|
|
#include "nsDirectoryServiceDefs.h"
|
2012-09-20 10:36:50 -07:00
|
|
|
#include "nsIObserverService.h"
|
|
|
|
#include "mozilla/Services.h"
|
2013-03-10 15:00:23 -07:00
|
|
|
#include "PlatformMacros.h"
|
2012-06-26 20:43:28 -07:00
|
|
|
|
2013-04-23 11:33:38 -07:00
|
|
|
#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
2013-04-23 10:10:29 -07:00
|
|
|
#include "AndroidBridge.h"
|
|
|
|
#endif
|
|
|
|
|
2012-08-06 15:58:47 -07:00
|
|
|
// JS
|
2013-08-26 19:05:20 -07:00
|
|
|
#include "js/OldDebugAPI.h"
|
2012-08-06 15:58:47 -07:00
|
|
|
|
2012-02-03 12:19:18 -08:00
|
|
|
#if defined(MOZ_PROFILING) && (defined(XP_MACOSX) || defined(XP_WIN))
|
2012-01-16 16:59:15 -08:00
|
|
|
#define USE_NS_STACKWALK
|
|
|
|
#endif
|
|
|
|
#ifdef USE_NS_STACKWALK
|
|
|
|
#include "nsStackWalk.h"
|
|
|
|
#endif
|
|
|
|
|
2013-09-25 08:28:34 -07:00
|
|
|
#if defined(XP_WIN)
|
|
|
|
typedef CONTEXT tickcontext_t;
|
|
|
|
#elif defined(LINUX)
|
|
|
|
#include <ucontext.h>
|
|
|
|
typedef ucontext_t tickcontext_t;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(LINUX) || defined(XP_MACOSX)
|
|
|
|
#include <sys/types.h>
|
|
|
|
pid_t gettid();
|
|
|
|
#endif
|
|
|
|
|
2013-09-11 11:53:14 -07:00
|
|
|
#if defined(SPS_ARCH_arm) && defined(MOZ_WIDGET_GONK)
|
|
|
|
// Should also work on other Android and ARM Linux, but not tested there yet.
|
|
|
|
#define USE_EHABI_STACKWALK
|
|
|
|
#endif
|
|
|
|
#ifdef USE_EHABI_STACKWALK
|
|
|
|
#include "EHABIStackWalk.h"
|
|
|
|
#endif
|
|
|
|
|
2011-12-07 11:48:15 -08:00
|
|
|
using std::string;
|
2011-12-16 06:03:54 -08:00
|
|
|
using namespace mozilla;
|
2011-12-07 11:48:15 -08:00
|
|
|
|
2011-12-04 11:09:00 -08:00
|
|
|
#ifndef MAXPATHLEN
|
2012-01-28 22:56:41 -08:00
|
|
|
#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
|
2011-12-04 11:09:00 -08:00
|
|
|
#endif
|
|
|
|
|
2013-03-25 14:57:28 -07:00
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
// BEGIN SaveProfileTask et al
|
2011-08-26 17:05:37 -07:00
|
|
|
|
2012-04-16 06:32:18 -07:00
|
|
|
std::string GetSharedLibraryInfoString();
|
|
|
|
|
2011-08-26 17:05:37 -07:00
|
|
|
void TableTicker::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);
|
|
|
|
}
|
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
void TableTicker::StreamMetaJSCustomObject(JSStreamWriter& b)
|
2012-06-26 20:43:28 -07:00
|
|
|
{
|
2014-04-21 13:48:47 -07:00
|
|
|
b.BeginObject();
|
|
|
|
|
|
|
|
b.NameValue("version", 2);
|
|
|
|
b.NameValue("interval", interval());
|
|
|
|
b.NameValue("stackwalk", mUseStackWalk);
|
|
|
|
b.NameValue("jank", mJankOnly);
|
|
|
|
b.NameValue("processType", XRE_GetProcessType());
|
|
|
|
|
|
|
|
TimeDuration delta = TimeStamp::Now() - sStartTime;
|
2014-05-01 13:27:36 -07:00
|
|
|
b.NameValue("startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
|
2014-04-21 13:48:47 -07:00
|
|
|
|
|
|
|
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.NameValue("platform", string.Data());
|
|
|
|
|
|
|
|
res = http->GetOscpu(string);
|
|
|
|
if (!NS_FAILED(res))
|
|
|
|
b.NameValue("oscpu", string.Data());
|
|
|
|
|
|
|
|
res = http->GetMisc(string);
|
|
|
|
if (!NS_FAILED(res))
|
|
|
|
b.NameValue("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.NameValue("abi", string.Data());
|
|
|
|
|
|
|
|
res = runtime->GetWidgetToolkit(string);
|
|
|
|
if (!NS_FAILED(res))
|
|
|
|
b.NameValue("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.NameValue("product", string.Data());
|
|
|
|
}
|
|
|
|
|
|
|
|
b.EndObject();
|
2012-06-26 20:43:28 -07:00
|
|
|
}
|
|
|
|
|
2012-11-30 09:49:20 -08:00
|
|
|
void TableTicker::ToStreamAsJSON(std::ostream& stream)
|
|
|
|
{
|
2014-04-21 13:48:47 -07:00
|
|
|
JSStreamWriter b(stream);
|
|
|
|
StreamJSObject(b);
|
2012-11-30 09:49:20 -08:00
|
|
|
}
|
|
|
|
|
2012-03-12 07:58:40 -07:00
|
|
|
JSObject* TableTicker::ToJSObject(JSContext *aCx)
|
|
|
|
{
|
2014-04-21 13:48:47 -07:00
|
|
|
JS::RootedValue val(aCx);
|
|
|
|
std::stringstream ss;
|
|
|
|
JSStreamWriter b(ss);
|
|
|
|
StreamJSObject(b);
|
|
|
|
NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str()));
|
|
|
|
JS_ParseJSON(aCx, static_cast<const jschar*>(js_string.get()), js_string.Length(), &val);
|
|
|
|
return &val.toObject();
|
2012-11-30 09:49:20 -08:00
|
|
|
}
|
2012-03-12 07:58:40 -07:00
|
|
|
|
2013-03-21 02:17:23 -07:00
|
|
|
struct SubprocessClosure {
|
2014-04-21 13:48:47 -07:00
|
|
|
SubprocessClosure(JSStreamWriter *aWriter)
|
|
|
|
: mWriter(aWriter)
|
2013-09-05 16:10:37 -07:00
|
|
|
{}
|
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
JSStreamWriter* mWriter;
|
2013-03-21 02:17:23 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
void SubProcessCallback(const char* aProfile, void* aClosure)
|
|
|
|
{
|
|
|
|
// Called by the observer to get their profile data included
|
|
|
|
// as a sub profile
|
2014-04-21 13:48:47 -07:00
|
|
|
SubprocessClosure* closure = (SubprocessClosure*)aClosure;
|
2013-03-21 02:17:23 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
// Add the string profile into the profile
|
|
|
|
closure->mWriter->Value(aProfile);
|
2013-03-21 02:17:23 -07:00
|
|
|
}
|
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
|
2013-04-23 11:33:38 -07:00
|
|
|
#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
2013-04-23 10:10:29 -07:00
|
|
|
static
|
2014-04-21 13:48:47 -07:00
|
|
|
void BuildJavaThreadJSObject(JSStreamWriter& b)
|
2013-04-23 10:10:29 -07:00
|
|
|
{
|
2014-04-21 13:48:47 -07:00
|
|
|
b.BeginObject();
|
|
|
|
|
|
|
|
b.NameValue("name", "Java Main Thread");
|
|
|
|
|
|
|
|
b.Name("samples");
|
|
|
|
b.BeginArray();
|
|
|
|
|
|
|
|
// for each sample
|
|
|
|
for (int sampleId = 0; true; sampleId++) {
|
|
|
|
bool firstRun = true;
|
|
|
|
// for each frame
|
|
|
|
for (int frameId = 0; true; frameId++) {
|
|
|
|
nsCString result;
|
|
|
|
bool hasFrame = AndroidBridge::Bridge()->GetFrameNameJavaProfiling(0, sampleId, frameId, result);
|
|
|
|
// when we run out of frames, we stop looping
|
|
|
|
if (!hasFrame) {
|
|
|
|
// if we found at least one frame, we have objects to close
|
|
|
|
if (!firstRun) {
|
|
|
|
b.EndArray();
|
|
|
|
b.EndObject();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// the first time around, open the sample object and frames array
|
|
|
|
if (firstRun) {
|
|
|
|
firstRun = false;
|
|
|
|
|
|
|
|
double sampleTime =
|
|
|
|
mozilla::widget::android::GeckoJavaSampler::GetSampleTimeJavaProfiling(0, sampleId);
|
|
|
|
|
|
|
|
b.BeginObject();
|
|
|
|
b.NameValue("time", sampleTime);
|
|
|
|
|
|
|
|
b.Name("frames");
|
|
|
|
b.BeginArray();
|
|
|
|
}
|
|
|
|
// add a frame to the sample
|
|
|
|
b.BeginObject();
|
|
|
|
b.NameValue("location", result.BeginReading());
|
|
|
|
b.EndObject();
|
|
|
|
}
|
|
|
|
// if we found no frames for this sample, we are done
|
|
|
|
if (firstRun) {
|
|
|
|
break;
|
2013-04-23 10:10:29 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
b.EndArray();
|
|
|
|
|
|
|
|
b.EndObject();
|
2013-04-23 10:10:29 -07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
void TableTicker::StreamJSObject(JSStreamWriter& b)
|
2012-11-30 09:49:20 -08:00
|
|
|
{
|
2014-04-21 13:48:47 -07:00
|
|
|
b.BeginObject();
|
|
|
|
// Put shared library info
|
|
|
|
b.NameValue("libs", GetSharedLibraryInfoString().c_str());
|
2012-07-11 12:36:04 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
// Put meta data
|
|
|
|
b.Name("meta");
|
|
|
|
StreamMetaJSCustomObject(b);
|
2012-03-12 07:58:40 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
// Lists the samples for each ThreadProfile
|
|
|
|
b.Name("threads");
|
|
|
|
b.BeginArray();
|
2012-03-12 07:58:40 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
SetPaused(true);
|
2013-03-29 12:34:49 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
{
|
|
|
|
mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex);
|
2013-03-29 12:34:49 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
|
|
|
|
// Thread not being profiled, skip it
|
|
|
|
if (!sRegisteredThreads->at(i)->Profile())
|
|
|
|
continue;
|
2013-04-03 15:59:17 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
MutexAutoLock lock(*sRegisteredThreads->at(i)->Profile()->GetMutex());
|
2013-03-29 12:34:49 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
sRegisteredThreads->at(i)->Profile()->StreamJSObject(b);
|
|
|
|
}
|
|
|
|
}
|
2013-03-29 12:34:49 -07:00
|
|
|
|
2014-04-28 19:20:51 -07:00
|
|
|
if (Sampler::CanNotifyObservers()) {
|
|
|
|
// Send a event asking any subprocesses (plugins) to
|
|
|
|
// give us their information
|
|
|
|
SubprocessClosure closure(&b);
|
|
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
|
|
if (os) {
|
|
|
|
nsRefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback, &closure);
|
|
|
|
os->NotifyObservers(pse, "profiler-subprocess", nullptr);
|
|
|
|
}
|
2014-04-21 13:48:47 -07:00
|
|
|
}
|
2013-04-23 10:10:29 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
|
|
|
if (ProfileJava()) {
|
|
|
|
mozilla::widget::android::GeckoJavaSampler::PauseJavaProfiling();
|
2013-04-23 10:10:29 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
BuildJavaThreadJSObject(b);
|
|
|
|
|
|
|
|
mozilla::widget::android::GeckoJavaSampler::UnpauseJavaProfiling();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
SetPaused(false);
|
|
|
|
b.EndArray();
|
2013-04-23 10:10:29 -07:00
|
|
|
|
2014-04-21 13:48:47 -07:00
|
|
|
b.EndObject();
|
2013-04-23 10:10:29 -07:00
|
|
|
}
|
2012-03-12 07:58:40 -07:00
|
|
|
|
2013-03-25 14:57:28 -07:00
|
|
|
// END SaveProfileTask et al
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2012-12-11 11:10:56 -08:00
|
|
|
static
|
|
|
|
void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr)
|
|
|
|
{
|
|
|
|
aProfile.addTag(ProfileEntry(aTagName, ""));
|
|
|
|
// Add one to store the null termination
|
|
|
|
size_t strLen = strlen(aStr) + 1;
|
|
|
|
for (size_t j = 0; j < strLen;) {
|
|
|
|
// Store as many characters in the void* as the platform allows
|
|
|
|
char text[sizeof(void*)];
|
2013-03-26 10:32:09 -07:00
|
|
|
size_t len = sizeof(void*)/sizeof(char);
|
2012-12-11 11:10:56 -08:00
|
|
|
if (j+len >= strLen) {
|
|
|
|
len = strLen - j;
|
|
|
|
}
|
|
|
|
memcpy(text, &aStr[j], len);
|
|
|
|
j += sizeof(void*)/sizeof(char);
|
|
|
|
// Cast to *((void**) to pass the text data to a void*
|
|
|
|
aProfile.addTag(ProfileEntry('d', *((void**)(&text[0]))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-27 16:04:58 -07:00
|
|
|
static
|
2012-08-06 15:58:47 -07:00
|
|
|
void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile,
|
2013-03-10 15:00:23 -07:00
|
|
|
PseudoStack *stack, void *lastpc)
|
2012-06-27 16:04:58 -07:00
|
|
|
{
|
2012-08-06 15:58:47 -07:00
|
|
|
int lineno = -1;
|
|
|
|
|
2012-06-27 16:04:58 -07:00
|
|
|
// First entry has tagName 's' (start)
|
|
|
|
// Check for magic pointer bit 1 to indicate copy
|
2012-08-06 11:35:56 -07:00
|
|
|
const char* sampleLabel = entry.label();
|
|
|
|
if (entry.isCopyLabel()) {
|
2012-06-27 16:04:58 -07:00
|
|
|
// Store the string using 1 or more 'd' (dynamic) tags
|
|
|
|
// that will happen to the preceding tag
|
|
|
|
|
2012-12-11 11:10:56 -08:00
|
|
|
addDynamicTag(aProfile, 'c', sampleLabel);
|
2014-05-28 15:44:41 -07:00
|
|
|
if (entry.isJs()) {
|
2012-08-06 15:58:47 -07:00
|
|
|
if (!entry.pc()) {
|
2013-11-11 11:16:31 -08:00
|
|
|
// The JIT only allows the top-most entry to have a nullptr pc
|
2012-08-06 15:58:47 -07:00
|
|
|
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) {
|
2013-11-11 11:16:31 -08:00
|
|
|
lineno = JS_PCToLineNumber(nullptr, entry.script(), jspc);
|
2012-08-06 15:58:47 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2013-11-11 11:16:31 -08:00
|
|
|
lineno = JS_PCToLineNumber(nullptr, entry.script(), entry.pc());
|
2012-08-06 15:58:47 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
lineno = entry.line();
|
|
|
|
}
|
2012-06-27 16:04:58 -07:00
|
|
|
} else {
|
|
|
|
aProfile.addTag(ProfileEntry('c', sampleLabel));
|
2014-05-28 15:44:41 -07:00
|
|
|
|
|
|
|
// XXX: Bug 1010578. Don't assume a CPP entry and try to get the
|
|
|
|
// line for js entries as well.
|
|
|
|
if (entry.isCpp()) {
|
|
|
|
lineno = entry.line();
|
|
|
|
}
|
2012-08-06 15:58:47 -07:00
|
|
|
}
|
2014-06-04 11:37:49 -07:00
|
|
|
|
2012-08-06 15:58:47 -07:00
|
|
|
if (lineno != -1) {
|
|
|
|
aProfile.addTag(ProfileEntry('n', lineno));
|
2012-06-27 16:04:58 -07:00
|
|
|
}
|
2014-06-04 11:37:49 -07:00
|
|
|
|
|
|
|
uint32_t category = entry.category();
|
|
|
|
MOZ_ASSERT(!(category & StackEntry::IS_CPP_ENTRY));
|
|
|
|
MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY));
|
|
|
|
|
|
|
|
if (category) {
|
|
|
|
aProfile.addTag(ProfileEntry('y', (int)category));
|
|
|
|
}
|
2012-06-27 16:04:58 -07:00
|
|
|
}
|
2012-02-03 12:19:18 -08:00
|
|
|
|
2013-09-11 11:53:14 -07:00
|
|
|
#if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK)
|
2012-01-16 16:59:15 -08:00
|
|
|
typedef struct {
|
|
|
|
void** array;
|
2012-06-27 16:04:58 -07:00
|
|
|
void** sp_array;
|
2012-01-16 16:59:15 -08:00
|
|
|
size_t size;
|
|
|
|
size_t count;
|
|
|
|
} PCArray;
|
|
|
|
|
2013-09-11 11:51:11 -07:00
|
|
|
static void mergeNativeBacktrace(ThreadProfile &aProfile, const PCArray &array) {
|
|
|
|
aProfile.addTag(ProfileEntry('s', "(root)"));
|
|
|
|
|
|
|
|
PseudoStack* stack = aProfile.GetPseudoStack();
|
|
|
|
uint32_t pseudoStackPos = 0;
|
|
|
|
|
|
|
|
/* We have two stacks, the native C stack we extracted from unwinding,
|
|
|
|
* and the pseudostack we managed during execution. We want to consolidate
|
|
|
|
* the two in order. We do so by merging using the approximate stack address
|
|
|
|
* when each entry was push. When pushing JS entry we may not now the stack
|
2013-11-11 11:16:31 -08:00
|
|
|
* address in which case we have a nullptr stack address in which case we assume
|
2013-09-11 11:51:11 -07:00
|
|
|
* that it follows immediatly the previous element.
|
|
|
|
*
|
|
|
|
* C Stack | Address -- Pseudo Stack | Address
|
|
|
|
* main() | 0x100 run_js() | 0x40
|
2013-11-11 11:16:31 -08:00
|
|
|
* start() | 0x80 jsCanvas() | nullptr
|
|
|
|
* timer() | 0x50 drawLine() | nullptr
|
2013-09-11 11:51:11 -07:00
|
|
|
* azure() | 0x10
|
|
|
|
*
|
|
|
|
* Merged: main(), start(), timer(), run_js(), jsCanvas(), drawLine(), azure()
|
|
|
|
*/
|
|
|
|
// i is the index in C stack starting at main and decreasing
|
|
|
|
// pseudoStackPos is the position in the Pseudo stack starting
|
|
|
|
// at the first frame (run_js in the example) and increasing.
|
|
|
|
for (size_t i = array.count; i > 0; --i) {
|
|
|
|
while (pseudoStackPos < stack->stackSize()) {
|
|
|
|
volatile StackEntry& entry = stack->mStack[pseudoStackPos];
|
|
|
|
|
|
|
|
if (entry.stackAddress() < array.sp_array[i-1] && entry.stackAddress())
|
|
|
|
break;
|
|
|
|
|
|
|
|
addProfileEntry(entry, aProfile, stack, array.array[0]);
|
|
|
|
pseudoStackPos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
aProfile.addTag(ProfileEntry('l', (void*)array.array[i-1]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef USE_NS_STACKWALK
|
2012-01-16 16:59:15 -08:00
|
|
|
static
|
2012-06-27 13:08:21 -07:00
|
|
|
void StackWalkCallback(void* aPC, void* aSP, void* aClosure)
|
2012-01-16 16:59:15 -08:00
|
|
|
{
|
|
|
|
PCArray* array = static_cast<PCArray*>(aClosure);
|
2012-12-20 21:31:57 -08:00
|
|
|
MOZ_ASSERT(array->count < array->size);
|
2012-06-27 16:04:58 -07:00
|
|
|
array->sp_array[array->count] = aSP;
|
|
|
|
array->array[array->count] = aPC;
|
|
|
|
array->count++;
|
2012-01-16 16:59:15 -08:00
|
|
|
}
|
|
|
|
|
2013-03-25 14:57:28 -07:00
|
|
|
void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
|
2012-01-16 16:59:15 -08:00
|
|
|
{
|
2012-12-05 15:10:15 -08:00
|
|
|
#ifndef XP_MACOSX
|
2013-04-03 15:59:17 -07:00
|
|
|
uintptr_t thread = GetThreadHandle(aSample->threadProfile->GetPlatformData());
|
2012-12-05 15:10:15 -08:00
|
|
|
MOZ_ASSERT(thread);
|
|
|
|
#endif
|
2012-01-16 16:59:15 -08:00
|
|
|
void* pc_array[1000];
|
2012-06-27 16:04:58 -07:00
|
|
|
void* sp_array[1000];
|
2012-01-16 16:59:15 -08:00
|
|
|
PCArray array = {
|
|
|
|
pc_array,
|
2012-06-27 16:04:58 -07:00
|
|
|
sp_array,
|
2012-01-16 16:59:15 -08:00
|
|
|
mozilla::ArrayLength(pc_array),
|
|
|
|
0
|
|
|
|
};
|
2012-03-22 15:31:56 -07:00
|
|
|
|
|
|
|
// Start with the current function.
|
2012-06-27 13:08:21 -07:00
|
|
|
StackWalkCallback(aSample->pc, aSample->sp, &array);
|
2012-03-22 15:31:56 -07:00
|
|
|
|
2013-03-26 10:32:09 -07:00
|
|
|
uint32_t maxFrames = uint32_t(array.size - array.count);
|
2012-02-03 12:19:18 -08:00
|
|
|
#ifdef XP_MACOSX
|
2013-04-03 15:59:17 -07:00
|
|
|
pthread_t pt = GetProfiledThread(aSample->threadProfile->GetPlatformData());
|
2012-02-14 21:17:34 -08:00
|
|
|
void *stackEnd = reinterpret_cast<void*>(-1);
|
|
|
|
if (pt)
|
2012-03-12 07:57:36 -07:00
|
|
|
stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt));
|
2012-11-21 13:24:39 -08:00
|
|
|
nsresult rv = NS_OK;
|
|
|
|
if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd)
|
2012-12-20 21:31:57 -08:00
|
|
|
rv = FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0,
|
|
|
|
maxFrames, &array,
|
|
|
|
reinterpret_cast<void**>(aSample->fp), stackEnd);
|
2012-02-03 12:19:18 -08:00
|
|
|
#else
|
2013-01-29 17:59:12 -08:00
|
|
|
void *platformData = nullptr;
|
|
|
|
#ifdef XP_WIN
|
2013-10-15 12:06:20 -07:00
|
|
|
if (aSample->isSamplingCurrentThread) {
|
|
|
|
// In this case we want NS_StackWalk to know that it's walking the
|
|
|
|
// current thread's stack, so we pass 0 as the thread handle.
|
|
|
|
thread = 0;
|
|
|
|
}
|
2013-01-29 17:59:12 -08:00
|
|
|
platformData = aSample->context;
|
|
|
|
#endif // XP_WIN
|
|
|
|
|
2012-12-20 21:31:57 -08:00
|
|
|
nsresult rv = NS_StackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
|
|
|
|
&array, thread, platformData);
|
2012-02-03 12:19:18 -08:00
|
|
|
#endif
|
2013-09-11 11:51:11 -07:00
|
|
|
if (NS_SUCCEEDED(rv))
|
|
|
|
mergeNativeBacktrace(aProfile, array);
|
2012-01-16 16:59:15 -08:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-09-11 11:53:14 -07:00
|
|
|
#ifdef USE_EHABI_STACKWALK
|
|
|
|
void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
|
|
|
|
{
|
|
|
|
void *pc_array[1000];
|
|
|
|
void *sp_array[1000];
|
|
|
|
PCArray array = {
|
|
|
|
pc_array,
|
|
|
|
sp_array,
|
|
|
|
mozilla::ArrayLength(pc_array),
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
2014-03-27 19:20:20 -07:00
|
|
|
const mcontext_t *mcontext = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext;
|
|
|
|
mcontext_t savedContext;
|
|
|
|
PseudoStack *pseudoStack = aProfile.GetPseudoStack();
|
|
|
|
|
|
|
|
array.count = 0;
|
|
|
|
// The pseudostack contains an "EnterJIT" frame whenever we enter
|
|
|
|
// JIT code with profiling enabled; the stack pointer value points
|
|
|
|
// the saved registers. We use this to unwind resume unwinding
|
|
|
|
// after encounting JIT code.
|
|
|
|
for (uint32_t i = pseudoStack->stackSize(); i > 0; --i) {
|
|
|
|
// The pseudostack grows towards higher indices, so we iterate
|
|
|
|
// backwards (from callee to caller).
|
|
|
|
volatile StackEntry &entry = pseudoStack->mStack[i - 1];
|
2014-05-28 15:44:41 -07:00
|
|
|
if (!entry.isJs() && strcmp(entry.label(), "EnterJIT") == 0) {
|
2014-03-27 19:20:20 -07:00
|
|
|
// Found JIT entry frame. Unwind up to that point (i.e., force
|
|
|
|
// the stack walk to stop before the block of saved registers;
|
|
|
|
// note that it yields nondecreasing stack pointers), then restore
|
|
|
|
// the saved state.
|
|
|
|
uint32_t *vSP = reinterpret_cast<uint32_t*>(entry.stackAddress());
|
|
|
|
|
|
|
|
array.count += EHABIStackWalk(*mcontext,
|
|
|
|
/* stackBase = */ vSP,
|
|
|
|
sp_array + array.count,
|
|
|
|
pc_array + array.count,
|
|
|
|
array.size - array.count);
|
|
|
|
|
|
|
|
memset(&savedContext, 0, sizeof(savedContext));
|
|
|
|
// See also: struct EnterJITStack in js/src/jit/arm/Trampoline-arm.cpp
|
|
|
|
savedContext.arm_r4 = *vSP++;
|
|
|
|
savedContext.arm_r5 = *vSP++;
|
|
|
|
savedContext.arm_r6 = *vSP++;
|
|
|
|
savedContext.arm_r7 = *vSP++;
|
|
|
|
savedContext.arm_r8 = *vSP++;
|
|
|
|
savedContext.arm_r9 = *vSP++;
|
|
|
|
savedContext.arm_r10 = *vSP++;
|
|
|
|
savedContext.arm_fp = *vSP++;
|
|
|
|
savedContext.arm_lr = *vSP++;
|
|
|
|
savedContext.arm_sp = reinterpret_cast<uint32_t>(vSP);
|
|
|
|
savedContext.arm_pc = savedContext.arm_lr;
|
|
|
|
mcontext = &savedContext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now unwind whatever's left (starting from either the last EnterJIT
|
|
|
|
// frame or, if no EnterJIT was found, the original registers).
|
|
|
|
array.count += EHABIStackWalk(*mcontext,
|
|
|
|
aProfile.GetStackTop(),
|
|
|
|
sp_array + array.count,
|
|
|
|
pc_array + array.count,
|
|
|
|
array.size - array.count);
|
|
|
|
|
2013-09-11 11:53:14 -07:00
|
|
|
mergeNativeBacktrace(aProfile, array);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2011-12-19 17:33:00 -08:00
|
|
|
static
|
2013-03-10 15:00:23 -07:00
|
|
|
void doSampleStackTrace(PseudoStack *aStack, ThreadProfile &aProfile, TickSample *sample)
|
2011-12-19 17:33:00 -08:00
|
|
|
{
|
2011-08-26 17:05:37 -07:00
|
|
|
// Sample
|
|
|
|
// 's' tag denotes the start of a sample block
|
|
|
|
// followed by 0 or more 'c' tags.
|
2012-06-26 20:25:14 -07:00
|
|
|
aProfile.addTag(ProfileEntry('s', "(root)"));
|
2012-08-06 11:35:56 -07:00
|
|
|
for (uint32_t i = 0; i < aStack->stackSize(); i++) {
|
2012-08-06 15:58:47 -07:00
|
|
|
addProfileEntry(aStack->mStack[i], aProfile, aStack, nullptr);
|
2011-08-26 17:05:37 -07:00
|
|
|
}
|
2012-04-13 11:56:51 -07:00
|
|
|
#ifdef ENABLE_SPS_LEAF_DATA
|
|
|
|
if (sample) {
|
2012-05-08 12:38:11 -07:00
|
|
|
aProfile.addTag(ProfileEntry('l', (void*)sample->pc));
|
2012-07-04 08:25:15 -07:00
|
|
|
#ifdef ENABLE_ARM_LR_SAVING
|
|
|
|
aProfile.addTag(ProfileEntry('L', (void*)sample->lr));
|
|
|
|
#endif
|
2012-04-13 11:56:51 -07:00
|
|
|
}
|
|
|
|
#endif
|
2011-12-19 17:33:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TableTicker::Tick(TickSample* sample)
|
2013-04-01 12:45:03 -07:00
|
|
|
{
|
|
|
|
if (HasUnwinderThread()) {
|
|
|
|
UnwinderTick(sample);
|
|
|
|
} else {
|
|
|
|
InplaceTick(sample);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TableTicker::InplaceTick(TickSample* sample)
|
2011-12-19 17:33:00 -08:00
|
|
|
{
|
2013-03-29 12:34:49 -07:00
|
|
|
ThreadProfile& currThreadProfile = *sample->threadProfile;
|
|
|
|
|
|
|
|
PseudoStack* stack = currThreadProfile.GetPseudoStack();
|
2011-12-20 12:13:52 -08:00
|
|
|
bool recordSample = true;
|
2013-10-08 07:05:25 -07:00
|
|
|
#if defined(XP_WIN)
|
|
|
|
bool powerSample = false;
|
|
|
|
#endif
|
2013-09-25 08:28:34 -07:00
|
|
|
|
|
|
|
/* Don't process the PeudoStack's markers or honour jankOnly if we're
|
|
|
|
immediately sampling the current thread. */
|
|
|
|
if (!sample->isSamplingCurrentThread) {
|
|
|
|
// Marker(s) come before the sample
|
|
|
|
ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers();
|
|
|
|
while (pendingMarkersList && pendingMarkersList->peek()) {
|
|
|
|
ProfilerMarker* marker = pendingMarkersList->popHead();
|
|
|
|
stack->addStoredMarker(marker);
|
|
|
|
currThreadProfile.addTag(ProfileEntry('m', marker));
|
2012-03-12 07:56:33 -07:00
|
|
|
}
|
2013-09-25 08:28:34 -07:00
|
|
|
stack->updateGeneration(currThreadProfile.GetGenerationID());
|
|
|
|
|
2013-10-08 07:05:25 -07:00
|
|
|
#if defined(XP_WIN)
|
|
|
|
if (mProfilePower) {
|
|
|
|
mIntelPowerGadget->TakeSample();
|
|
|
|
powerSample = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-09-25 08:28:34 -07:00
|
|
|
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
|
|
|
|
currThreadProfile.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;
|
|
|
|
}
|
2011-12-20 12:13:52 -08:00
|
|
|
}
|
|
|
|
}
|
2012-01-10 15:02:00 -08:00
|
|
|
}
|
2011-12-20 12:13:52 -08:00
|
|
|
|
2013-09-11 11:53:14 -07:00
|
|
|
#if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK)
|
2012-01-18 15:07:46 -08:00
|
|
|
if (mUseStackWalk) {
|
2013-03-29 12:34:49 -07:00
|
|
|
doNativeBacktrace(currThreadProfile, sample);
|
2012-01-18 15:07:46 -08:00
|
|
|
} else {
|
2013-03-29 12:34:49 -07:00
|
|
|
doSampleStackTrace(stack, currThreadProfile, mAddLeafAddresses ? sample : nullptr);
|
2011-12-20 12:13:52 -08:00
|
|
|
}
|
2012-01-18 15:07:46 -08:00
|
|
|
#else
|
2013-03-29 12:34:49 -07:00
|
|
|
doSampleStackTrace(stack, currThreadProfile, mAddLeafAddresses ? sample : nullptr);
|
2012-01-18 15:07:46 -08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if (recordSample)
|
2013-03-29 12:34:49 -07:00
|
|
|
currThreadProfile.flush();
|
2011-12-07 11:48:15 -08:00
|
|
|
|
2014-06-06 14:53:42 -07:00
|
|
|
if (sample && currThreadProfile.GetThreadResponsiveness()->HasData()) {
|
|
|
|
TimeDuration delta = currThreadProfile.GetThreadResponsiveness()->GetUnresponsiveDuration(sample->timestamp);
|
2014-04-10 07:52:23 -07:00
|
|
|
currThreadProfile.addTag(ProfileEntry('r', static_cast<float>(delta.ToMilliseconds())));
|
2011-12-07 11:48:15 -08:00
|
|
|
}
|
2012-07-05 12:49:19 -07:00
|
|
|
|
|
|
|
if (sample) {
|
2013-04-23 10:10:29 -07:00
|
|
|
TimeDuration delta = sample->timestamp - sStartTime;
|
2014-04-10 07:52:23 -07:00
|
|
|
currThreadProfile.addTag(ProfileEntry('t', static_cast<float>(delta.ToMilliseconds())));
|
2012-07-05 12:49:19 -07:00
|
|
|
}
|
2012-09-05 08:45:17 -07:00
|
|
|
|
2014-05-19 11:31:31 -07:00
|
|
|
// rssMemory is equal to 0 when we are not recording.
|
|
|
|
if (sample && sample->rssMemory != 0) {
|
|
|
|
currThreadProfile.addTag(ProfileEntry('R', static_cast<float>(sample->rssMemory)));
|
|
|
|
}
|
|
|
|
|
2014-06-05 08:31:09 -07:00
|
|
|
// ussMemory is equal to 0 when we are not recording.
|
|
|
|
if (sample && sample->ussMemory != 0) {
|
|
|
|
currThreadProfile.addTag(ProfileEntry('U', static_cast<float>(sample->ussMemory)));
|
|
|
|
}
|
|
|
|
|
2013-10-08 07:05:25 -07:00
|
|
|
#if defined(XP_WIN)
|
|
|
|
if (powerSample) {
|
2014-04-10 07:52:23 -07:00
|
|
|
currThreadProfile.addTag(ProfileEntry('p', static_cast<float>(mIntelPowerGadget->GetTotalPackagePowerInWatts())));
|
2013-10-08 07:05:25 -07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-09-05 08:45:17 -07:00
|
|
|
if (sLastFrameNumber != sFrameNumber) {
|
2013-03-29 12:34:49 -07:00
|
|
|
currThreadProfile.addTag(ProfileEntry('f', sFrameNumber));
|
2012-09-05 08:45:17 -07:00
|
|
|
sLastFrameNumber = sFrameNumber;
|
|
|
|
}
|
2011-08-26 17:05:37 -07:00
|
|
|
}
|
|
|
|
|
2013-09-25 08:28:34 -07:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
SyncProfile* NewSyncProfile()
|
|
|
|
{
|
|
|
|
PseudoStack* stack = tlsPseudoStack.get();
|
|
|
|
if (!stack) {
|
|
|
|
MOZ_ASSERT(stack);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
Thread::tid_t tid = Thread::GetCurrentId();
|
|
|
|
|
2014-06-06 14:53:42 -07:00
|
|
|
ThreadInfo* info = new ThreadInfo("SyncProfile", tid, NS_IsMainThread(), stack, nullptr);
|
|
|
|
SyncProfile* profile = new SyncProfile(info, GET_BACKTRACE_DEFAULT_ENTRY);
|
2013-09-25 08:28:34 -07:00
|
|
|
return profile;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
SyncProfile* TableTicker::GetBacktrace()
|
|
|
|
{
|
|
|
|
SyncProfile* profile = NewSyncProfile();
|
|
|
|
|
|
|
|
TickSample sample;
|
|
|
|
sample.threadProfile = profile;
|
|
|
|
|
2013-10-22 21:56:50 -07:00
|
|
|
#if defined(HAVE_NATIVE_UNWIND)
|
2013-09-25 08:28:34 -07:00
|
|
|
#if defined(XP_WIN) || defined(LINUX)
|
|
|
|
tickcontext_t context;
|
|
|
|
sample.PopulateContext(&context);
|
|
|
|
#elif defined(XP_MACOSX)
|
|
|
|
sample.PopulateContext(nullptr);
|
2013-10-22 21:56:50 -07:00
|
|
|
#endif
|
2013-09-25 08:28:34 -07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
sample.isSamplingCurrentThread = true;
|
|
|
|
sample.timestamp = mozilla::TimeStamp::Now();
|
|
|
|
|
|
|
|
if (!HasUnwinderThread()) {
|
|
|
|
profile->BeginUnwind();
|
|
|
|
}
|
|
|
|
|
|
|
|
Tick(&sample);
|
|
|
|
|
|
|
|
if (!HasUnwinderThread()) {
|
|
|
|
profile->EndUnwind();
|
|
|
|
}
|
|
|
|
|
|
|
|
return profile;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_callback(const ProfileEntry& entry, const char* tagStringData)
|
|
|
|
{
|
2013-03-25 14:57:28 -07:00
|
|
|
switch (entry.getTagName()) {
|
2012-11-19 15:13:28 -08:00
|
|
|
case 's':
|
|
|
|
case 'c':
|
|
|
|
printf_stderr(" %s\n", tagStringData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-10 15:00:23 -07:00
|
|
|
void mozilla_sampler_print_location1()
|
2012-11-19 15:13:28 -08:00
|
|
|
{
|
|
|
|
if (!stack_key_initialized)
|
2013-11-11 11:16:31 -08:00
|
|
|
profiler_init(nullptr);
|
2012-11-19 15:13:28 -08:00
|
|
|
|
2013-09-25 08:28:34 -07:00
|
|
|
SyncProfile* syncProfile = NewSyncProfile();
|
|
|
|
if (!syncProfile) {
|
2012-11-19 15:13:28 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-09-25 08:28:34 -07:00
|
|
|
syncProfile->BeginUnwind();
|
2013-11-11 11:16:31 -08:00
|
|
|
doSampleStackTrace(syncProfile->GetPseudoStack(), *syncProfile, nullptr);
|
2013-09-25 08:28:34 -07:00
|
|
|
syncProfile->EndUnwind();
|
2012-11-19 15:13:28 -08:00
|
|
|
|
|
|
|
printf_stderr("Backtrace:\n");
|
2013-09-25 08:28:34 -07:00
|
|
|
syncProfile->IterateTags(print_callback);
|
2014-06-06 14:53:42 -07:00
|
|
|
ThreadInfo* info = syncProfile->GetThreadInfo();
|
2013-09-25 08:28:34 -07:00
|
|
|
delete syncProfile;
|
2014-06-06 14:53:42 -07:00
|
|
|
delete info;
|
2012-11-19 15:13:28 -08:00
|
|
|
}
|
2012-12-17 15:25:50 -08:00
|
|
|
|
|
|
|
|