mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 00:08:39 +09:00
Remove SPS profiler.
- Conditionals and code blocks. (MOZ_ENABLE_PROFILER_SPS) - Stub out several profiler-only functions.
This commit is contained in:
parent
3fe8aff3f5
commit
1a823f5ad6
113 changed files with 12 additions and 24501 deletions
|
|
@ -169,14 +169,10 @@
|
|||
#endif
|
||||
#ifdef NS_PRINTING
|
||||
#endif
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#endif
|
||||
#ifdef NECKO_WIFI
|
||||
#endif
|
||||
#ifdef MOZ_WEBRTC
|
||||
#endif
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#endif
|
||||
#ifdef MOZ_ENABLE_XREMOTE
|
||||
#endif
|
||||
#ifdef XP_MACOSX
|
||||
|
|
|
|||
|
|
@ -251,9 +251,6 @@
|
|||
@RESPATH@/components/layout_xul.xpt
|
||||
@RESPATH@/components/locale.xpt
|
||||
@RESPATH@/components/lwbrk.xpt
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
@RESPATH@/components/memory_profiler.xpt
|
||||
#endif
|
||||
@RESPATH@/browser/components/migration.xpt
|
||||
@RESPATH@/components/mimetype.xpt
|
||||
@RESPATH@/components/mozfind.xpt
|
||||
|
|
@ -285,9 +282,6 @@
|
|||
@RESPATH@/components/plugin.xpt
|
||||
@RESPATH@/components/pref.xpt
|
||||
@RESPATH@/components/prefetch.xpt
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
@RESPATH@/components/profiler.xpt
|
||||
#endif
|
||||
@RESPATH@/components/rdf.xpt
|
||||
@RESPATH@/components/satchel.xpt
|
||||
@RESPATH@/components/saxparser.xpt
|
||||
|
|
|
|||
|
|
@ -13470,10 +13470,6 @@ nsresult
|
|||
ConnectionPool::
|
||||
ThreadRunnable::Run()
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
char stackTopGuess;
|
||||
#endif // MOZ_ENABLE_PROFILER_SPS
|
||||
|
||||
MOZ_ASSERT(!IsOnBackgroundThread());
|
||||
MOZ_ASSERT(mContinueRunning);
|
||||
|
||||
|
|
@ -13490,10 +13486,6 @@ ThreadRunnable::Run()
|
|||
const nsPrintfCString threadName("IndexedDB #%lu", mSerialNumber);
|
||||
|
||||
PR_SetCurrentThreadName(threadName.get());
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
profiler_register_thread(threadName.get(), &stackTopGuess);
|
||||
#endif // MOZ_ENABLE_PROFILER_SPS
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -13537,10 +13529,6 @@ ThreadRunnable::Run()
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
profiler_unregister_thread();
|
||||
#endif // MOZ_ENABLE_PROFILER_SPS
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ const char kPrefMaxSerilizedMsgSize[] = IDB_PREF_BRANCH_ROOT "maxSerializedMsgSi
|
|||
const char kPrefLoggingEnabled[] = IDB_PREF_LOGGING_BRANCH_ROOT "enabled";
|
||||
const char kPrefLoggingDetails[] = IDB_PREF_LOGGING_BRANCH_ROOT "details";
|
||||
|
||||
#if defined(DEBUG) || defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
#if defined(DEBUG)
|
||||
const char kPrefLoggingProfiler[] =
|
||||
IDB_PREF_LOGGING_BRANCH_ROOT "profiler-marks";
|
||||
#endif
|
||||
|
|
@ -415,10 +415,7 @@ IndexedDatabaseManager::Init()
|
|||
|
||||
Preferences::RegisterCallback(LoggingModePrefChangedCallback,
|
||||
kPrefLoggingDetails);
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
Preferences::RegisterCallback(LoggingModePrefChangedCallback,
|
||||
kPrefLoggingProfiler);
|
||||
#endif
|
||||
|
||||
Preferences::RegisterCallbackAndCall(LoggingModePrefChangedCallback,
|
||||
kPrefLoggingEnabled);
|
||||
|
||||
|
|
@ -479,10 +476,6 @@ IndexedDatabaseManager::Destroy()
|
|||
|
||||
Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
|
||||
kPrefLoggingDetails);
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
|
||||
kPrefLoggingProfiler);
|
||||
#endif
|
||||
Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
|
||||
kPrefLoggingEnabled);
|
||||
|
||||
|
|
@ -1060,15 +1053,13 @@ IndexedDatabaseManager::LoggingModePrefChangedCallback(
|
|||
}
|
||||
|
||||
bool useProfiler =
|
||||
#if defined(DEBUG) || defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
#if defined(DEBUG)
|
||||
Preferences::GetBool(kPrefLoggingProfiler);
|
||||
#if !defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
if (useProfiler) {
|
||||
NS_WARNING("IndexedDB cannot create profiler marks because this build does "
|
||||
"not have profiler extensions enabled!");
|
||||
useProfiler = false;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -2612,15 +2612,6 @@ ContentChild::RecvShutdown()
|
|||
|
||||
GetIPCChannel()->SetAbortOnError(false);
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (profiler_is_active()) {
|
||||
// We're shutting down while we were profiling. Send the
|
||||
// profile up to the parent so that we don't lose this
|
||||
// information.
|
||||
Unused << RecvGatherProfile();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start a timer that will insure we quickly exit after a reasonable
|
||||
// period of time. Prevents shutdown hangs after our connection to the
|
||||
// parent closes.
|
||||
|
|
|
|||
|
|
@ -81,9 +81,6 @@
|
|||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ProcessHangMonitor.h"
|
||||
#include "mozilla/ProcessHangMonitorIPC.h"
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "mozilla/ProfileGatherer.h"
|
||||
#endif
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
|
@ -220,11 +217,6 @@
|
|||
#include "nsIBrowserSearchService.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "nsIProfiler.h"
|
||||
#include "nsIProfileSaveEvent.h"
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include "mozilla/widget/AudioSession.h"
|
||||
#endif
|
||||
|
|
@ -245,9 +237,6 @@ extern const char* kForceEnableE10sPref;
|
|||
|
||||
using base::ChildPrivileges;
|
||||
using base::KillProcess;
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
using mozilla::ProfileGatherer;
|
||||
#endif
|
||||
|
||||
using namespace mozilla::dom::power;
|
||||
using namespace mozilla::media;
|
||||
|
|
@ -514,14 +503,6 @@ static const char* sObserverTopics[] = {
|
|||
"file-watcher-update",
|
||||
#ifdef ACCESSIBILITY
|
||||
"a11y-init-or-shutdown",
|
||||
#endif
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
"profiler-started",
|
||||
"profiler-stopped",
|
||||
"profiler-paused",
|
||||
"profiler-resumed",
|
||||
"profiler-subprocess-gather",
|
||||
"profiler-subprocess",
|
||||
#endif
|
||||
"cacheservice:empty-cache",
|
||||
};
|
||||
|
|
@ -1305,26 +1286,6 @@ ContentParent::Init()
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
|
||||
bool profilerActive = false;
|
||||
DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
if (profilerActive) {
|
||||
nsCOMPtr<nsIProfilerStartParams> currentProfilerParams;
|
||||
rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
nsCOMPtr<nsISupports> gatherer;
|
||||
rv = profiler->GetProfileGatherer(getter_AddRefs(gatherer));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
|
||||
|
||||
StartProfiler(currentProfilerParams);
|
||||
}
|
||||
#endif
|
||||
|
||||
RefPtr<GeckoMediaPluginServiceParent> gmps(GeckoMediaPluginServiceParent::GetSingleton());
|
||||
gmps->UpdateContentProcessGMPCapabilities();
|
||||
}
|
||||
|
|
@ -1751,12 +1712,6 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
|
|||
|
||||
mConsoleService = nullptr;
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (mGatherer && !mProfile.IsEmpty()) {
|
||||
mGatherer->OOPExitProfile(mProfile);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (obs) {
|
||||
RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
|
||||
|
||||
|
|
@ -2556,27 +2511,6 @@ ContentParent::Observe(nsISupports* aSubject,
|
|||
NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess");
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
// Need to do this before the mIsAlive check to avoid missing profiles.
|
||||
if (!strcmp(aTopic, "profiler-subprocess-gather")) {
|
||||
if (mGatherer) {
|
||||
mGatherer->WillGatherOOPProfile();
|
||||
if (mIsAlive && mSubprocess) {
|
||||
Unused << SendGatherProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!strcmp(aTopic, "profiler-subprocess")) {
|
||||
nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
|
||||
if (pse) {
|
||||
if (!mProfile.IsEmpty()) {
|
||||
pse->AddSubProfile(mProfile.get());
|
||||
mProfile.Truncate();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!mIsAlive || !mSubprocess)
|
||||
return NS_OK;
|
||||
|
||||
|
|
@ -2662,22 +2596,6 @@ ContentParent::Observe(nsISupports* aSubject,
|
|||
Unused << SendShutdownA11y();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
else if (!strcmp(aTopic, "profiler-started")) {
|
||||
nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
|
||||
StartProfiler(params);
|
||||
}
|
||||
else if (!strcmp(aTopic, "profiler-stopped")) {
|
||||
mGatherer = nullptr;
|
||||
Unused << SendStopProfiler();
|
||||
}
|
||||
else if (!strcmp(aTopic, "profiler-paused")) {
|
||||
Unused << SendPauseProfiler(true);
|
||||
}
|
||||
else if (!strcmp(aTopic, "profiler-resumed")) {
|
||||
Unused << SendPauseProfiler(false);
|
||||
}
|
||||
#endif
|
||||
else if (!strcmp(aTopic, "cacheservice:empty-cache")) {
|
||||
Unused << SendNotifyEmptyHTTPCache();
|
||||
|
|
@ -4559,13 +4477,7 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
|
|||
bool
|
||||
ContentParent::RecvProfile(const nsCString& aProfile)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (NS_WARN_IF(!mGatherer)) {
|
||||
return true;
|
||||
}
|
||||
mProfile = aProfile;
|
||||
mGatherer->GatheredOOPProfile();
|
||||
#endif
|
||||
/*** STUB ***/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -4658,29 +4570,7 @@ ContentParent::RecvNotifyBenchmarkResult(const nsString& aCodecName,
|
|||
void
|
||||
ContentParent::StartProfiler(nsIProfilerStartParams* aParams)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (NS_WARN_IF(!aParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilerInitParams ipcParams;
|
||||
|
||||
ipcParams.enabled() = true;
|
||||
aParams->GetEntries(&ipcParams.entries());
|
||||
aParams->GetInterval(&ipcParams.interval());
|
||||
ipcParams.features() = aParams->GetFeatures();
|
||||
ipcParams.threadFilters() = aParams->GetThreadFilterNames();
|
||||
|
||||
Unused << SendStartProfiler(ipcParams);
|
||||
|
||||
nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
|
||||
if (NS_WARN_IF(!profiler)) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsISupports> gatherer;
|
||||
profiler->GetProfileGatherer(getter_AddRefs(gatherer));
|
||||
mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
|
||||
#endif
|
||||
/*** STUB ***/
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
|||
|
|
@ -42,9 +42,6 @@ class nsIWidget;
|
|||
|
||||
namespace mozilla {
|
||||
class PRemoteSpellcheckEngineParent;
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
class ProfileGatherer;
|
||||
#endif
|
||||
|
||||
namespace embedding {
|
||||
class PrintingParent;
|
||||
|
|
@ -1128,9 +1125,6 @@ private:
|
|||
|
||||
PProcessHangMonitorParent* mHangMonitorActor;
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
RefPtr<mozilla::ProfileGatherer> mGatherer;
|
||||
#endif
|
||||
nsCString mProfile;
|
||||
|
||||
UniquePtr<gfx::DriverCrashGuard> mDriverCrashGuard;
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@
|
|||
#include "mozilla/plugins/PluginBridge.h"
|
||||
#include "mozilla/plugins/PluginInstanceParent.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "mozilla/ProfileGatherer.h"
|
||||
#endif
|
||||
#include "mozilla/ProcessHangMonitor.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
|
@ -46,11 +43,6 @@
|
|||
#include "PluginUtilsWin.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "nsIProfiler.h"
|
||||
#include "nsIProfileSaveEvent.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
#include <glib.h>
|
||||
#elif XP_MACOSX
|
||||
|
|
@ -61,9 +53,6 @@
|
|||
using base::KillProcess;
|
||||
|
||||
using mozilla::PluginLibrary;
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
using mozilla::ProfileGatherer;
|
||||
#endif
|
||||
using mozilla::ipc::MessageChannel;
|
||||
using mozilla::ipc::GeckoChildProcessHost;
|
||||
using mozilla::dom::CrashReporterParent;
|
||||
|
|
@ -544,25 +533,6 @@ PluginModuleChromeParent::OnProcessLaunched(const bool aSucceeded)
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
|
||||
bool profilerActive = false;
|
||||
DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (profilerActive) {
|
||||
nsCOMPtr<nsIProfilerStartParams> currentProfilerParams;
|
||||
rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
nsCOMPtr<nsISupports> gatherer;
|
||||
rv = profiler->GetProfileGatherer(getter_AddRefs(gatherer));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
|
||||
|
||||
StartProfiler(currentProfilerParams);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -648,10 +618,6 @@ PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath,
|
|||
mSandboxLevel = aSandboxLevel;
|
||||
mRunID = GeckoChildProcessHost::GetUniqueID();
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
InitPluginProfiling();
|
||||
#endif
|
||||
|
||||
mozilla::HangMonitor::RegisterAnnotator(*this);
|
||||
}
|
||||
|
||||
|
|
@ -661,10 +627,6 @@ PluginModuleChromeParent::~PluginModuleChromeParent()
|
|||
NS_RUNTIMEABORT("unsafe destruction");
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
ShutdownPluginProfiling();
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
// If we registered for audio notifications, stop.
|
||||
mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this,
|
||||
|
|
@ -2680,133 +2642,10 @@ PluginModuleParent::AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
class PluginProfilerObserver final : public nsIObserver,
|
||||
public nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
explicit PluginProfilerObserver(PluginModuleChromeParent* pmp)
|
||||
: mPmp(pmp)
|
||||
{}
|
||||
|
||||
private:
|
||||
~PluginProfilerObserver() {}
|
||||
PluginModuleChromeParent* mPmp;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(PluginProfilerObserver, nsIObserver, nsISupportsWeakReference)
|
||||
|
||||
NS_IMETHODIMP
|
||||
PluginProfilerObserver::Observe(nsISupports *aSubject,
|
||||
const char *aTopic,
|
||||
const char16_t *aData)
|
||||
{
|
||||
if (!strcmp(aTopic, "profiler-started")) {
|
||||
nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
|
||||
mPmp->StartProfiler(params);
|
||||
} else if (!strcmp(aTopic, "profiler-stopped")) {
|
||||
mPmp->StopProfiler();
|
||||
} else if (!strcmp(aTopic, "profiler-subprocess-gather")) {
|
||||
mPmp->GatherAsyncProfile();
|
||||
} else if (!strcmp(aTopic, "profiler-subprocess")) {
|
||||
nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
|
||||
mPmp->GatheredAsyncProfile(pse);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::InitPluginProfiling()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
mProfilerObserver = new PluginProfilerObserver(this);
|
||||
observerService->AddObserver(mProfilerObserver, "profiler-started", false);
|
||||
observerService->AddObserver(mProfilerObserver, "profiler-stopped", false);
|
||||
observerService->AddObserver(mProfilerObserver, "profiler-subprocess-gather", false);
|
||||
observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::ShutdownPluginProfiling()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
observerService->RemoveObserver(mProfilerObserver, "profiler-started");
|
||||
observerService->RemoveObserver(mProfilerObserver, "profiler-stopped");
|
||||
observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess-gather");
|
||||
observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::StartProfiler(nsIProfilerStartParams* aParams)
|
||||
{
|
||||
if (NS_WARN_IF(!aParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilerInitParams ipcParams;
|
||||
|
||||
ipcParams.enabled() = true;
|
||||
aParams->GetEntries(&ipcParams.entries());
|
||||
aParams->GetInterval(&ipcParams.interval());
|
||||
ipcParams.features() = aParams->GetFeatures();
|
||||
ipcParams.threadFilters() = aParams->GetThreadFilterNames();
|
||||
|
||||
Unused << SendStartProfiler(ipcParams);
|
||||
|
||||
nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
|
||||
if (NS_WARN_IF(!profiler)) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsISupports> gatherer;
|
||||
profiler->GetProfileGatherer(getter_AddRefs(gatherer));
|
||||
mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::StopProfiler()
|
||||
{
|
||||
mGatherer = nullptr;
|
||||
Unused << SendStopProfiler();
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::GatherAsyncProfile()
|
||||
{
|
||||
if (NS_WARN_IF(!mGatherer)) {
|
||||
return;
|
||||
}
|
||||
mGatherer->WillGatherOOPProfile();
|
||||
Unused << SendGatherProfile();
|
||||
}
|
||||
|
||||
void
|
||||
PluginModuleChromeParent::GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent)
|
||||
{
|
||||
if (aSaveEvent && !mProfile.IsEmpty()) {
|
||||
aSaveEvent->AddSubProfile(mProfile.get());
|
||||
mProfile.Truncate();
|
||||
}
|
||||
}
|
||||
#endif // MOZ_ENABLE_PROFILER_SPS
|
||||
|
||||
bool
|
||||
PluginModuleChromeParent::RecvProfile(const nsCString& aProfile)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (NS_WARN_IF(!mGatherer)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mProfile = aProfile;
|
||||
mGatherer->GatheredOOPProfile();
|
||||
#endif
|
||||
/*** STUB ***/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,6 @@ class nsIProfileSaveEvent;
|
|||
class nsPluginTag;
|
||||
|
||||
namespace mozilla {
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
class ProfileGatherer;
|
||||
#endif
|
||||
|
||||
namespace layers {
|
||||
class TextureClientRecycleAllocator;
|
||||
} // namespace layers
|
||||
|
|
@ -467,13 +463,6 @@ class PluginModuleChromeParent
|
|||
|
||||
void CachedSettingChanged();
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
void GatherAsyncProfile();
|
||||
void GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent);
|
||||
void StartProfiler(nsIProfilerStartParams* aParams);
|
||||
void StopProfiler();
|
||||
#endif
|
||||
|
||||
virtual bool
|
||||
RecvProfile(const nsCString& aProfile) override;
|
||||
|
||||
|
|
@ -519,11 +508,6 @@ private:
|
|||
|
||||
virtual void UpdatePluginTimeout() override;
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
void InitPluginProfiling();
|
||||
void ShutdownPluginProfiling();
|
||||
#endif
|
||||
|
||||
void RegisterSettingsCallbacks();
|
||||
void UnregisterSettingsCallbacks();
|
||||
|
||||
|
|
@ -606,9 +590,6 @@ private:
|
|||
// processes in existence!
|
||||
dom::ContentParent* mContentParent;
|
||||
nsCOMPtr<nsIObserver> mPluginOfflineObserver;
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
RefPtr<mozilla::ProfileGatherer> mGatherer;
|
||||
#endif
|
||||
nsCString mProfile;
|
||||
bool mIsBlocklisted;
|
||||
static bool sInstantiated;
|
||||
|
|
|
|||
|
|
@ -2811,13 +2811,6 @@ WorkerThreadPrimaryRunnable::Run()
|
|||
}
|
||||
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
PseudoStack* stack = mozilla_get_pseudo_stack();
|
||||
if (stack) {
|
||||
stack->sampleContext(cx);
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
JSAutoRequest ar(cx);
|
||||
|
||||
|
|
@ -2829,12 +2822,6 @@ WorkerThreadPrimaryRunnable::Run()
|
|||
}
|
||||
|
||||
BackgroundChild::CloseForCurrentThread();
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (stack) {
|
||||
stack->sampleContext(nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// There may still be runnables on the debugger event queue that hold a
|
||||
|
|
|
|||
|
|
@ -35,9 +35,6 @@
|
|||
#include "TextRenderer.h" // for TextRenderer
|
||||
#include <vector>
|
||||
#include "GeckoProfiler.h" // for GeckoProfiler
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "ProfilerMarkers.h" // for ProfilerMarkers
|
||||
#endif
|
||||
|
||||
#define CULLING_LOG(...)
|
||||
// #define CULLING_LOG(...) printf_stderr("CULLING: " __VA_ARGS__)
|
||||
|
|
@ -86,26 +83,7 @@ static gfx::IntRect ContainerVisibleRect(ContainerT* aContainer)
|
|||
|
||||
static void PrintUniformityInfo(Layer* aLayer)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (!profiler_is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't want to print a log for smaller layers
|
||||
if (aLayer->GetLocalVisibleRegion().GetBounds().width < 300 ||
|
||||
aLayer->GetLocalVisibleRegion().GetBounds().height < 300) {
|
||||
return;
|
||||
}
|
||||
|
||||
Matrix4x4 transform = aLayer->AsLayerComposite()->GetShadowBaseTransform();
|
||||
if (!transform.Is2D()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point translation = transform.As2D().GetTranslation();
|
||||
LayerTranslationPayload* payload = new LayerTranslationPayload(aLayer, translation);
|
||||
PROFILER_MARKER_PAYLOAD("LayerTranslation", payload);
|
||||
#endif
|
||||
/*** STUB ***/
|
||||
}
|
||||
|
||||
/* all of the per-layer prepared data we need to maintain */
|
||||
|
|
|
|||
|
|
@ -74,9 +74,6 @@
|
|||
#include "mozilla/HalTypes.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "ProfilerMarkers.h"
|
||||
#endif
|
||||
#include "mozilla/VsyncDispatcher.h"
|
||||
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
|
||||
#include "VsyncSource.h"
|
||||
|
|
@ -1923,11 +1920,7 @@ CompositorBridgeParent::GetAPZCTreeManager(uint64_t aLayersId)
|
|||
static void
|
||||
InsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
|
||||
VsyncPayload* payload = new VsyncPayload(aVsyncTimestamp);
|
||||
PROFILER_MARKER_PAYLOAD("VsyncTimestamp", payload);
|
||||
#endif
|
||||
/*** STUB ***/
|
||||
}
|
||||
|
||||
/*static */ void
|
||||
|
|
|
|||
|
|
@ -73,9 +73,6 @@
|
|||
#include "mozilla/HalTypes.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "ProfilerMarkers.h"
|
||||
#endif
|
||||
#include "mozilla/VsyncDispatcher.h"
|
||||
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
|
||||
#include "VsyncSource.h"
|
||||
|
|
|
|||
|
|
@ -73,24 +73,10 @@ protected:
|
|||
|
||||
MOZ_IS_CLASS_INIT
|
||||
void Init() {
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
mPseudoStackHack = mozilla_get_pseudo_stack();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CleanUp() {
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
mPseudoStackHack = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
// This is needed to avoid a spurious leak report. There's no other
|
||||
// use for it. See bug 1239504 and bug 1215265.
|
||||
MOZ_INIT_OUTSIDE_CTOR PseudoStack* mPseudoStackHack;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
#include "ContextStateTracker.h"
|
||||
#include "GLContext.h"
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
#include "ProfilerMarkers.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
|
@ -110,15 +107,6 @@ ContextStateTrackerOGL::Flush(GLContext* aGL)
|
|||
|
||||
aGL->fDeleteQueries(1, &handle);
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
PROFILER_MARKER_PAYLOAD("gpu_timer_query", new GPUMarkerPayload(
|
||||
mCompletedSections[0].mCpuTimeStart,
|
||||
mCompletedSections[0].mCpuTimeEnd,
|
||||
0,
|
||||
gpuTime
|
||||
));
|
||||
#endif
|
||||
|
||||
mCompletedSections.RemoveElementAt(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1528,12 +1528,6 @@ XPCJSContext::~XPCJSContext()
|
|||
delete mDyingWrappedNativeProtoMap;
|
||||
mDyingWrappedNativeProtoMap = nullptr;
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
// Tell the profiler that the context is gone
|
||||
if (PseudoStack* stack = mozilla_get_pseudo_stack())
|
||||
stack->sampleContext(nullptr);
|
||||
#endif
|
||||
|
||||
Preferences::UnregisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this);
|
||||
}
|
||||
|
||||
|
|
@ -3382,10 +3376,6 @@ XPCJSContext::Initialize()
|
|||
JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, this);
|
||||
JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
|
||||
js::SetPreserveWrapperCallback(cx, PreserveWrapper);
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (PseudoStack* stack = mozilla_get_pseudo_stack())
|
||||
stack->sampleContext(cx);
|
||||
#endif
|
||||
JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
|
||||
js::SetActivityCallback(cx, ActivityCallback, this);
|
||||
JS_AddInterruptCallback(cx, InterruptCallback);
|
||||
|
|
|
|||
|
|
@ -61,9 +61,6 @@ RestyleTracker::Document() const {
|
|||
|
||||
struct RestyleEnumerateData : RestyleTracker::Hints {
|
||||
RefPtr<dom::Element> mElement;
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
UniquePtr<ProfilerBacktrace> mBacktrace;
|
||||
#endif
|
||||
};
|
||||
|
||||
inline void
|
||||
|
|
@ -257,12 +254,6 @@ RestyleTracker::DoProcessRestyles()
|
|||
data->mRestyleHint, MarkerTracingType::START)));
|
||||
}
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
Maybe<GeckoProfilerTracingRAII> profilerRAII;
|
||||
if (profiler_feature_active("restyle")) {
|
||||
profilerRAII.emplace("Paint", "Styles", Move(data->mBacktrace));
|
||||
}
|
||||
#endif
|
||||
ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint,
|
||||
data->mRestyleHintData);
|
||||
AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
|
||||
|
|
@ -337,9 +328,6 @@ RestyleTracker::DoProcessRestyles()
|
|||
// We can move data since we'll be clearing mPendingRestyles after
|
||||
// we finish enumerating it.
|
||||
restyle->mRestyleHintData = Move(data->mRestyleHintData);
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
restyle->mBacktrace = Move(data->mBacktrace);
|
||||
#endif
|
||||
|
||||
#ifdef RESTYLE_LOGGING
|
||||
count++;
|
||||
|
|
@ -365,12 +353,6 @@ RestyleTracker::DoProcessRestyles()
|
|||
index++, count);
|
||||
LOG_RESTYLE_INDENT();
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
Maybe<GeckoProfilerTracingRAII> profilerRAII;
|
||||
if (profiler_feature_active("restyle")) {
|
||||
profilerRAII.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace));
|
||||
}
|
||||
#endif
|
||||
if (isTimelineRecording) {
|
||||
timelines->AddMarkerForDocShell(docShell, Move(
|
||||
MakeUnique<RestyleTimelineMarker>(
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@
|
|||
#include "GeckoProfiler.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
#include "ProfilerBacktrace.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class RestyleManager;
|
||||
|
|
@ -128,9 +124,6 @@ public:
|
|||
// that we called AddPendingRestyle for and found the element this is
|
||||
// the RestyleData for as its nearest restyle root.
|
||||
nsTArray<RefPtr<Element>> mDescendants;
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
UniquePtr<ProfilerBacktrace> mBacktrace;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -265,11 +258,6 @@ RestyleTracker::AddPendingRestyleToTable(Element* aElement,
|
|||
if (!existingData) {
|
||||
RestyleData* rd =
|
||||
new RestyleData(aRestyleHint, aMinChangeHint, aRestyleHintData);
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
if (profiler_feature_active("restyle")) {
|
||||
rd->mBacktrace.reset(profiler_get_backtrace());
|
||||
}
|
||||
#endif
|
||||
mPendingRestyles.Put(aElement, rd);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4064,23 +4064,6 @@ PresShell::FlushPendingNotifications(mozilla::ChangesToFlush aFlush)
|
|||
*/
|
||||
mozFlushType flushType = aFlush.mFlushType;
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
static const char flushTypeNames[][20] = {
|
||||
"Content",
|
||||
"ContentAndNotify",
|
||||
"Style",
|
||||
"InterruptibleLayout",
|
||||
"Layout",
|
||||
"Display"
|
||||
};
|
||||
|
||||
// Make sure that we don't miss things added to mozFlushType!
|
||||
MOZ_ASSERT(static_cast<uint32_t>(flushType) <= ArrayLength(flushTypeNames));
|
||||
|
||||
PROFILER_LABEL_PRINTF("PresShell", "Flush",
|
||||
js::ProfileEntry::Category::GRAPHICS, "(Flush_%s)", flushTypeNames[flushType - 1]);
|
||||
#endif
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
#ifdef DEBUG
|
||||
nsAccessibilityService* accService = GetAccService();
|
||||
|
|
|
|||
|
|
@ -174,9 +174,6 @@
|
|||
@BINPATH@/components/layout_xul.xpt
|
||||
@BINPATH@/components/locale.xpt
|
||||
@BINPATH@/components/lwbrk.xpt
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
@BINPATH@/components/memory_profiler.xpt
|
||||
#endif
|
||||
@BINPATH@/components/mimetype.xpt
|
||||
@BINPATH@/components/mozfind.xpt
|
||||
@BINPATH@/components/necko_about.xpt
|
||||
|
|
@ -206,9 +203,6 @@
|
|||
@BINPATH@/components/plugin.xpt
|
||||
@BINPATH@/components/pref.xpt
|
||||
@BINPATH@/components/prefetch.xpt
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
@BINPATH@/components/profiler.xpt
|
||||
#endif
|
||||
@BINPATH@/components/rdf.xpt
|
||||
@BINPATH@/components/satchel.xpt
|
||||
@BINPATH@/components/saxparser.xpt
|
||||
|
|
|
|||
|
|
@ -110,18 +110,6 @@ public:
|
|||
// We don't report SQLite I/O on Windows because we have a comprehensive
|
||||
// mechanism for intercepting I/O on that platform that captures a superset
|
||||
// of the data captured here.
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(XP_WIN)
|
||||
if (IOInterposer::IsObservedOperation(op)) {
|
||||
const char* main_ref = "sqlite-mainthread";
|
||||
const char* other_ref = "sqlite-otherthread";
|
||||
|
||||
// Create observation
|
||||
IOInterposeObserver::Observation ob(op, start, end,
|
||||
(mainThread ? main_ref : other_ref));
|
||||
// Report observation
|
||||
IOInterposer::Report(ob);
|
||||
}
|
||||
#endif /* defined(MOZ_ENABLE_PROFILER_SPS) && !defined(XP_WIN) */
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -73,9 +73,6 @@
|
|||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/StartupTimeline.h"
|
||||
#include "mozilla/HangMonitor.h"
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
#include "shared-libraries.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -682,13 +679,6 @@ public:
|
|||
static void ShutdownTelemetry();
|
||||
static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName,
|
||||
uint32_t delay);
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
static void RecordChromeHang(uint32_t aDuration,
|
||||
Telemetry::ProcessedStack &aStack,
|
||||
int32_t aSystemUptime,
|
||||
int32_t aFirefoxUptime,
|
||||
HangAnnotationsPtr aAnnotations);
|
||||
#endif
|
||||
static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
struct Stat {
|
||||
|
|
@ -2224,31 +2214,6 @@ TelemetryImpl::RecordIceCandidates(const uint32_t iceCandidateBitmask,
|
|||
sTelemetry->mWebrtcTelemetry.RecordIceCandidateMask(iceCandidateBitmask, success);
|
||||
}
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
void
|
||||
TelemetryImpl::RecordChromeHang(uint32_t aDuration,
|
||||
Telemetry::ProcessedStack &aStack,
|
||||
int32_t aSystemUptime,
|
||||
int32_t aFirefoxUptime,
|
||||
HangAnnotationsPtr aAnnotations)
|
||||
{
|
||||
if (!sTelemetry || !TelemetryHistogram::CanRecordExtended())
|
||||
return;
|
||||
|
||||
HangAnnotationsPtr annotations;
|
||||
// We only pass aAnnotations if it is not empty.
|
||||
if (aAnnotations && !aAnnotations->IsEmpty()) {
|
||||
annotations = Move(aAnnotations);
|
||||
}
|
||||
|
||||
MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
|
||||
|
||||
sTelemetry->mHangReports.AddHang(aStack, aDuration,
|
||||
aSystemUptime, aFirefoxUptime,
|
||||
Move(annotations));
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats)
|
||||
{
|
||||
|
|
@ -2452,18 +2417,6 @@ struct StackFrame
|
|||
uint16_t mModIndex; // The index of module that has this program counter.
|
||||
};
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
static bool CompareByPC(const StackFrame &a, const StackFrame &b)
|
||||
{
|
||||
return a.mPC < b.mPC;
|
||||
}
|
||||
|
||||
static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
|
||||
{
|
||||
return a.mIndex < b.mIndex;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
|
@ -2629,60 +2582,6 @@ GetStackAndModules(const std::vector<uintptr_t>& aPCs)
|
|||
rawStack.push_back(Frame);
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
// Remove all modules not referenced by a PC on the stack
|
||||
std::sort(rawStack.begin(), rawStack.end(), CompareByPC);
|
||||
|
||||
size_t moduleIndex = 0;
|
||||
size_t stackIndex = 0;
|
||||
size_t stackSize = rawStack.size();
|
||||
|
||||
SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
|
||||
rawModules.SortByAddress();
|
||||
|
||||
while (moduleIndex < rawModules.GetSize()) {
|
||||
const SharedLibrary& module = rawModules.GetEntry(moduleIndex);
|
||||
uintptr_t moduleStart = module.GetStart();
|
||||
uintptr_t moduleEnd = module.GetEnd() - 1;
|
||||
// the interval is [moduleStart, moduleEnd)
|
||||
|
||||
bool moduleReferenced = false;
|
||||
for (;stackIndex < stackSize; ++stackIndex) {
|
||||
uintptr_t pc = rawStack[stackIndex].mPC;
|
||||
if (pc >= moduleEnd)
|
||||
break;
|
||||
|
||||
if (pc >= moduleStart) {
|
||||
// If the current PC is within the current module, mark
|
||||
// module as used
|
||||
moduleReferenced = true;
|
||||
rawStack[stackIndex].mPC -= moduleStart;
|
||||
rawStack[stackIndex].mModIndex = moduleIndex;
|
||||
} else {
|
||||
// PC does not belong to any module. It is probably from
|
||||
// the JIT. Use a fixed mPC so that we don't get different
|
||||
// stacks on different runs.
|
||||
rawStack[stackIndex].mPC =
|
||||
std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleReferenced) {
|
||||
++moduleIndex;
|
||||
} else {
|
||||
// Remove module if no PCs within its address range
|
||||
rawModules.RemoveEntries(moduleIndex, moduleIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (;stackIndex < stackSize; ++stackIndex) {
|
||||
// These PCs are past the last module.
|
||||
rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
|
||||
std::sort(rawStack.begin(), rawStack.end(), CompareByIndex);
|
||||
#endif
|
||||
|
||||
// Copy the information to the return value.
|
||||
ProcessedStack Ret;
|
||||
for (std::vector<StackFrame>::iterator i = rawStack.begin(),
|
||||
|
|
@ -2692,28 +2591,6 @@ GetStackAndModules(const std::vector<uintptr_t>& aPCs)
|
|||
Ret.AddFrame(frame);
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
|
||||
const SharedLibrary &info = rawModules.GetEntry(i);
|
||||
const std::string &name = info.GetName();
|
||||
std::string basename = name;
|
||||
#ifdef XP_MACOSX
|
||||
// FIXME: We want to use just the basename as the libname, but the
|
||||
// current profiler addon needs the full path name, so we compute the
|
||||
// basename in here.
|
||||
size_t pos = name.rfind('/');
|
||||
if (pos != std::string::npos) {
|
||||
basename = name.substr(pos + 1);
|
||||
}
|
||||
#endif
|
||||
mozilla::Telemetry::ProcessedStack::Module module = {
|
||||
basename,
|
||||
info.GetBreakpadId()
|
||||
};
|
||||
Ret.AddModule(module);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
|
|
@ -2910,19 +2787,6 @@ void Init()
|
|||
MOZ_ASSERT(telemetryService);
|
||||
}
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
void RecordChromeHang(uint32_t duration,
|
||||
ProcessedStack &aStack,
|
||||
int32_t aSystemUptime,
|
||||
int32_t aFirefoxUptime,
|
||||
HangAnnotationsPtr aAnnotations)
|
||||
{
|
||||
TelemetryImpl::RecordChromeHang(duration, aStack,
|
||||
aSystemUptime, aFirefoxUptime,
|
||||
Move(aAnnotations));
|
||||
}
|
||||
#endif
|
||||
|
||||
void RecordThreadHangStats(ThreadHangStats& aStats)
|
||||
{
|
||||
TelemetryImpl::RecordThreadHangStats(aStats);
|
||||
|
|
|
|||
|
|
@ -311,25 +311,6 @@ const uint32_t kSlowSQLThresholdForMainThread = 50;
|
|||
const uint32_t kSlowSQLThresholdForHelperThreads = 100;
|
||||
|
||||
class ProcessedStack;
|
||||
|
||||
/**
|
||||
* Record the main thread's call stack after it hangs.
|
||||
*
|
||||
* @param aDuration - Approximate duration of main thread hang, in seconds
|
||||
* @param aStack - Array of PCs from the hung call stack
|
||||
* @param aSystemUptime - System uptime at the time of the hang, in minutes
|
||||
* @param aFirefoxUptime - Firefox uptime at the time of the hang, in minutes
|
||||
* @param aAnnotations - Any annotations to be added to the report
|
||||
*/
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
void RecordChromeHang(uint32_t aDuration,
|
||||
ProcessedStack &aStack,
|
||||
int32_t aSystemUptime,
|
||||
int32_t aFirefoxUptime,
|
||||
mozilla::UniquePtr<mozilla::HangMonitor::HangAnnotations>
|
||||
aAnnotations);
|
||||
#endif
|
||||
|
||||
class ThreadHangStats;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -257,13 +257,6 @@ MOZ_SAFE_BROWSING:
|
|||
false,
|
||||
#endif
|
||||
|
||||
MOZ_ENABLE_PROFILER_SPS:
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
|
||||
MOZ_ANDROID_ACTIVITY_STREAM:
|
||||
#ifdef MOZ_ANDROID_ACTIVITY_STREAM
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -72,9 +72,6 @@ var initTable = [
|
|||
["obs", "@mozilla.org/observer-service;1", "nsIObserverService"],
|
||||
["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
|
||||
["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
["profiler", "@mozilla.org/tools/profiler;1", "nsIProfiler"],
|
||||
#endif
|
||||
["scriptloader", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"],
|
||||
["scriptSecurityManager", "@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager"],
|
||||
#ifdef MOZ_TOOLKIT_SEARCH
|
||||
|
|
|
|||
|
|
@ -22,23 +22,6 @@ def systrace(value, target):
|
|||
|
||||
set_define('MOZ_USE_SYSTRACE', systrace)
|
||||
|
||||
@depends(target)
|
||||
def sps_profiler(target):
|
||||
if target.os == 'Android':
|
||||
return target.cpu in ('arm', 'x86')
|
||||
elif target.kernel == 'Linux':
|
||||
return target.cpu in ('x86', 'x86_64')
|
||||
return target.os in ('OSX', 'WINNT')
|
||||
|
||||
@depends(sps_profiler)
|
||||
def sps_profiler_define(value):
|
||||
if value:
|
||||
return True
|
||||
|
||||
set_config('MOZ_ENABLE_PROFILER_SPS', sps_profiler_define)
|
||||
set_define('MOZ_ENABLE_PROFILER_SPS', sps_profiler_define)
|
||||
|
||||
|
||||
# JACK cubeb backend
|
||||
# ==============================================================
|
||||
option('--enable-jack', env='MOZ_JACK',
|
||||
|
|
|
|||
|
|
@ -1,168 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 "GCHeapProfilerImpl.h"
|
||||
|
||||
#include "UncensoredAllocator.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
GCHeapProfilerImpl::GCHeapProfilerImpl()
|
||||
{
|
||||
mLock = PR_NewLock();
|
||||
mMarking = false;
|
||||
}
|
||||
|
||||
GCHeapProfilerImpl::~GCHeapProfilerImpl()
|
||||
{
|
||||
if (mLock) {
|
||||
PR_DestroyLock(mLock);
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<nsCString>
|
||||
GCHeapProfilerImpl::GetNames() const
|
||||
{
|
||||
return mTraceTable.GetNames();
|
||||
}
|
||||
|
||||
nsTArray<TrieNode>
|
||||
GCHeapProfilerImpl::GetTraces() const
|
||||
{
|
||||
return mTraceTable.GetTraces();
|
||||
}
|
||||
|
||||
const nsTArray<AllocEvent>&
|
||||
GCHeapProfilerImpl::GetEvents() const
|
||||
{
|
||||
return mAllocEvents;
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::reset()
|
||||
{
|
||||
mTraceTable.Reset();
|
||||
mAllocEvents.Clear();
|
||||
mNurseryEntries.Clear();
|
||||
mTenuredEntriesFG.Clear();
|
||||
mTenuredEntriesBG.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sampleTenured(void* addr, uint32_t size)
|
||||
{
|
||||
SampleInternal(addr, size, mTenuredEntriesFG);
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sampleNursery(void* addr, uint32_t size)
|
||||
{
|
||||
SampleInternal(addr, size, mNurseryEntries);
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::markTenuredStart()
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
if (!mMarking) {
|
||||
mMarking = true;
|
||||
mTenuredEntriesFG.SwapElements(mTenuredEntriesBG);
|
||||
MOZ_ASSERT(mTenuredEntriesFG.Count() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::markTenured(void* addr)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
if (mMarking) {
|
||||
AllocEntry entry;
|
||||
if (mTenuredEntriesBG.Get(addr, &entry)) {
|
||||
entry.mMarked = true;
|
||||
mTenuredEntriesBG.Put(addr, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sweepTenured()
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
if (mMarking) {
|
||||
mMarking = false;
|
||||
for (auto iter = mTenuredEntriesBG.Iter(); !iter.Done(); iter.Next()) {
|
||||
if (iter.Data().mMarked) {
|
||||
iter.Data().mMarked = false;
|
||||
mTenuredEntriesFG.Put(iter.Key(), iter.Data());
|
||||
} else {
|
||||
AllocEvent& oldEvent = mAllocEvents[iter.Data().mEventIdx];
|
||||
AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
|
||||
mAllocEvents.AppendElement(newEvent);
|
||||
}
|
||||
}
|
||||
mTenuredEntriesBG.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sweepNursery()
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
for (auto iter = mNurseryEntries.Iter(); !iter.Done(); iter.Next()) {
|
||||
AllocEvent& oldEvent = mAllocEvents[iter.Data().mEventIdx];
|
||||
AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
|
||||
mAllocEvents.AppendElement(newEvent);
|
||||
}
|
||||
mNurseryEntries.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::moveNurseryToTenured(void* addrOld, void* addrNew)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
AllocEntry entryOld;
|
||||
if (!mNurseryEntries.Get(addrOld, &entryOld)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Because the tenured heap is sampled, the address might already be there.
|
||||
// If not, the address is inserted with the old event.
|
||||
AllocEntry tenuredEntryOld;
|
||||
if (!mTenuredEntriesFG.Get(addrNew, &tenuredEntryOld)) {
|
||||
mTenuredEntriesFG.Put(addrNew, AllocEntry(entryOld.mEventIdx));
|
||||
} else {
|
||||
// If it is already inserted, the insertion above will fail and the
|
||||
// iterator of the already-inserted element is returned.
|
||||
// We choose to ignore the the new event by setting its size zero and point
|
||||
// the newly allocated address to the old event.
|
||||
// An event of size zero will be skipped when reporting.
|
||||
mAllocEvents[entryOld.mEventIdx].mSize = 0;
|
||||
tenuredEntryOld.mEventIdx = entryOld.mEventIdx;
|
||||
mTenuredEntriesFG.Put(addrNew, tenuredEntryOld);
|
||||
}
|
||||
mNurseryEntries.Remove(addrOld);
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::SampleInternal(void* aAddr, uint32_t aSize, AllocMap& aTable)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
size_t nSamples = AddBytesSampled(aSize);
|
||||
if (nSamples > 0) {
|
||||
nsTArray<nsCString> trace = GetStacktrace();
|
||||
AllocEvent ai(mTraceTable.Insert(trace), nSamples * mSampleSize, TimeStamp::Now());
|
||||
aTable.Put(aAddr, AllocEntry(mAllocEvents.Length()));
|
||||
mAllocEvents.AppendElement(ai);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 memory_profiler_GCHeapProfilerImpl_h
|
||||
#define memory_profiler_GCHeapProfilerImpl_h
|
||||
|
||||
#include "CompactTraceTable.h"
|
||||
#include "MemoryProfiler.h"
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class GCHeapProfilerImpl final : public GCHeapProfiler
|
||||
, public ProfilerImpl
|
||||
{
|
||||
public:
|
||||
GCHeapProfilerImpl();
|
||||
~GCHeapProfilerImpl() override;
|
||||
|
||||
nsTArray<nsCString> GetNames() const override;
|
||||
nsTArray<TrieNode> GetTraces() const override;
|
||||
const nsTArray<AllocEvent>& GetEvents() const override;
|
||||
|
||||
void reset() override;
|
||||
void sampleTenured(void* addr, uint32_t size) override;
|
||||
void sampleNursery(void* addr, uint32_t size) override;
|
||||
void markTenuredStart() override;
|
||||
void markTenured(void* addr) override;
|
||||
void sweepTenured() override;
|
||||
void sweepNursery() override;
|
||||
void moveNurseryToTenured(void* addrOld, void* addrNew) override;
|
||||
|
||||
private:
|
||||
void SampleInternal(void* addr, uint32_t size, AllocMap& table);
|
||||
|
||||
PRLock* mLock;
|
||||
bool mMarking;
|
||||
|
||||
AllocMap mNurseryEntries;
|
||||
AllocMap mTenuredEntriesFG;
|
||||
AllocMap mTenuredEntriesBG;
|
||||
|
||||
nsTArray<AllocEvent> mAllocEvents;
|
||||
CompactTraceTable mTraceTable;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // memory_profiler_GCHeapProfilerImpl_h
|
||||
|
|
@ -1,324 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 "MemoryProfiler.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include "GCHeapProfilerImpl.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "NativeProfilerImpl.h"
|
||||
#include "UncensoredAllocator.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "nsIDOMClassInfo.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "prtime.h"
|
||||
#include "xpcprivate.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#define MEMORY_PROFILER_SAMPLE_SIZE 65536
|
||||
#define BACKTRACE_BUFFER_SIZE 16384
|
||||
|
||||
ProfilerImpl::ProfilerImpl()
|
||||
: mSampleSize(MEMORY_PROFILER_SAMPLE_SIZE)
|
||||
{
|
||||
mLog1minusP = std::log(1.0 - 1.0 / mSampleSize);
|
||||
mRemainingBytes = std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
|
||||
}
|
||||
|
||||
nsTArray<nsCString>
|
||||
ProfilerImpl::GetStacktrace()
|
||||
{
|
||||
nsTArray<nsCString> trace;
|
||||
auto output = MakeUnique<char[]>(BACKTRACE_BUFFER_SIZE);
|
||||
|
||||
profiler_get_backtrace_noalloc(output.get(), BACKTRACE_BUFFER_SIZE);
|
||||
for (const char* p = output.get(); *p; p += strlen(p) + 1) {
|
||||
trace.AppendElement()->Assign(p);
|
||||
}
|
||||
|
||||
return trace;
|
||||
}
|
||||
|
||||
// Generate a random number in [0, 1).
|
||||
double
|
||||
ProfilerImpl::DRandom()
|
||||
{
|
||||
return double(((uint64_t(std::rand()) & ((1 << 26) - 1)) << 27) +
|
||||
(uint64_t(std::rand()) & ((1 << 27) - 1)))
|
||||
/ (uint64_t(1) << 53);
|
||||
}
|
||||
|
||||
size_t
|
||||
ProfilerImpl::AddBytesSampled(uint32_t aBytes)
|
||||
{
|
||||
size_t nSamples = 0;
|
||||
while (mRemainingBytes <= aBytes) {
|
||||
mRemainingBytes += std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
|
||||
nSamples++;
|
||||
}
|
||||
mRemainingBytes -= aBytes;
|
||||
return nSamples;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(MemoryProfiler, nsIMemoryProfiler)
|
||||
|
||||
PRLock* MemoryProfiler::sLock;
|
||||
uint32_t MemoryProfiler::sProfileContextCount;
|
||||
StaticAutoPtr<NativeProfilerImpl> MemoryProfiler::sNativeProfiler;
|
||||
StaticAutoPtr<JSContextProfilerMap> MemoryProfiler::sJSContextProfilerMap;
|
||||
TimeStamp MemoryProfiler::sStartTime;
|
||||
|
||||
void
|
||||
MemoryProfiler::InitOnce()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized) {
|
||||
MallocHook::Initialize();
|
||||
sLock = PR_NewLock();
|
||||
sProfileContextCount = 0;
|
||||
sJSContextProfilerMap = new JSContextProfilerMap();
|
||||
ClearOnShutdown(&sJSContextProfilerMap);
|
||||
ClearOnShutdown(&sNativeProfiler);
|
||||
std::srand(PR_Now());
|
||||
bool ignored;
|
||||
sStartTime = TimeStamp::ProcessCreation(ignored);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::StartProfiler()
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
ProfilerForJSContext profiler;
|
||||
if (!sJSContextProfilerMap->Get(context, &profiler) ||
|
||||
!profiler.mEnabled) {
|
||||
if (sProfileContextCount == 0) {
|
||||
js::EnableContextProfilingStack(context, true);
|
||||
if (!sNativeProfiler) {
|
||||
sNativeProfiler = new NativeProfilerImpl();
|
||||
}
|
||||
MemProfiler::SetNativeProfiler(sNativeProfiler);
|
||||
}
|
||||
GCHeapProfilerImpl* gp = new GCHeapProfilerImpl();
|
||||
profiler.mEnabled = true;
|
||||
profiler.mProfiler = gp;
|
||||
sJSContextProfilerMap->Put(context, profiler);
|
||||
MemProfiler::GetMemProfiler(context)->start(gp);
|
||||
if (sProfileContextCount == 0) {
|
||||
MallocHook::Enable(sNativeProfiler);
|
||||
}
|
||||
sProfileContextCount++;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::StopProfiler()
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
ProfilerForJSContext profiler;
|
||||
if (sJSContextProfilerMap->Get(context, &profiler) &&
|
||||
profiler.mEnabled) {
|
||||
MemProfiler::GetMemProfiler(context)->stop();
|
||||
if (--sProfileContextCount == 0) {
|
||||
MallocHook::Disable();
|
||||
MemProfiler::SetNativeProfiler(nullptr);
|
||||
js::EnableContextProfilingStack(context, false);
|
||||
}
|
||||
profiler.mEnabled = false;
|
||||
sJSContextProfilerMap->Put(context, profiler);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::ResetProfiler()
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
ProfilerForJSContext profiler;
|
||||
if (!sJSContextProfilerMap->Get(context, &profiler) ||
|
||||
!profiler.mEnabled) {
|
||||
delete profiler.mProfiler;
|
||||
profiler.mProfiler = nullptr;
|
||||
sJSContextProfilerMap->Put(context, profiler);
|
||||
}
|
||||
if (sProfileContextCount == 0) {
|
||||
sNativeProfiler = nullptr;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
struct MergedTraces
|
||||
{
|
||||
nsTArray<nsCString> mNames;
|
||||
nsTArray<TrieNode> mTraces;
|
||||
nsTArray<AllocEvent> mEvents;
|
||||
};
|
||||
|
||||
// Merge events and corresponding traces and names.
|
||||
static MergedTraces
|
||||
MergeResults(const nsTArray<nsCString>& names0,
|
||||
const nsTArray<TrieNode>& traces0,
|
||||
const nsTArray<AllocEvent>& events0,
|
||||
const nsTArray<nsCString>& names1,
|
||||
const nsTArray<TrieNode>& traces1,
|
||||
const nsTArray<AllocEvent>& events1)
|
||||
{
|
||||
NodeIndexMap<nsCStringHashKey, nsCString> names;
|
||||
NodeIndexMap<nsGenericHashKey<TrieNode>, TrieNode> traces;
|
||||
nsTArray<AllocEvent> events;
|
||||
|
||||
nsTArray<size_t> names1Tonames0(names1.Length());
|
||||
nsTArray<size_t> traces1Totraces0(traces1.Length());
|
||||
|
||||
// Merge names.
|
||||
for (auto& i: names0) {
|
||||
names.Insert(i);
|
||||
}
|
||||
for (auto& i: names1) {
|
||||
names1Tonames0.AppendElement(names.Insert(i));
|
||||
}
|
||||
|
||||
// Merge traces. Note that traces1[i].parentIdx < i for all i > 0.
|
||||
for (auto& i: traces0) {
|
||||
traces.Insert(i);
|
||||
}
|
||||
traces1Totraces0.AppendElement(0);
|
||||
for (size_t i = 1; i < traces1.Length(); i++) {
|
||||
TrieNode node = traces1[i];
|
||||
node.parentIdx = traces1Totraces0[node.parentIdx];
|
||||
node.nameIdx = names1Tonames0[node.nameIdx];
|
||||
traces1Totraces0.AppendElement(traces.Insert(node));
|
||||
}
|
||||
|
||||
// Merge the events according to timestamps.
|
||||
auto p0 = events0.begin();
|
||||
auto p1 = events1.begin();
|
||||
|
||||
while (p0 != events0.end() && p1 != events1.end()) {
|
||||
if (p0->mTimestamp < p1->mTimestamp) {
|
||||
events.AppendElement(*p0++);
|
||||
} else {
|
||||
events.AppendElement(*p1++);
|
||||
events.LastElement().mTraceIdx =
|
||||
traces1Totraces0[events.LastElement().mTraceIdx];
|
||||
}
|
||||
}
|
||||
|
||||
while (p0 != events0.end()) {
|
||||
events.AppendElement(*p0++);
|
||||
}
|
||||
|
||||
while (p1 != events1.end()) {
|
||||
events.AppendElement(*p1++);
|
||||
events.LastElement().mTraceIdx =
|
||||
traces1Totraces0[events.LastElement().mTraceIdx];
|
||||
}
|
||||
|
||||
return MergedTraces{names.Serialize(), traces.Serialize(), Move(events)};
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::GetResults(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
// Getting results when the profiler is running is not allowed.
|
||||
if (sProfileContextCount > 0) {
|
||||
return NS_OK;
|
||||
}
|
||||
// Return immediately when native profiler does not exist.
|
||||
if (!sNativeProfiler) {
|
||||
return NS_OK;
|
||||
}
|
||||
// Return immediately when there's no result in current context.
|
||||
ProfilerForJSContext profiler;
|
||||
if (!sJSContextProfilerMap->Get(context, &profiler) ||
|
||||
!profiler.mProfiler) {
|
||||
return NS_OK;
|
||||
}
|
||||
GCHeapProfilerImpl* gp = profiler.mProfiler;
|
||||
|
||||
auto results = MergeResults(gp->GetNames(), gp->GetTraces(), gp->GetEvents(),
|
||||
sNativeProfiler->GetNames(),
|
||||
sNativeProfiler->GetTraces(),
|
||||
sNativeProfiler->GetEvents());
|
||||
const nsTArray<nsCString>& names = results.mNames;
|
||||
const nsTArray<TrieNode>& traces = results.mTraces;
|
||||
const nsTArray<AllocEvent>& events = results.mEvents;
|
||||
|
||||
JS::RootedObject jsnames(cx, JS_NewArrayObject(cx, names.Length()));
|
||||
JS::RootedObject jstraces(cx, JS_NewArrayObject(cx, traces.Length()));
|
||||
JS::RootedObject jsevents(cx, JS_NewArrayObject(cx, events.Length()));
|
||||
|
||||
for (size_t i = 0; i < names.Length(); i++) {
|
||||
JS::RootedString name(cx, JS_NewStringCopyZ(cx, names[i].get()));
|
||||
JS_SetElement(cx, jsnames, i, name);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < traces.Length(); i++) {
|
||||
JS::RootedObject tn(cx, JS_NewPlainObject(cx));
|
||||
JS::RootedValue nameIdx(cx, JS_NumberValue(traces[i].nameIdx));
|
||||
JS::RootedValue parentIdx(cx, JS_NumberValue(traces[i].parentIdx));
|
||||
JS_SetProperty(cx, tn, "nameIdx", nameIdx);
|
||||
JS_SetProperty(cx, tn, "parentIdx", parentIdx);
|
||||
JS_SetElement(cx, jstraces, i, tn);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (auto ent: events) {
|
||||
if (ent.mSize == 0) {
|
||||
continue;
|
||||
}
|
||||
MOZ_ASSERT(!sStartTime.IsNull());
|
||||
double time = (ent.mTimestamp - sStartTime).ToMilliseconds();
|
||||
JS::RootedObject tn(cx, JS_NewPlainObject(cx));
|
||||
JS::RootedValue size(cx, JS_NumberValue(ent.mSize));
|
||||
JS::RootedValue traceIdx(cx, JS_NumberValue(ent.mTraceIdx));
|
||||
JS::RootedValue timestamp(cx, JS_NumberValue(time));
|
||||
JS_SetProperty(cx, tn, "size", size);
|
||||
JS_SetProperty(cx, tn, "traceIdx", traceIdx);
|
||||
JS_SetProperty(cx, tn, "timestamp", timestamp);
|
||||
JS_SetElement(cx, jsevents, i++, tn);
|
||||
}
|
||||
JS_SetArrayLength(cx, jsevents, i);
|
||||
|
||||
JS::RootedObject result(cx, JS_NewPlainObject(cx));
|
||||
JS::RootedValue objnames(cx, ObjectOrNullValue(jsnames));
|
||||
JS_SetProperty(cx, result, "names", objnames);
|
||||
JS::RootedValue objtraces(cx, ObjectOrNullValue(jstraces));
|
||||
JS_SetProperty(cx, result, "traces", objtraces);
|
||||
JS::RootedValue objevents(cx, ObjectOrNullValue(jsevents));
|
||||
JS_SetProperty(cx, result, "events", objevents);
|
||||
aResult.setObject(*result);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 tools_profiler_MemoryProfiler_h
|
||||
#define tools_profiler_MemoryProfiler_h
|
||||
|
||||
#include "nsIMemoryProfiler.h"
|
||||
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
#include "CompactTraceTable.h"
|
||||
#include "nsTArray.h"
|
||||
#include "prlock.h"
|
||||
|
||||
#define MEMORY_PROFILER_CID \
|
||||
{ 0xf976eaa2, 0xcc1f, 0x47ee, \
|
||||
{ 0x81, 0x29, 0xb8, 0x26, 0x2a, 0x3d, 0xb6, 0xb2 } }
|
||||
|
||||
#define MEMORY_PROFILER_CONTRACT_ID "@mozilla.org/tools/memory-profiler;1"
|
||||
|
||||
struct PRLock;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class NativeProfilerImpl;
|
||||
class GCHeapProfilerImpl;
|
||||
|
||||
struct ProfilerForJSContext
|
||||
{
|
||||
ProfilerForJSContext()
|
||||
: mProfiler(nullptr)
|
||||
, mEnabled(false)
|
||||
{}
|
||||
GCHeapProfilerImpl* mProfiler;
|
||||
bool mEnabled;
|
||||
};
|
||||
using JSContextProfilerMap =
|
||||
nsDataHashtable<nsClearingPtrHashKey<JSContext>, ProfilerForJSContext>;
|
||||
|
||||
class MemoryProfiler final : public nsIMemoryProfiler
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMEMORYPROFILER
|
||||
|
||||
private:
|
||||
static void InitOnce();
|
||||
~MemoryProfiler() {}
|
||||
|
||||
// The accesses to other static member are guarded by sLock and
|
||||
// sProfileContextCount.
|
||||
static PRLock* sLock;
|
||||
static uint32_t sProfileContextCount;
|
||||
|
||||
static StaticAutoPtr<NativeProfilerImpl> sNativeProfiler;
|
||||
static StaticAutoPtr<JSContextProfilerMap> sJSContextProfilerMap;
|
||||
static TimeStamp sStartTime;
|
||||
};
|
||||
|
||||
// Allocation events to be reported.
|
||||
struct AllocEvent {
|
||||
TimeStamp mTimestamp;
|
||||
// index to a stacktrace singleton.
|
||||
uint32_t mTraceIdx;
|
||||
// Allocation size
|
||||
int32_t mSize;
|
||||
|
||||
AllocEvent(uint32_t aTraceIdx, int32_t aSize, TimeStamp aTimestamp)
|
||||
: mTimestamp(aTimestamp)
|
||||
, mTraceIdx(aTraceIdx)
|
||||
, mSize(aSize)
|
||||
{}
|
||||
};
|
||||
|
||||
// Index to allocation events but also a mark bit to be GC-able.
|
||||
struct AllocEntry {
|
||||
uint32_t mEventIdx : 31;
|
||||
bool mMarked : 1;
|
||||
|
||||
// Default constructor for uninitialized stack value required by
|
||||
// getter methods.
|
||||
AllocEntry()
|
||||
: mEventIdx(0)
|
||||
, mMarked(false)
|
||||
{}
|
||||
explicit AllocEntry(int aEventIdx)
|
||||
: mEventIdx(aEventIdx)
|
||||
, mMarked(false)
|
||||
{}
|
||||
};
|
||||
|
||||
using AllocMap = nsDataHashtable<nsClearingVoidPtrHashKey, AllocEntry>;
|
||||
|
||||
class ProfilerImpl
|
||||
{
|
||||
public:
|
||||
static nsTArray<nsCString> GetStacktrace();
|
||||
static double DRandom();
|
||||
|
||||
ProfilerImpl();
|
||||
virtual nsTArray<nsCString> GetNames() const = 0;
|
||||
virtual nsTArray<TrieNode> GetTraces() const = 0;
|
||||
virtual const nsTArray<AllocEvent>& GetEvents() const = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The sampler generates a random variable which conforms to a geometric
|
||||
* distribution of probability p = 1 / mSampleSize to calculate the
|
||||
* next-to-be-sampled byte directly; It avoids rolling a dice on each byte.
|
||||
*
|
||||
* Let Bn denote a Bernoulli process with first success on n-th trial, the
|
||||
* cumulative distribution function of Bn is Cn = 1 - (1 - p) ^ n.
|
||||
* Let U denote a uniformly distributed random variable in [0, 1).
|
||||
* A geometric random variable can be generated by Cn's reverse function:
|
||||
* G = floor(log(1 - U) / log(1 - p)).
|
||||
*
|
||||
* @param aSize the number of bytes seen
|
||||
* @return the number of events sampled
|
||||
*/
|
||||
size_t AddBytesSampled(uint32_t aBytes);
|
||||
|
||||
uint32_t mSampleSize;
|
||||
|
||||
private:
|
||||
uint32_t mRemainingBytes;
|
||||
double mLog1minusP;
|
||||
};
|
||||
|
||||
/*
|
||||
* This class is used to make sure the profile data is only accessed
|
||||
* on one thread at a time. Don't use mozilla::Mutex because we don't
|
||||
* want to allocate memory.
|
||||
*/
|
||||
class AutoMPLock
|
||||
{
|
||||
public:
|
||||
explicit AutoMPLock(PRLock* aLock)
|
||||
{
|
||||
MOZ_ASSERT(aLock);
|
||||
mLock = aLock;
|
||||
PR_Lock(mLock);
|
||||
}
|
||||
|
||||
~AutoMPLock()
|
||||
{
|
||||
PR_Unlock(mLock);
|
||||
}
|
||||
|
||||
private:
|
||||
PRLock* mLock;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 "NativeProfilerImpl.h"
|
||||
|
||||
#include "UncensoredAllocator.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NativeProfilerImpl::NativeProfilerImpl()
|
||||
{
|
||||
mLock = PR_NewLock();
|
||||
}
|
||||
|
||||
NativeProfilerImpl::~NativeProfilerImpl()
|
||||
{
|
||||
if (mLock) {
|
||||
PR_DestroyLock(mLock);
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<nsCString>
|
||||
NativeProfilerImpl::GetNames() const
|
||||
{
|
||||
return mTraceTable.GetNames();
|
||||
}
|
||||
|
||||
nsTArray<TrieNode>
|
||||
NativeProfilerImpl::GetTraces() const
|
||||
{
|
||||
return mTraceTable.GetTraces();
|
||||
}
|
||||
|
||||
const nsTArray<AllocEvent>&
|
||||
NativeProfilerImpl::GetEvents() const
|
||||
{
|
||||
return mAllocEvents;
|
||||
}
|
||||
|
||||
void
|
||||
NativeProfilerImpl::reset()
|
||||
{
|
||||
mTraceTable.Reset();
|
||||
mAllocEvents.Clear();
|
||||
mNativeEntries.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
NativeProfilerImpl::sampleNative(void* addr, uint32_t size)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
size_t nSamples = AddBytesSampled(size);
|
||||
if (nSamples > 0) {
|
||||
nsTArray<nsCString> trace = GetStacktrace();
|
||||
AllocEvent ai(mTraceTable.Insert(trace), nSamples * mSampleSize, TimeStamp::Now());
|
||||
mNativeEntries.Put(addr, AllocEntry(mAllocEvents.Length()));
|
||||
mAllocEvents.AppendElement(ai);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NativeProfilerImpl::removeNative(void* addr)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
|
||||
AllocEntry entry;
|
||||
if (!mNativeEntries.Get(addr, &entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AllocEvent& oldEvent = mAllocEvents[entry.mEventIdx];
|
||||
AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
|
||||
mAllocEvents.AppendElement(newEvent);
|
||||
mNativeEntries.Remove(addr);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 memory_profiler_NativeProfilerImpl_h
|
||||
#define memory_profiler_NativeProfilerImpl_h
|
||||
|
||||
#include "CompactTraceTable.h"
|
||||
#include "MemoryProfiler.h"
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
struct PRLock;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class NativeProfilerImpl final : public NativeProfiler
|
||||
, public ProfilerImpl
|
||||
{
|
||||
public:
|
||||
NativeProfilerImpl();
|
||||
~NativeProfilerImpl() override;
|
||||
|
||||
nsTArray<nsCString> GetNames() const override;
|
||||
nsTArray<TrieNode> GetTraces() const override;
|
||||
const nsTArray<AllocEvent>& GetEvents() const override;
|
||||
|
||||
void reset() override;
|
||||
void sampleNative(void* addr, uint32_t size) override;
|
||||
void removeNative(void* addr) override;
|
||||
|
||||
private:
|
||||
PRLock* mLock;
|
||||
AllocMap mNativeEntries;
|
||||
nsTArray<AllocEvent> mAllocEvents;
|
||||
CompactTraceTable mTraceTable;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // memory_profiler_NativeProfilerImpl_h
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 "UncensoredAllocator.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "MainThreadUtils.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "nsDebug.h"
|
||||
#include "prlock.h"
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
#include "replace_malloc_bridge.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_THREAD_LOCAL(bool) MallocHook::mEnabledTLS;
|
||||
NativeProfiler* MallocHook::mNativeProfiler;
|
||||
malloc_hook_table_t MallocHook::mMallocHook;
|
||||
#endif
|
||||
|
||||
AutoUseUncensoredAllocator::AutoUseUncensoredAllocator()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MallocHook::mEnabledTLS.set(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
AutoUseUncensoredAllocator::~AutoUseUncensoredAllocator()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MallocHook::mEnabledTLS.set(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
MallocHook::Enabled()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
return mEnabledTLS.get() && mNativeProfiler;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void*
|
||||
MallocHook::SampleNative(void* aAddr, size_t aSize)
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
if (MallocHook::Enabled()) {
|
||||
mNativeProfiler->sampleNative(aAddr, aSize);
|
||||
}
|
||||
#endif
|
||||
return aAddr;
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::RemoveNative(void* aAddr)
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
if (MallocHook::Enabled()) {
|
||||
mNativeProfiler->removeNative(aAddr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::Initialize()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mMallocHook.free_hook = RemoveNative;
|
||||
mMallocHook.malloc_hook = SampleNative;
|
||||
ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
|
||||
if (bridge) {
|
||||
mozilla::Unused << bridge->RegisterHook("memory-profiler", nullptr, nullptr);
|
||||
}
|
||||
|
||||
bool success = mEnabledTLS.init();
|
||||
if (NS_WARN_IF(!success)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::Enable(NativeProfiler* aNativeProfiler)
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
|
||||
if (bridge) {
|
||||
const malloc_table_t* alloc_funcs =
|
||||
bridge->RegisterHook("memory-profiler", nullptr, &mMallocHook);
|
||||
if (alloc_funcs) {
|
||||
mNativeProfiler = aNativeProfiler;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::Disable()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
|
||||
if (bridge) {
|
||||
bridge->RegisterHook("memory-profiler", nullptr, nullptr);
|
||||
mNativeProfiler = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 memory_profiler_UncensoredAllocator_h
|
||||
#define memory_profiler_UncensoredAllocator_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
#include "replace_malloc_bridge.h"
|
||||
#endif
|
||||
|
||||
class NativeProfiler;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MallocHook final
|
||||
{
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Enable(NativeProfiler* aNativeProfiler);
|
||||
static void Disable();
|
||||
static bool Enabled();
|
||||
private:
|
||||
static void* SampleNative(void* aAddr, size_t aSize);
|
||||
static void RemoveNative(void* aAddr);
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
static MOZ_THREAD_LOCAL(bool) mEnabledTLS;
|
||||
static NativeProfiler* mNativeProfiler;
|
||||
static malloc_hook_table_t mMallocHook;
|
||||
#endif
|
||||
friend class AutoUseUncensoredAllocator;
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoUseUncensoredAllocator final
|
||||
{
|
||||
public:
|
||||
AutoUseUncensoredAllocator();
|
||||
~AutoUseUncensoredAllocator();
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // memory_profiler_UncensoredAllocator_h
|
||||
|
|
@ -4,26 +4,5 @@
|
|||
# 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/.
|
||||
|
||||
if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
|
||||
XPIDL_MODULE = 'memory_profiler'
|
||||
XPIDL_SOURCES += [
|
||||
'nsIMemoryProfiler.idl',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'GCHeapProfilerImpl.cpp',
|
||||
'MemoryProfiler.cpp',
|
||||
'NativeProfilerImpl.cpp',
|
||||
'nsMemoryProfilerFactory.cpp',
|
||||
'UncensoredAllocator.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/js/xpconnect/src',
|
||||
'/xpcom/base',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
/* -*- Mode: IDL; 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 "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* The memory profiler samples allocation events. An allocation event
|
||||
* includes a type (what and at where is going to be allocated), a
|
||||
* size, a timestamp and the corresponding stack trace. Free events
|
||||
* are also tracked. For managed languages, namely languages relying
|
||||
* on garbage collection, a free event is generated when an object is
|
||||
* reclaimed by the garbage collector. These sampled events can be
|
||||
* used to approximate the full history of allocations afterwards.
|
||||
* That means we can get various memory profiles of a program in
|
||||
* different perspectives by post-processing the history in different
|
||||
* ways. The profiler is designed at the very beginning to support not
|
||||
* only JavaScript but also native codes. Naturally, not only
|
||||
* JavaScript objects but also native allocations are tracked.
|
||||
*
|
||||
* The result returned is the sampled allocation event traces in a
|
||||
* compact format. The events is sorted according to the timestamp
|
||||
* when the event happened. Each event has a trace index pointing to
|
||||
* the traces table. Each trace entry has a name index pointing to the
|
||||
* names table and a parent index pointing to the parent trace in the
|
||||
* traces table. By following the trace index one could rebuild the
|
||||
* complete backtrace of an allocation event.
|
||||
*
|
||||
* [ Events ]
|
||||
* +-------+-------+ +-------+
|
||||
* | Size | Size | | Size |
|
||||
* |-------|-------| |-------|
|
||||
* | Time | Time |......| Time |
|
||||
* |-------|-------| |-------|
|
||||
* +-- Trace | Trace | | Trace |
|
||||
* | +-------+-------+ +-------+
|
||||
* |
|
||||
* | [ Traces ]
|
||||
* +->--------+--------+ +--------+ +--------+
|
||||
* -| Name | Name | | Name | | Name |
|
||||
* / |--------|--------|...|--------|...|--------|
|
||||
* | | Parent | Parent | | Parent | | Parent |
|
||||
* | +---|----+----^--++ +--^--|--+ +---^----+
|
||||
* | | | | | | |
|
||||
* | +---------+ +-------+ +----------+
|
||||
* | [ Names ]
|
||||
* | +-----------------+-----------------+
|
||||
* +-> Function name | Function name |
|
||||
* | & line numbers | & line numbers |......
|
||||
* +-----------------+-----------------+
|
||||
*
|
||||
*/
|
||||
[scriptable, uuid(1e10e7a9-bc05-4878-a687-36c9ea4428b1)]
|
||||
interface nsIMemoryProfiler : nsISupports
|
||||
{
|
||||
void startProfiler();
|
||||
void stopProfiler();
|
||||
void resetProfiler();
|
||||
|
||||
/**
|
||||
* Get results in an object which contains three tables:
|
||||
* {
|
||||
* names, // an array of function names and positions
|
||||
* traces, // an array of {nameIdx, parentIdx}
|
||||
* events, // an array of {size, timestamp, traceIdx}
|
||||
* }
|
||||
* Should only be called after stopProfiler.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
jsval getResults();
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 20; 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/ModuleUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "MemoryProfiler.h"
|
||||
|
||||
using mozilla::MemoryProfiler;
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(MemoryProfiler)
|
||||
|
||||
NS_DEFINE_NAMED_CID(MEMORY_PROFILER_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kMemoryProfilerCIDs[] = {
|
||||
{ &kMEMORY_PROFILER_CID, false, nullptr, MemoryProfilerConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kMemoryProfilerContracts[] = {
|
||||
{ MEMORY_PROFILER_CONTRACT_ID, &kMEMORY_PROFILER_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module kMemoryProfilerModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kMemoryProfilerCIDs,
|
||||
kMemoryProfilerContracts
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsMemoryProfilerModule) = &kMemoryProfilerModule;
|
||||
|
|
@ -1,678 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* This is an implementation of stack unwinding according to a subset
|
||||
* of the ARM Exception Handling ABI, as described in:
|
||||
* http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf
|
||||
*
|
||||
* This handles only the ARM-defined "personality routines" (chapter
|
||||
* 9), and don't track the value of FP registers, because profiling
|
||||
* needs only chain of PC/SP values.
|
||||
*
|
||||
* Because the exception handling info may not be accurate for all
|
||||
* possible places where an async signal could occur (e.g., in a
|
||||
* prologue or epilogue), this bounds-checks all stack accesses.
|
||||
*
|
||||
* This file uses "struct" for structures in the exception tables and
|
||||
* "class" otherwise. We should avoid violating the C++11
|
||||
* standard-layout rules in the former.
|
||||
*/
|
||||
|
||||
#include "EHABIStackWalk.h"
|
||||
|
||||
#include "shared-libraries.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <elf.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#ifndef PT_ARM_EXIDX
|
||||
#define PT_ARM_EXIDX 0x70000001
|
||||
#endif
|
||||
|
||||
// Bug 1082817: ICS B2G has a buggy linker that doesn't always ensure
|
||||
// that the EXIDX is sorted by address, as the spec requires. So in
|
||||
// that case we build and sort an array of pointers into the index,
|
||||
// and binary-search that; otherwise, we search the index in place
|
||||
// (avoiding the time and space overhead of the indirection).
|
||||
#if defined(ANDROID_VERSION) && ANDROID_VERSION < 16
|
||||
#define HAVE_UNSORTED_EXIDX
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
struct PRel31 {
|
||||
uint32_t mBits;
|
||||
bool topBit() const { return mBits & 0x80000000; }
|
||||
uint32_t value() const { return mBits & 0x7fffffff; }
|
||||
int32_t offset() const { return (static_cast<int32_t>(mBits) << 1) >> 1; }
|
||||
const void *compute() const {
|
||||
return reinterpret_cast<const char *>(this) + offset();
|
||||
}
|
||||
private:
|
||||
PRel31(const PRel31 &copied) = delete;
|
||||
PRel31() = delete;
|
||||
};
|
||||
|
||||
struct EHEntry {
|
||||
PRel31 startPC;
|
||||
PRel31 exidx;
|
||||
private:
|
||||
EHEntry(const EHEntry &copied) = delete;
|
||||
EHEntry() = delete;
|
||||
};
|
||||
|
||||
class EHState {
|
||||
// Note that any core register can be used as a "frame pointer" to
|
||||
// influence the unwinding process, so this must track all of them.
|
||||
uint32_t mRegs[16];
|
||||
public:
|
||||
bool unwind(const EHEntry *aEntry, const void *stackBase);
|
||||
uint32_t &operator[](int i) { return mRegs[i]; }
|
||||
const uint32_t &operator[](int i) const { return mRegs[i]; }
|
||||
EHState(const mcontext_t &);
|
||||
};
|
||||
|
||||
enum {
|
||||
R_SP = 13,
|
||||
R_LR = 14,
|
||||
R_PC = 15
|
||||
};
|
||||
|
||||
#ifdef HAVE_UNSORTED_EXIDX
|
||||
class EHEntryHandle {
|
||||
const EHEntry *mValue;
|
||||
public:
|
||||
EHEntryHandle(const EHEntry *aEntry) : mValue(aEntry) { }
|
||||
const EHEntry *value() const { return mValue; }
|
||||
};
|
||||
|
||||
bool operator<(const EHEntryHandle &lhs, const EHEntryHandle &rhs) {
|
||||
return lhs.value()->startPC.compute() < rhs.value()->startPC.compute();
|
||||
}
|
||||
#endif
|
||||
|
||||
class EHTable {
|
||||
uint32_t mStartPC;
|
||||
uint32_t mEndPC;
|
||||
uint32_t mLoadOffset;
|
||||
#ifdef HAVE_UNSORTED_EXIDX
|
||||
// In principle we should be able to binary-search the index section in
|
||||
// place, but the ICS toolchain's linker is noncompliant and produces
|
||||
// indices that aren't entirely sorted (e.g., libc). So we have this:
|
||||
std::vector<EHEntryHandle> mEntries;
|
||||
typedef std::vector<EHEntryHandle>::const_iterator EntryIterator;
|
||||
EntryIterator entriesBegin() const { return mEntries.begin(); }
|
||||
EntryIterator entriesEnd() const { return mEntries.end(); }
|
||||
static const EHEntry* entryGet(EntryIterator aEntry) {
|
||||
return aEntry->value();
|
||||
}
|
||||
#else
|
||||
typedef const EHEntry *EntryIterator;
|
||||
EntryIterator mEntriesBegin, mEntriesEnd;
|
||||
EntryIterator entriesBegin() const { return mEntriesBegin; }
|
||||
EntryIterator entriesEnd() const { return mEntriesEnd; }
|
||||
static const EHEntry* entryGet(EntryIterator aEntry) { return aEntry; }
|
||||
#endif
|
||||
std::string mName;
|
||||
public:
|
||||
EHTable(const void *aELF, size_t aSize, const std::string &aName);
|
||||
const EHEntry *lookup(uint32_t aPC) const;
|
||||
bool isValid() const { return entriesEnd() != entriesBegin(); }
|
||||
const std::string &name() const { return mName; }
|
||||
uint32_t startPC() const { return mStartPC; }
|
||||
uint32_t endPC() const { return mEndPC; }
|
||||
uint32_t loadOffset() const { return mLoadOffset; }
|
||||
};
|
||||
|
||||
class EHAddrSpace {
|
||||
std::vector<uint32_t> mStarts;
|
||||
std::vector<EHTable> mTables;
|
||||
static mozilla::Atomic<const EHAddrSpace*> sCurrent;
|
||||
public:
|
||||
explicit EHAddrSpace(const std::vector<EHTable>& aTables);
|
||||
const EHTable *lookup(uint32_t aPC) const;
|
||||
static void Update();
|
||||
static const EHAddrSpace *Get();
|
||||
};
|
||||
|
||||
|
||||
void EHABIStackWalkInit()
|
||||
{
|
||||
EHAddrSpace::Update();
|
||||
}
|
||||
|
||||
size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase,
|
||||
void **aSPs, void **aPCs, const size_t aNumFrames)
|
||||
{
|
||||
const EHAddrSpace *space = EHAddrSpace::Get();
|
||||
EHState state(aContext);
|
||||
size_t count = 0;
|
||||
|
||||
while (count < aNumFrames) {
|
||||
uint32_t pc = state[R_PC], sp = state[R_SP];
|
||||
aPCs[count] = reinterpret_cast<void *>(pc);
|
||||
aSPs[count] = reinterpret_cast<void *>(sp);
|
||||
count++;
|
||||
|
||||
if (!space)
|
||||
break;
|
||||
// TODO: cache these lookups. Binary-searching libxul is
|
||||
// expensive (possibly more expensive than doing the actual
|
||||
// unwind), and even a small cache should help.
|
||||
const EHTable *table = space->lookup(pc);
|
||||
if (!table)
|
||||
break;
|
||||
const EHEntry *entry = table->lookup(pc);
|
||||
if (!entry)
|
||||
break;
|
||||
if (!state.unwind(entry, stackBase))
|
||||
break;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
class EHInterp {
|
||||
public:
|
||||
// Note that stackLimit is exclusive and stackBase is inclusive
|
||||
// (i.e, stackLimit < SP <= stackBase), following the convention
|
||||
// set by the AAPCS spec.
|
||||
EHInterp(EHState &aState, const EHEntry *aEntry,
|
||||
uint32_t aStackLimit, uint32_t aStackBase)
|
||||
: mState(aState),
|
||||
mStackLimit(aStackLimit),
|
||||
mStackBase(aStackBase),
|
||||
mNextWord(0),
|
||||
mWordsLeft(0),
|
||||
mFailed(false)
|
||||
{
|
||||
const PRel31 &exidx = aEntry->exidx;
|
||||
uint32_t firstWord;
|
||||
|
||||
if (exidx.mBits == 1) { // EXIDX_CANTUNWIND
|
||||
mFailed = true;
|
||||
return;
|
||||
}
|
||||
if (exidx.topBit()) {
|
||||
firstWord = exidx.mBits;
|
||||
} else {
|
||||
mNextWord = reinterpret_cast<const uint32_t *>(exidx.compute());
|
||||
firstWord = *mNextWord++;
|
||||
}
|
||||
|
||||
switch (firstWord >> 24) {
|
||||
case 0x80: // short
|
||||
mWord = firstWord << 8;
|
||||
mBytesLeft = 3;
|
||||
break;
|
||||
case 0x81: case 0x82: // long; catch descriptor size ignored
|
||||
mWord = firstWord << 16;
|
||||
mBytesLeft = 2;
|
||||
mWordsLeft = (firstWord >> 16) & 0xff;
|
||||
break;
|
||||
default:
|
||||
// unknown personality
|
||||
mFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool unwind();
|
||||
|
||||
private:
|
||||
// TODO: GCC has been observed not CSEing repeated reads of
|
||||
// mState[R_SP] with writes to mFailed between them, suggesting that
|
||||
// it hasn't determined that they can't alias and is thus missing
|
||||
// optimization opportunities. So, we may want to flatten EHState
|
||||
// into this class; this may also make the code simpler.
|
||||
EHState &mState;
|
||||
uint32_t mStackLimit;
|
||||
uint32_t mStackBase;
|
||||
const uint32_t *mNextWord;
|
||||
uint32_t mWord;
|
||||
uint8_t mWordsLeft;
|
||||
uint8_t mBytesLeft;
|
||||
bool mFailed;
|
||||
|
||||
enum {
|
||||
I_ADDSP = 0x00, // 0sxxxxxx (subtract if s)
|
||||
M_ADDSP = 0x80,
|
||||
I_POPMASK = 0x80, // 1000iiii iiiiiiii (if any i set)
|
||||
M_POPMASK = 0xf0,
|
||||
I_MOVSP = 0x90, // 1001nnnn
|
||||
M_MOVSP = 0xf0,
|
||||
I_POPN = 0xa0, // 1010lnnn
|
||||
M_POPN = 0xf0,
|
||||
I_FINISH = 0xb0, // 10110000
|
||||
I_POPLO = 0xb1, // 10110001 0000iiii (if any i set)
|
||||
I_ADDSPBIG = 0xb2, // 10110010 uleb128
|
||||
I_POPFDX = 0xb3, // 10110011 sssscccc
|
||||
I_POPFDX8 = 0xb8, // 10111nnn
|
||||
M_POPFDX8 = 0xf8,
|
||||
// "Intel Wireless MMX" extensions omitted.
|
||||
I_POPFDD = 0xc8, // 1100100h sssscccc
|
||||
M_POPFDD = 0xfe,
|
||||
I_POPFDD8 = 0xd0, // 11010nnn
|
||||
M_POPFDD8 = 0xf8
|
||||
};
|
||||
|
||||
uint8_t next() {
|
||||
if (mBytesLeft == 0) {
|
||||
if (mWordsLeft == 0) {
|
||||
return I_FINISH;
|
||||
}
|
||||
mWordsLeft--;
|
||||
mWord = *mNextWord++;
|
||||
mBytesLeft = 4;
|
||||
}
|
||||
mBytesLeft--;
|
||||
mWord = (mWord << 8) | (mWord >> 24); // rotate
|
||||
return mWord;
|
||||
}
|
||||
|
||||
uint32_t &vSP() { return mState[R_SP]; }
|
||||
uint32_t *ptrSP() { return reinterpret_cast<uint32_t *>(vSP()); }
|
||||
|
||||
void checkStackBase() { if (vSP() > mStackBase) mFailed = true; }
|
||||
void checkStackLimit() { if (vSP() <= mStackLimit) mFailed = true; }
|
||||
void checkStackAlign() { if ((vSP() & 3) != 0) mFailed = true; }
|
||||
void checkStack() {
|
||||
checkStackBase();
|
||||
checkStackLimit();
|
||||
checkStackAlign();
|
||||
}
|
||||
|
||||
void popRange(uint8_t first, uint8_t last, uint16_t mask) {
|
||||
bool hasSP = false;
|
||||
uint32_t tmpSP;
|
||||
if (mask == 0)
|
||||
mFailed = true;
|
||||
for (uint8_t r = first; r <= last; ++r) {
|
||||
if (mask & 1) {
|
||||
if (r == R_SP) {
|
||||
hasSP = true;
|
||||
tmpSP = *ptrSP();
|
||||
} else
|
||||
mState[r] = *ptrSP();
|
||||
vSP() += 4;
|
||||
checkStackBase();
|
||||
if (mFailed)
|
||||
return;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
if (hasSP) {
|
||||
vSP() = tmpSP;
|
||||
checkStack();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool EHState::unwind(const EHEntry *aEntry, const void *stackBasePtr) {
|
||||
// The unwinding program cannot set SP to less than the initial value.
|
||||
uint32_t stackLimit = mRegs[R_SP] - 4;
|
||||
uint32_t stackBase = reinterpret_cast<uint32_t>(stackBasePtr);
|
||||
EHInterp interp(*this, aEntry, stackLimit, stackBase);
|
||||
return interp.unwind();
|
||||
}
|
||||
|
||||
bool EHInterp::unwind() {
|
||||
mState[R_PC] = 0;
|
||||
checkStack();
|
||||
while (!mFailed) {
|
||||
uint8_t insn = next();
|
||||
#if DEBUG_EHABI_UNWIND
|
||||
LOGF("unwind insn = %02x", (unsigned)insn);
|
||||
#endif
|
||||
// Try to put the common cases first.
|
||||
|
||||
// 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4
|
||||
// 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4
|
||||
if ((insn & M_ADDSP) == I_ADDSP) {
|
||||
uint32_t offset = ((insn & 0x3f) << 2) + 4;
|
||||
if (insn & 0x40) {
|
||||
vSP() -= offset;
|
||||
checkStackLimit();
|
||||
} else {
|
||||
vSP() += offset;
|
||||
checkStackBase();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10100nnn: Pop r4-r[4+nnn]
|
||||
// 10101nnn: Pop r4-r[4+nnn], r14
|
||||
if ((insn & M_POPN) == I_POPN) {
|
||||
uint8_t n = (insn & 0x07) + 1;
|
||||
bool lr = insn & 0x08;
|
||||
uint32_t *ptr = ptrSP();
|
||||
vSP() += (n + (lr ? 1 : 0)) * 4;
|
||||
checkStackBase();
|
||||
for (uint8_t r = 4; r < 4 + n; ++r)
|
||||
mState[r] = *ptr++;
|
||||
if (lr)
|
||||
mState[R_LR] = *ptr++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1011000: Finish
|
||||
if (insn == I_FINISH) {
|
||||
if (mState[R_PC] == 0) {
|
||||
mState[R_PC] = mState[R_LR];
|
||||
// Non-standard change (bug 916106): Prevent the caller from
|
||||
// re-using LR. Since the caller is by definition not a leaf
|
||||
// routine, it will have to restore LR from somewhere to
|
||||
// return to its own caller, so we can safely zero it here.
|
||||
// This makes a difference only if an error in unwinding
|
||||
// (e.g., caused by starting from within a prologue/epilogue)
|
||||
// causes us to load a pointer to a leaf routine as LR; if we
|
||||
// don't do something, we'll go into an infinite loop of
|
||||
// "returning" to that same function.
|
||||
mState[R_LR] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1001nnnn: Set vsp = r[nnnn]
|
||||
if ((insn & M_MOVSP) == I_MOVSP) {
|
||||
vSP() = mState[insn & 0x0f];
|
||||
checkStack();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11001000 sssscccc: Pop VFP regs D[16+ssss]-D[16+ssss+cccc] (as FLDMFDD)
|
||||
// 11001001 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDD)
|
||||
if ((insn & M_POPFDD) == I_POPFDD) {
|
||||
uint8_t n = (next() & 0x0f) + 1;
|
||||
// Note: if the 16+ssss+cccc > 31, the encoding is reserved.
|
||||
// As the space is currently unused, we don't try to check.
|
||||
vSP() += 8 * n;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11010nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDD)
|
||||
if ((insn & M_POPFDD8) == I_POPFDD8) {
|
||||
uint8_t n = (insn & 0x07) + 1;
|
||||
vSP() += 8 * n;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2)
|
||||
if (insn == I_ADDSPBIG) {
|
||||
uint32_t acc = 0;
|
||||
uint8_t shift = 0;
|
||||
uint8_t byte;
|
||||
do {
|
||||
if (shift >= 32)
|
||||
return false;
|
||||
byte = next();
|
||||
acc |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
uint32_t offset = 0x204 + (acc << 2);
|
||||
// The calculations above could have overflowed.
|
||||
// But the one we care about is this:
|
||||
if (vSP() + offset < vSP())
|
||||
mFailed = true;
|
||||
vSP() += offset;
|
||||
// ...so that this is the only other check needed:
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1000iiii iiiiiiii (i not all 0): Pop under masks {r15-r12}, {r11-r4}
|
||||
if ((insn & M_POPMASK) == I_POPMASK) {
|
||||
popRange(4, 15, ((insn & 0x0f) << 8) | next());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1011001 0000iiii (i not all 0): Pop under mask {r3-r0}
|
||||
if (insn == I_POPLO) {
|
||||
popRange(0, 3, next() & 0x0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10110011 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDX)
|
||||
if (insn == I_POPFDX) {
|
||||
uint8_t n = (next() & 0x0f) + 1;
|
||||
vSP() += 8 * n + 4;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10111nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDX)
|
||||
if ((insn & M_POPFDX8) == I_POPFDX8) {
|
||||
uint8_t n = (insn & 0x07) + 1;
|
||||
vSP() += 8 * n + 4;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// unhandled instruction
|
||||
#ifdef DEBUG_EHABI_UNWIND
|
||||
LOGF("Unhandled EHABI instruction 0x%02x", insn);
|
||||
#endif
|
||||
mFailed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool operator<(const EHTable &lhs, const EHTable &rhs) {
|
||||
return lhs.startPC() < rhs.startPC();
|
||||
}
|
||||
|
||||
// Async signal unsafe.
|
||||
EHAddrSpace::EHAddrSpace(const std::vector<EHTable>& aTables)
|
||||
: mTables(aTables)
|
||||
{
|
||||
std::sort(mTables.begin(), mTables.end());
|
||||
DebugOnly<uint32_t> lastEnd = 0;
|
||||
for (std::vector<EHTable>::iterator i = mTables.begin();
|
||||
i != mTables.end(); ++i) {
|
||||
MOZ_ASSERT(i->startPC() >= lastEnd);
|
||||
mStarts.push_back(i->startPC());
|
||||
lastEnd = i->endPC();
|
||||
}
|
||||
}
|
||||
|
||||
const EHTable *EHAddrSpace::lookup(uint32_t aPC) const {
|
||||
ptrdiff_t i = (std::upper_bound(mStarts.begin(), mStarts.end(), aPC)
|
||||
- mStarts.begin()) - 1;
|
||||
|
||||
if (i < 0 || aPC >= mTables[i].endPC())
|
||||
return 0;
|
||||
return &mTables[i];
|
||||
}
|
||||
|
||||
|
||||
const EHEntry *EHTable::lookup(uint32_t aPC) const {
|
||||
MOZ_ASSERT(aPC >= mStartPC);
|
||||
if (aPC >= mEndPC)
|
||||
return nullptr;
|
||||
|
||||
EntryIterator begin = entriesBegin();
|
||||
EntryIterator end = entriesEnd();
|
||||
MOZ_ASSERT(begin < end);
|
||||
if (aPC < reinterpret_cast<uint32_t>(entryGet(begin)->startPC.compute()))
|
||||
return nullptr;
|
||||
|
||||
while (end - begin > 1) {
|
||||
#ifdef EHABI_UNWIND_MORE_ASSERTS
|
||||
if (entryGet(end - 1)->startPC.compute()
|
||||
< entryGet(begin)->startPC.compute()) {
|
||||
MOZ_CRASH("unsorted exidx");
|
||||
}
|
||||
#endif
|
||||
EntryIterator mid = begin + (end - begin) / 2;
|
||||
if (aPC < reinterpret_cast<uint32_t>(entryGet(mid)->startPC.compute()))
|
||||
end = mid;
|
||||
else
|
||||
begin = mid;
|
||||
}
|
||||
return entryGet(begin);
|
||||
}
|
||||
|
||||
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
static const unsigned char hostEndian = ELFDATA2LSB;
|
||||
#elif MOZ_BIG_ENDIAN
|
||||
static const unsigned char hostEndian = ELFDATA2MSB;
|
||||
#else
|
||||
#error "No endian?"
|
||||
#endif
|
||||
|
||||
// Async signal unsafe: std::vector::reserve, std::string copy ctor.
|
||||
EHTable::EHTable(const void *aELF, size_t aSize, const std::string &aName)
|
||||
: mStartPC(~0), // largest uint32_t
|
||||
mEndPC(0),
|
||||
#ifndef HAVE_UNSORTED_EXIDX
|
||||
mEntriesBegin(nullptr),
|
||||
mEntriesEnd(nullptr),
|
||||
#endif
|
||||
mName(aName)
|
||||
{
|
||||
const uint32_t base = reinterpret_cast<uint32_t>(aELF);
|
||||
|
||||
if (aSize < sizeof(Elf32_Ehdr))
|
||||
return;
|
||||
|
||||
const Elf32_Ehdr &file = *(reinterpret_cast<Elf32_Ehdr *>(base));
|
||||
if (memcmp(&file.e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 ||
|
||||
file.e_ident[EI_CLASS] != ELFCLASS32 ||
|
||||
file.e_ident[EI_DATA] != hostEndian ||
|
||||
file.e_ident[EI_VERSION] != EV_CURRENT ||
|
||||
file.e_ident[EI_OSABI] != ELFOSABI_SYSV ||
|
||||
#ifdef EI_ABIVERSION
|
||||
file.e_ident[EI_ABIVERSION] != 0 ||
|
||||
#endif
|
||||
file.e_machine != EM_ARM ||
|
||||
file.e_version != EV_CURRENT)
|
||||
// e_flags?
|
||||
return;
|
||||
|
||||
MOZ_ASSERT(file.e_phoff + file.e_phnum * file.e_phentsize <= aSize);
|
||||
const Elf32_Phdr *exidxHdr = 0, *zeroHdr = 0;
|
||||
for (unsigned i = 0; i < file.e_phnum; ++i) {
|
||||
const Elf32_Phdr &phdr =
|
||||
*(reinterpret_cast<Elf32_Phdr *>(base + file.e_phoff
|
||||
+ i * file.e_phentsize));
|
||||
if (phdr.p_type == PT_ARM_EXIDX) {
|
||||
exidxHdr = &phdr;
|
||||
} else if (phdr.p_type == PT_LOAD) {
|
||||
if (phdr.p_offset == 0) {
|
||||
zeroHdr = &phdr;
|
||||
}
|
||||
if (phdr.p_flags & PF_X) {
|
||||
mStartPC = std::min(mStartPC, phdr.p_vaddr);
|
||||
mEndPC = std::max(mEndPC, phdr.p_vaddr + phdr.p_memsz);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!exidxHdr)
|
||||
return;
|
||||
if (!zeroHdr)
|
||||
return;
|
||||
mLoadOffset = base - zeroHdr->p_vaddr;
|
||||
mStartPC += mLoadOffset;
|
||||
mEndPC += mLoadOffset;
|
||||
|
||||
// Create a sorted index of the index to work around linker bugs.
|
||||
const EHEntry *startTable =
|
||||
reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr);
|
||||
const EHEntry *endTable =
|
||||
reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr
|
||||
+ exidxHdr->p_memsz);
|
||||
#ifdef HAVE_UNSORTED_EXIDX
|
||||
mEntries.reserve(endTable - startTable);
|
||||
for (const EHEntry *i = startTable; i < endTable; ++i)
|
||||
mEntries.push_back(i);
|
||||
std::sort(mEntries.begin(), mEntries.end());
|
||||
#else
|
||||
mEntriesBegin = startTable;
|
||||
mEntriesEnd = endTable;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
mozilla::Atomic<const EHAddrSpace*> EHAddrSpace::sCurrent(nullptr);
|
||||
|
||||
// Async signal safe; can fail if Update() hasn't returned yet.
|
||||
const EHAddrSpace *EHAddrSpace::Get() {
|
||||
return sCurrent;
|
||||
}
|
||||
|
||||
// Collect unwinding information from loaded objects. Calls after the
|
||||
// first have no effect. Async signal unsafe.
|
||||
void EHAddrSpace::Update() {
|
||||
const EHAddrSpace *space = sCurrent;
|
||||
if (space)
|
||||
return;
|
||||
|
||||
SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
|
||||
std::vector<EHTable> tables;
|
||||
|
||||
for (size_t i = 0; i < info.GetSize(); ++i) {
|
||||
const SharedLibrary &lib = info.GetEntry(i);
|
||||
if (lib.GetOffset() != 0)
|
||||
// TODO: if it has a name, and we haven't seen a mapping of
|
||||
// offset 0 for that file, try opening it and reading the
|
||||
// headers instead. The only thing I've seen so far that's
|
||||
// linked so as to need that treatment is the dynamic linker
|
||||
// itself.
|
||||
continue;
|
||||
EHTable tab(reinterpret_cast<const void *>(lib.GetStart()),
|
||||
lib.GetEnd() - lib.GetStart(), lib.GetName());
|
||||
if (tab.isValid())
|
||||
tables.push_back(tab);
|
||||
}
|
||||
space = new EHAddrSpace(tables);
|
||||
|
||||
if (!sCurrent.compareExchange(nullptr, space)) {
|
||||
delete space;
|
||||
space = sCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EHState::EHState(const mcontext_t &context) {
|
||||
#ifdef linux
|
||||
mRegs[0] = context.arm_r0;
|
||||
mRegs[1] = context.arm_r1;
|
||||
mRegs[2] = context.arm_r2;
|
||||
mRegs[3] = context.arm_r3;
|
||||
mRegs[4] = context.arm_r4;
|
||||
mRegs[5] = context.arm_r5;
|
||||
mRegs[6] = context.arm_r6;
|
||||
mRegs[7] = context.arm_r7;
|
||||
mRegs[8] = context.arm_r8;
|
||||
mRegs[9] = context.arm_r9;
|
||||
mRegs[10] = context.arm_r10;
|
||||
mRegs[11] = context.arm_fp;
|
||||
mRegs[12] = context.arm_ip;
|
||||
mRegs[13] = context.arm_sp;
|
||||
mRegs[14] = context.arm_lr;
|
||||
mRegs[15] = context.arm_pc;
|
||||
#else
|
||||
# error "Unhandled OS for ARM EHABI unwinding"
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* This is an implementation of stack unwinding according to a subset
|
||||
* of the ARM Exception Handling ABI; see the comment at the top of
|
||||
* the .cpp file for details.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_EHABIStackWalk_h__
|
||||
#define mozilla_EHABIStackWalk_h__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <ucontext.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void EHABIStackWalkInit();
|
||||
|
||||
size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase,
|
||||
void **aSPs, void **aPCs, size_t aNumFrames);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,181 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef GeckoSampler_h
|
||||
#define GeckoSampler_h
|
||||
|
||||
#include "platform.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "ThreadProfile.h"
|
||||
#include "ThreadInfo.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "IntelPowerGadget.h"
|
||||
#endif
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
#include "GeckoTaskTracer.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace mozilla {
|
||||
class ProfileGatherer;
|
||||
} // namespace mozilla
|
||||
|
||||
typedef mozilla::Vector<std::string> ThreadNameFilterList;
|
||||
typedef mozilla::Vector<std::string> FeatureList;
|
||||
|
||||
static bool
|
||||
threadSelected(ThreadInfo* aInfo, const ThreadNameFilterList &aThreadNameFilters) {
|
||||
if (aThreadNameFilters.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string name = aInfo->Name();
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
|
||||
for (uint32_t i = 0; i < aThreadNameFilters.length(); ++i) {
|
||||
std::string filter = aThreadNameFilters[i];
|
||||
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
||||
|
||||
// Crude, non UTF-8 compatible, case insensitive substring search
|
||||
if (name.find(filter) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extern mozilla::TimeStamp sLastTracerEvent;
|
||||
extern int sFrameNumber;
|
||||
extern int sLastFrameNumber;
|
||||
|
||||
class GeckoSampler: public Sampler {
|
||||
public:
|
||||
GeckoSampler(double aInterval, int aEntrySize,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount);
|
||||
~GeckoSampler();
|
||||
|
||||
void RegisterThread(ThreadInfo* aInfo) {
|
||||
if (!aInfo->IsMainThread() && !mProfileThreads) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!threadSelected(aInfo, mThreadNameFilters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadProfile* profile = new ThreadProfile(aInfo, mBuffer);
|
||||
aInfo->SetProfile(profile);
|
||||
}
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void Tick(TickSample* sample) override;
|
||||
|
||||
// Immediately captures the calling thread's call stack and returns it.
|
||||
virtual SyncProfile* GetBacktrace() override;
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void RequestSave() override
|
||||
{
|
||||
mSaveRequested = true;
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
if (mTaskTracer) {
|
||||
mozilla::tasktracer::StopLogging();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void HandleSaveRequest() override;
|
||||
virtual void DeleteExpiredMarkers() override;
|
||||
|
||||
ThreadProfile* GetPrimaryThreadProfile()
|
||||
{
|
||||
if (!mPrimaryThreadProfile) {
|
||||
::MutexAutoLock lock(*sRegisteredThreadsMutex);
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->IsMainThread() && !info->IsPendingDelete()) {
|
||||
mPrimaryThreadProfile = info->Profile();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mPrimaryThreadProfile;
|
||||
}
|
||||
|
||||
void ToStreamAsJSON(std::ostream& stream, double aSinceTime = 0);
|
||||
#ifndef SPS_STANDALONE
|
||||
virtual JSObject *ToJSObject(JSContext *aCx, double aSinceTime = 0);
|
||||
void GetGatherer(nsISupports** aRetVal);
|
||||
#endif
|
||||
mozilla::UniquePtr<char[]> ToJSON(double aSinceTime = 0);
|
||||
virtual void ToJSObjectAsync(double aSinceTime = 0, mozilla::dom::Promise* aPromise = 0);
|
||||
void StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter);
|
||||
void StreamTaskTracer(SpliceableJSONWriter& aWriter);
|
||||
void FlushOnJSShutdown(JSContext* aContext);
|
||||
bool ProfileJS() const { return mProfileJS; }
|
||||
bool ProfileJava() const { return mProfileJava; }
|
||||
bool ProfileGPU() const { return mProfileGPU; }
|
||||
bool ProfilePower() const { return mProfilePower; }
|
||||
bool ProfileThreads() const override { return mProfileThreads; }
|
||||
bool InPrivacyMode() const { return mPrivacyMode; }
|
||||
bool AddMainThreadIO() const { return mAddMainThreadIO; }
|
||||
bool ProfileMemory() const { return mProfileMemory; }
|
||||
bool TaskTracer() const { return mTaskTracer; }
|
||||
bool LayersDump() const { return mLayersDump; }
|
||||
bool DisplayListDump() const { return mDisplayListDump; }
|
||||
bool ProfileRestyle() const { return mProfileRestyle; }
|
||||
const ThreadNameFilterList& ThreadNameFilters() { return mThreadNameFilters; }
|
||||
const FeatureList& Features() { return mFeatures; }
|
||||
|
||||
void GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration);
|
||||
|
||||
protected:
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void InplaceTick(TickSample* sample);
|
||||
|
||||
// Not implemented on platforms which do not support backtracing
|
||||
void doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample);
|
||||
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime);
|
||||
|
||||
// This represent the application's main thread (SAMPLER_INIT)
|
||||
ThreadProfile* mPrimaryThreadProfile;
|
||||
RefPtr<ProfileBuffer> mBuffer;
|
||||
bool mSaveRequested;
|
||||
bool mAddLeafAddresses;
|
||||
bool mUseStackWalk;
|
||||
bool mProfileJS;
|
||||
bool mProfileGPU;
|
||||
bool mProfileThreads;
|
||||
bool mProfileJava;
|
||||
bool mProfilePower;
|
||||
bool mLayersDump;
|
||||
bool mDisplayListDump;
|
||||
bool mProfileRestyle;
|
||||
|
||||
// Keep the thread filter to check against new thread that
|
||||
// are started while profiling
|
||||
ThreadNameFilterList mThreadNameFilters;
|
||||
FeatureList mFeatures;
|
||||
bool mPrivacyMode;
|
||||
bool mAddMainThreadIO;
|
||||
bool mProfileMemory;
|
||||
bool mTaskTracer;
|
||||
#if defined(XP_WIN)
|
||||
IntelPowerGadget* mIntelPowerGadget;
|
||||
#endif
|
||||
|
||||
private:
|
||||
RefPtr<mozilla::ProfileGatherer> mGatherer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,310 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013, Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Author: Joe Olivas <joseph.k.olivas@intel.com>
|
||||
*/
|
||||
|
||||
#include "nsDebug.h"
|
||||
#include "nsString.h"
|
||||
#include "IntelPowerGadget.h"
|
||||
#include "prenv.h"
|
||||
|
||||
IntelPowerGadget::IntelPowerGadget() :
|
||||
libpowergadget(nullptr),
|
||||
Initialize(nullptr),
|
||||
GetNumNodes(nullptr),
|
||||
GetMsrName(nullptr),
|
||||
GetMsrFunc(nullptr),
|
||||
ReadMSR(nullptr),
|
||||
WriteMSR(nullptr),
|
||||
GetIAFrequency(nullptr),
|
||||
GetTDP(nullptr),
|
||||
GetMaxTemperature(nullptr),
|
||||
GetThresholds(nullptr),
|
||||
GetTemperature(nullptr),
|
||||
ReadSample(nullptr),
|
||||
GetSysTime(nullptr),
|
||||
GetRDTSC(nullptr),
|
||||
GetTimeInterval(nullptr),
|
||||
GetBaseFrequency(nullptr),
|
||||
GetPowerData(nullptr),
|
||||
StartLog(nullptr),
|
||||
StopLog(nullptr),
|
||||
GetNumMsrs(nullptr),
|
||||
packageMSR(-1),
|
||||
cpuMSR(-1),
|
||||
freqMSR(-1),
|
||||
tempMSR(-1)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
IntelPowerGadget::Init()
|
||||
{
|
||||
bool success = false;
|
||||
const char *path = PR_GetEnv("IPG_Dir");
|
||||
nsCString ipg_library;
|
||||
if (path && *path) {
|
||||
ipg_library.Append(path);
|
||||
ipg_library.Append('/');
|
||||
ipg_library.AppendLiteral(PG_LIBRARY_NAME);
|
||||
libpowergadget = PR_LoadLibrary(ipg_library.get());
|
||||
}
|
||||
|
||||
if(libpowergadget) {
|
||||
Initialize = (IPGInitialize) PR_FindFunctionSymbol(libpowergadget, "IntelEnergyLibInitialize");
|
||||
GetNumNodes = (IPGGetNumNodes) PR_FindFunctionSymbol(libpowergadget, "GetNumNodes");
|
||||
GetMsrName = (IPGGetMsrName) PR_FindFunctionSymbol(libpowergadget, "GetMsrName");
|
||||
GetMsrFunc = (IPGGetMsrFunc) PR_FindFunctionSymbol(libpowergadget, "GetMsrFunc");
|
||||
ReadMSR = (IPGReadMSR) PR_FindFunctionSymbol(libpowergadget, "ReadMSR");
|
||||
WriteMSR = (IPGWriteMSR) PR_FindFunctionSymbol(libpowergadget, "WriteMSR");
|
||||
GetIAFrequency = (IPGGetIAFrequency) PR_FindFunctionSymbol(libpowergadget, "GetIAFrequency");
|
||||
GetTDP = (IPGGetTDP) PR_FindFunctionSymbol(libpowergadget, "GetTDP");
|
||||
GetMaxTemperature = (IPGGetMaxTemperature) PR_FindFunctionSymbol(libpowergadget, "GetMaxTemperature");
|
||||
GetThresholds = (IPGGetThresholds) PR_FindFunctionSymbol(libpowergadget, "GetThresholds");
|
||||
GetTemperature = (IPGGetTemperature) PR_FindFunctionSymbol(libpowergadget, "GetTemperature");
|
||||
ReadSample = (IPGReadSample) PR_FindFunctionSymbol(libpowergadget, "ReadSample");
|
||||
GetSysTime = (IPGGetSysTime) PR_FindFunctionSymbol(libpowergadget, "GetSysTime");
|
||||
GetRDTSC = (IPGGetRDTSC) PR_FindFunctionSymbol(libpowergadget, "GetRDTSC");
|
||||
GetTimeInterval = (IPGGetTimeInterval) PR_FindFunctionSymbol(libpowergadget, "GetTimeInterval");
|
||||
GetBaseFrequency = (IPGGetBaseFrequency) PR_FindFunctionSymbol(libpowergadget, "GetBaseFrequency");
|
||||
GetPowerData = (IPGGetPowerData) PR_FindFunctionSymbol(libpowergadget, "GetPowerData");
|
||||
StartLog = (IPGStartLog) PR_FindFunctionSymbol(libpowergadget, "StartLog");
|
||||
StopLog = (IPGStopLog) PR_FindFunctionSymbol(libpowergadget, "StopLog");
|
||||
GetNumMsrs = (IPGGetNumMsrs) PR_FindFunctionSymbol(libpowergadget, "GetNumMsrs");
|
||||
}
|
||||
|
||||
if(Initialize) {
|
||||
Initialize();
|
||||
int msrCount = GetNumberMsrs();
|
||||
wchar_t name[1024] = {0};
|
||||
for(int i = 0; i < msrCount; ++i) {
|
||||
GetMsrName(i, name);
|
||||
int func = 0;
|
||||
GetMsrFunc(i, &func);
|
||||
// MSR for frequency
|
||||
if(wcscmp(name, L"CPU Frequency") == 0 && (func == 0)) {
|
||||
this->freqMSR = i;
|
||||
}
|
||||
// MSR for Package
|
||||
else if(wcscmp(name, L"Processor") == 0 && (func == 1)) {
|
||||
this->packageMSR = i;
|
||||
}
|
||||
// MSR for CPU
|
||||
else if(wcscmp(name, L"IA") == 0 && (func == 1)) {
|
||||
this->cpuMSR = i;
|
||||
}
|
||||
// MSR for Temperature
|
||||
else if(wcscmp(name, L"Package") == 0 && (func == 2)) {
|
||||
this->tempMSR = i;
|
||||
}
|
||||
}
|
||||
// Grab one sample at startup for a diff
|
||||
TakeSample();
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
IntelPowerGadget::~IntelPowerGadget()
|
||||
{
|
||||
if(libpowergadget) {
|
||||
NS_WARNING("Unloading PowerGadget library!\n");
|
||||
PR_UnloadLibrary(libpowergadget);
|
||||
libpowergadget = nullptr;
|
||||
Initialize = nullptr;
|
||||
GetNumNodes = nullptr;
|
||||
GetMsrName = nullptr;
|
||||
GetMsrFunc = nullptr;
|
||||
ReadMSR = nullptr;
|
||||
WriteMSR = nullptr;
|
||||
GetIAFrequency = nullptr;
|
||||
GetTDP = nullptr;
|
||||
GetMaxTemperature = nullptr;
|
||||
GetThresholds = nullptr;
|
||||
GetTemperature = nullptr;
|
||||
ReadSample = nullptr;
|
||||
GetSysTime = nullptr;
|
||||
GetRDTSC = nullptr;
|
||||
GetTimeInterval = nullptr;
|
||||
GetBaseFrequency = nullptr;
|
||||
GetPowerData = nullptr;
|
||||
StartLog = nullptr;
|
||||
StopLog = nullptr;
|
||||
GetNumMsrs = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetNumberNodes()
|
||||
{
|
||||
int nodes = 0;
|
||||
if(GetNumNodes) {
|
||||
int ok = GetNumNodes(&nodes);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetNumberMsrs()
|
||||
{
|
||||
int msrs = 0;
|
||||
if(GetNumMsrs) {
|
||||
int ok = GetNumMsrs(&msrs);
|
||||
}
|
||||
return msrs;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetCPUFrequency(int node)
|
||||
{
|
||||
int frequency = 0;
|
||||
if(GetIAFrequency) {
|
||||
int ok = GetIAFrequency(node, &frequency);
|
||||
}
|
||||
return frequency;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTdp(int node)
|
||||
{
|
||||
double tdp = 0.0;
|
||||
if(GetTDP) {
|
||||
int ok = GetTDP(node, &tdp);
|
||||
}
|
||||
return tdp;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetMaxTemp(int node)
|
||||
{
|
||||
int maxTemperatureC = 0;
|
||||
if(GetMaxTemperature) {
|
||||
int ok = GetMaxTemperature(node, &maxTemperatureC);
|
||||
}
|
||||
return maxTemperatureC;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetTemp(int node)
|
||||
{
|
||||
int temperatureC = 0;
|
||||
if(GetTemperature) {
|
||||
int ok = GetTemperature(node, &temperatureC);
|
||||
}
|
||||
return temperatureC;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::TakeSample()
|
||||
{
|
||||
int ok = 0;
|
||||
if(ReadSample) {
|
||||
ok = ReadSample();
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IntelPowerGadget::GetRdtsc()
|
||||
{
|
||||
uint64_t rdtsc = 0;
|
||||
if(GetRDTSC) {
|
||||
int ok = GetRDTSC(&rdtsc);
|
||||
}
|
||||
return rdtsc;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetInterval()
|
||||
{
|
||||
double interval = 0.0;
|
||||
if(GetTimeInterval) {
|
||||
int ok = GetTimeInterval(&interval);
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetCPUBaseFrequency(int node)
|
||||
{
|
||||
double freq = 0.0;
|
||||
if(GetBaseFrequency) {
|
||||
int ok = GetBaseFrequency(node, &freq);
|
||||
}
|
||||
return freq;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTotalPackagePowerInWatts()
|
||||
{
|
||||
int nodes = GetNumberNodes();
|
||||
double totalPower = 0.0;
|
||||
for(int i = 0; i < nodes; ++i) {
|
||||
totalPower += GetPackagePowerInWatts(i);
|
||||
}
|
||||
return totalPower;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetPackagePowerInWatts(int node)
|
||||
{
|
||||
int numResult = 0;
|
||||
double result[] = {0.0, 0.0, 0.0};
|
||||
if(GetPowerData && packageMSR != -1) {
|
||||
int ok = GetPowerData(node, packageMSR, result, &numResult);
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTotalCPUPowerInWatts()
|
||||
{
|
||||
int nodes = GetNumberNodes();
|
||||
double totalPower = 0.0;
|
||||
for(int i = 0; i < nodes; ++i) {
|
||||
totalPower += GetCPUPowerInWatts(i);
|
||||
}
|
||||
return totalPower;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetCPUPowerInWatts(int node)
|
||||
{
|
||||
int numResult = 0;
|
||||
double result[] = {0.0, 0.0, 0.0};
|
||||
if(GetPowerData && cpuMSR != -1) {
|
||||
int ok = GetPowerData(node, cpuMSR, result, &numResult);
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTotalGPUPowerInWatts()
|
||||
{
|
||||
int nodes = GetNumberNodes();
|
||||
double totalPower = 0.0;
|
||||
for(int i = 0; i < nodes; ++i) {
|
||||
totalPower += GetGPUPowerInWatts(i);
|
||||
}
|
||||
return totalPower;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetGPUPowerInWatts(int node)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013, Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Author: Joe Olivas <joseph.k.olivas@intel.com>
|
||||
*/
|
||||
|
||||
#ifndef profiler_IntelPowerGadget_h
|
||||
#define profiler_IntelPowerGadget_h
|
||||
|
||||
#ifdef _MSC_VER
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#include "prlink.h"
|
||||
|
||||
typedef int (*IPGInitialize) ();
|
||||
typedef int (*IPGGetNumNodes) (int *nNodes);
|
||||
typedef int (*IPGGetNumMsrs) (int *nMsr);
|
||||
typedef int (*IPGGetMsrName) (int iMsr, wchar_t *szName);
|
||||
typedef int (*IPGGetMsrFunc) (int iMsr, int *pFuncID);
|
||||
typedef int (*IPGReadMSR) (int iNode, unsigned int address, uint64_t *value);
|
||||
typedef int (*IPGWriteMSR) (int iNode, unsigned int address, uint64_t value);
|
||||
typedef int (*IPGGetIAFrequency) (int iNode, int *freqInMHz);
|
||||
typedef int (*IPGGetTDP) (int iNode, double *TDP);
|
||||
typedef int (*IPGGetMaxTemperature) (int iNode, int *degreeC);
|
||||
typedef int (*IPGGetThresholds) (int iNode, int *degree1C, int *degree2C);
|
||||
typedef int (*IPGGetTemperature) (int iNode, int *degreeC);
|
||||
typedef int (*IPGReadSample) ();
|
||||
typedef int (*IPGGetSysTime) (void *pSysTime);
|
||||
typedef int (*IPGGetRDTSC) (uint64_t *pTSC);
|
||||
typedef int (*IPGGetTimeInterval) (double *pOffset);
|
||||
typedef int (*IPGGetBaseFrequency) (int iNode, double *pBaseFrequency);
|
||||
typedef int (*IPGGetPowerData) (int iNode, int iMSR, double *pResult, int *nResult);
|
||||
typedef int (*IPGStartLog) (wchar_t *szFileName);
|
||||
typedef int (*IPGStopLog) ();
|
||||
|
||||
#if defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64)
|
||||
#define PG_LIBRARY_NAME "EnergyLib64"
|
||||
#else
|
||||
#define PG_LIBRARY_NAME "EnergyLib32"
|
||||
#endif
|
||||
|
||||
|
||||
class IntelPowerGadget
|
||||
{
|
||||
public:
|
||||
|
||||
IntelPowerGadget();
|
||||
~IntelPowerGadget();
|
||||
|
||||
// Fails if initialization is incomplete
|
||||
bool Init();
|
||||
|
||||
// Returns the number of packages on the system
|
||||
int GetNumberNodes();
|
||||
|
||||
// Returns the number of MSRs being tracked
|
||||
int GetNumberMsrs();
|
||||
|
||||
// Given a node, returns the temperature
|
||||
int GetCPUFrequency(int);
|
||||
|
||||
// Returns the TDP of the given node
|
||||
double GetTdp(int);
|
||||
|
||||
// Returns the maximum temperature for the given node
|
||||
int GetMaxTemp(int);
|
||||
|
||||
// Returns the current temperature in degrees C
|
||||
// of the given node
|
||||
int GetTemp(int);
|
||||
|
||||
// Takes a sample of data. Must be called before
|
||||
// any current data is retrieved.
|
||||
int TakeSample();
|
||||
|
||||
// Gets the timestamp of the most recent sample
|
||||
uint64_t GetRdtsc();
|
||||
|
||||
// returns number of seconds between the last
|
||||
// two samples
|
||||
double GetInterval();
|
||||
|
||||
// Returns the base frequency for the given node
|
||||
double GetCPUBaseFrequency(int node);
|
||||
|
||||
// Returns the combined package power for all
|
||||
// packages on the system for the last sample.
|
||||
double GetTotalPackagePowerInWatts();
|
||||
double GetPackagePowerInWatts(int node);
|
||||
|
||||
// Returns the combined CPU power for all
|
||||
// packages on the system for the last sample.
|
||||
// If the reading is not available, returns 0.0
|
||||
double GetTotalCPUPowerInWatts();
|
||||
double GetCPUPowerInWatts(int node);
|
||||
|
||||
// Returns the combined GPU power for all
|
||||
// packages on the system for the last sample.
|
||||
// If the reading is not available, returns 0.0
|
||||
double GetTotalGPUPowerInWatts();
|
||||
double GetGPUPowerInWatts(int node);
|
||||
|
||||
private:
|
||||
|
||||
PRLibrary *libpowergadget;
|
||||
IPGInitialize Initialize;
|
||||
IPGGetNumNodes GetNumNodes;
|
||||
IPGGetNumMsrs GetNumMsrs;
|
||||
IPGGetMsrName GetMsrName;
|
||||
IPGGetMsrFunc GetMsrFunc;
|
||||
IPGReadMSR ReadMSR;
|
||||
IPGWriteMSR WriteMSR;
|
||||
IPGGetIAFrequency GetIAFrequency;
|
||||
IPGGetTDP GetTDP;
|
||||
IPGGetMaxTemperature GetMaxTemperature;
|
||||
IPGGetThresholds GetThresholds;
|
||||
IPGGetTemperature GetTemperature;
|
||||
IPGReadSample ReadSample;
|
||||
IPGGetSysTime GetSysTime;
|
||||
IPGGetRDTSC GetRDTSC;
|
||||
IPGGetTimeInterval GetTimeInterval;
|
||||
IPGGetBaseFrequency GetBaseFrequency;
|
||||
IPGGetPowerData GetPowerData;
|
||||
IPGStartLog StartLog;
|
||||
IPGStopLog StopLog;
|
||||
|
||||
int packageMSR;
|
||||
int cpuMSR;
|
||||
int freqMSR;
|
||||
int tempMSR;
|
||||
};
|
||||
|
||||
#endif // profiler_IntelPowerGadget_h
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ProfileBuffer.h"
|
||||
|
||||
ProfileBuffer::ProfileBuffer(int aEntrySize)
|
||||
: mEntries(MakeUnique<ProfileEntry[]>(aEntrySize))
|
||||
, mWritePos(0)
|
||||
, mReadPos(0)
|
||||
, mEntrySize(aEntrySize)
|
||||
, mGeneration(0)
|
||||
{
|
||||
}
|
||||
|
||||
ProfileBuffer::~ProfileBuffer()
|
||||
{
|
||||
while (mStoredMarkers.peek()) {
|
||||
delete mStoredMarkers.popHead();
|
||||
}
|
||||
}
|
||||
|
||||
// Called from signal, call only reentrant functions
|
||||
void ProfileBuffer::addTag(const ProfileEntry& aTag)
|
||||
{
|
||||
mEntries[mWritePos++] = aTag;
|
||||
if (mWritePos == mEntrySize) {
|
||||
// Wrapping around may result in things referenced in the buffer (e.g.,
|
||||
// JIT code addresses and markers) being incorrectly collected.
|
||||
MOZ_ASSERT(mGeneration != UINT32_MAX);
|
||||
mGeneration++;
|
||||
mWritePos = 0;
|
||||
}
|
||||
if (mWritePos == mReadPos) {
|
||||
// Keep one slot open.
|
||||
mEntries[mReadPos] = ProfileEntry();
|
||||
mReadPos = (mReadPos + 1) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::addStoredMarker(ProfilerMarker *aStoredMarker) {
|
||||
aStoredMarker->SetGeneration(mGeneration);
|
||||
mStoredMarkers.insert(aStoredMarker);
|
||||
}
|
||||
|
||||
void ProfileBuffer::deleteExpiredStoredMarkers() {
|
||||
// Delete markers of samples that have been overwritten due to circular
|
||||
// buffer wraparound.
|
||||
uint32_t generation = mGeneration;
|
||||
while (mStoredMarkers.peek() &&
|
||||
mStoredMarkers.peek()->HasExpired(generation)) {
|
||||
delete mStoredMarkers.popHead();
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::reset() {
|
||||
mGeneration += 2;
|
||||
mReadPos = mWritePos = 0;
|
||||
}
|
||||
|
||||
#define DYNAMIC_MAX_STRING 8192
|
||||
|
||||
char* ProfileBuffer::processDynamicTag(int readPos,
|
||||
int* tagsConsumed, char* tagBuff)
|
||||
{
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
int tagBuffPos = 0;
|
||||
|
||||
// Read the string stored in mTagData until the null character is seen
|
||||
bool seenNullByte = false;
|
||||
while (readAheadPos != mWritePos && !seenNullByte) {
|
||||
(*tagsConsumed)++;
|
||||
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
||||
for (size_t pos = 0; pos < sizeof(void*); pos++) {
|
||||
tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
|
||||
if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
|
||||
seenNullByte = true;
|
||||
break;
|
||||
}
|
||||
tagBuffPos++;
|
||||
}
|
||||
if (!seenNullByte)
|
||||
readAheadPos = (readAheadPos + 1) % mEntrySize;
|
||||
}
|
||||
return tagBuff;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_PROFILE_BUFFER_H
|
||||
#define MOZ_PROFILE_BUFFER_H
|
||||
|
||||
#include "ProfileEntry.h"
|
||||
#include "platform.h"
|
||||
#include "ProfileJSONWriter.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/RefCounted.h"
|
||||
|
||||
class ProfileBuffer : public mozilla::RefCounted<ProfileBuffer> {
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ProfileBuffer)
|
||||
|
||||
explicit ProfileBuffer(int aEntrySize);
|
||||
|
||||
virtual ~ProfileBuffer();
|
||||
|
||||
void addTag(const ProfileEntry& aTag);
|
||||
void StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId, double aSinceTime,
|
||||
JSContext* cx, UniqueStacks& aUniqueStacks);
|
||||
void StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId, double aSinceTime,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
void DuplicateLastSample(int aThreadId);
|
||||
|
||||
void addStoredMarker(ProfilerMarker* aStoredMarker);
|
||||
|
||||
// The following two methods are not signal safe! They delete markers.
|
||||
void deleteExpiredStoredMarkers();
|
||||
void reset();
|
||||
|
||||
protected:
|
||||
char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff);
|
||||
int FindLastSampleOfThread(int aThreadId);
|
||||
|
||||
public:
|
||||
// Circular buffer 'Keep One Slot Open' implementation for simplicity
|
||||
mozilla::UniquePtr<ProfileEntry[]> mEntries;
|
||||
|
||||
// Points to the next entry we will write to, which is also the one at which
|
||||
// we need to stop reading.
|
||||
int mWritePos;
|
||||
|
||||
// Points to the entry at which we can start reading.
|
||||
int mReadPos;
|
||||
|
||||
// The number of entries in our buffer.
|
||||
int mEntrySize;
|
||||
|
||||
// How many times mWritePos has wrapped around.
|
||||
uint32_t mGeneration;
|
||||
|
||||
// Markers that marker entries in the buffer might refer to.
|
||||
ProfilerMarkerLinkedList mStoredMarkers;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,881 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <ostream>
|
||||
#include "platform.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
// JS
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "js/TrackedOptimizationInfo.h"
|
||||
#endif
|
||||
|
||||
// Self
|
||||
#include "ProfileEntry.h"
|
||||
|
||||
using mozilla::MakeUnique;
|
||||
using mozilla::UniquePtr;
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Some;
|
||||
using mozilla::Nothing;
|
||||
using mozilla::JSONWriter;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN ProfileEntry
|
||||
|
||||
ProfileEntry::ProfileEntry()
|
||||
: mTagData(nullptr)
|
||||
, mTagName(0)
|
||||
{ }
|
||||
|
||||
// aTagData must not need release (i.e. be a string from the text segment)
|
||||
ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
|
||||
: mTagData(aTagData)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
|
||||
: mTagMarker(aTagMarker)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
|
||||
: mTagPtr(aTagPtr)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, double aTagDouble)
|
||||
: mTagDouble(aTagDouble)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
|
||||
: mTagOffset(aTagOffset)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
|
||||
: mTagAddress(aTagAddress)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, int aTagInt)
|
||||
: mTagInt(aTagInt)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
|
||||
: mTagChar(aTagChar)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
bool ProfileEntry::is_ent_hint(char hintChar) {
|
||||
return mTagName == 'h' && mTagChar == hintChar;
|
||||
}
|
||||
|
||||
bool ProfileEntry::is_ent_hint() {
|
||||
return mTagName == 'h';
|
||||
}
|
||||
|
||||
bool ProfileEntry::is_ent(char tagChar) {
|
||||
return mTagName == tagChar;
|
||||
}
|
||||
|
||||
void* ProfileEntry::get_tagPtr() {
|
||||
// No consistency checking. Oh well.
|
||||
return mTagPtr;
|
||||
}
|
||||
|
||||
// END ProfileEntry
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class JSONSchemaWriter
|
||||
{
|
||||
JSONWriter& mWriter;
|
||||
uint32_t mIndex;
|
||||
|
||||
public:
|
||||
explicit JSONSchemaWriter(JSONWriter& aWriter)
|
||||
: mWriter(aWriter)
|
||||
, mIndex(0)
|
||||
{
|
||||
aWriter.StartObjectProperty("schema");
|
||||
}
|
||||
|
||||
void WriteField(const char* aName) {
|
||||
mWriter.IntProperty(aName, mIndex++);
|
||||
}
|
||||
|
||||
~JSONSchemaWriter() {
|
||||
mWriter.EndObject();
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
class StreamOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp
|
||||
{
|
||||
JSONWriter& mWriter;
|
||||
UniqueJSONStrings& mUniqueStrings;
|
||||
bool mStartedTypeList;
|
||||
|
||||
public:
|
||||
StreamOptimizationTypeInfoOp(JSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
|
||||
: mWriter(aWriter)
|
||||
, mUniqueStrings(aUniqueStrings)
|
||||
, mStartedTypeList(false)
|
||||
{ }
|
||||
|
||||
void readType(const char* keyedBy, const char* name,
|
||||
const char* location, Maybe<unsigned> lineno) override {
|
||||
if (!mStartedTypeList) {
|
||||
mStartedTypeList = true;
|
||||
mWriter.StartObjectElement();
|
||||
mWriter.StartArrayProperty("typeset");
|
||||
}
|
||||
|
||||
mWriter.StartObjectElement();
|
||||
{
|
||||
mUniqueStrings.WriteProperty(mWriter, "keyedBy", keyedBy);
|
||||
if (name) {
|
||||
mUniqueStrings.WriteProperty(mWriter, "name", name);
|
||||
}
|
||||
if (location) {
|
||||
mUniqueStrings.WriteProperty(mWriter, "location", location);
|
||||
}
|
||||
if (lineno.isSome()) {
|
||||
mWriter.IntProperty("line", *lineno);
|
||||
}
|
||||
}
|
||||
mWriter.EndObject();
|
||||
}
|
||||
|
||||
void operator()(JS::TrackedTypeSite site, const char* mirType) override {
|
||||
if (mStartedTypeList) {
|
||||
mWriter.EndArray();
|
||||
mStartedTypeList = false;
|
||||
} else {
|
||||
mWriter.StartObjectElement();
|
||||
}
|
||||
|
||||
{
|
||||
mUniqueStrings.WriteProperty(mWriter, "site", JS::TrackedTypeSiteString(site));
|
||||
mUniqueStrings.WriteProperty(mWriter, "mirType", mirType);
|
||||
}
|
||||
mWriter.EndObject();
|
||||
}
|
||||
};
|
||||
|
||||
// As mentioned in ProfileEntry.h, the JSON format contains many arrays whose
|
||||
// elements are laid out according to various schemas to help
|
||||
// de-duplication. This RAII class helps write these arrays by keeping track of
|
||||
// the last non-null element written and adding the appropriate number of null
|
||||
// elements when writing new non-null elements. It also automatically opens and
|
||||
// closes an array element on the given JSON writer.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// // Define the schema of elements in this type of array: [FOO, BAR, BAZ]
|
||||
// enum Schema : uint32_t {
|
||||
// FOO = 0,
|
||||
// BAR = 1,
|
||||
// BAZ = 2
|
||||
// };
|
||||
//
|
||||
// AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings);
|
||||
// if (shouldWriteFoo) {
|
||||
// writer.IntElement(FOO, getFoo());
|
||||
// }
|
||||
// ... etc ...
|
||||
class MOZ_RAII AutoArraySchemaWriter
|
||||
{
|
||||
friend class AutoObjectWriter;
|
||||
|
||||
SpliceableJSONWriter& mJSONWriter;
|
||||
UniqueJSONStrings* mStrings;
|
||||
uint32_t mNextFreeIndex;
|
||||
|
||||
public:
|
||||
AutoArraySchemaWriter(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aStrings)
|
||||
: mJSONWriter(aWriter)
|
||||
, mStrings(&aStrings)
|
||||
, mNextFreeIndex(0)
|
||||
{
|
||||
mJSONWriter.StartArrayElement();
|
||||
}
|
||||
|
||||
// If you don't have access to a UniqueStrings, you had better not try and
|
||||
// write a string element down the line!
|
||||
explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter)
|
||||
: mJSONWriter(aWriter)
|
||||
, mStrings(nullptr)
|
||||
, mNextFreeIndex(0)
|
||||
{
|
||||
mJSONWriter.StartArrayElement();
|
||||
}
|
||||
|
||||
~AutoArraySchemaWriter() {
|
||||
mJSONWriter.EndArray();
|
||||
}
|
||||
|
||||
void FillUpTo(uint32_t aIndex) {
|
||||
MOZ_ASSERT(aIndex >= mNextFreeIndex);
|
||||
mJSONWriter.NullElements(aIndex - mNextFreeIndex);
|
||||
mNextFreeIndex = aIndex + 1;
|
||||
}
|
||||
|
||||
void IntElement(uint32_t aIndex, uint32_t aValue) {
|
||||
FillUpTo(aIndex);
|
||||
mJSONWriter.IntElement(aValue);
|
||||
}
|
||||
|
||||
void DoubleElement(uint32_t aIndex, double aValue) {
|
||||
FillUpTo(aIndex);
|
||||
mJSONWriter.DoubleElement(aValue);
|
||||
}
|
||||
|
||||
void StringElement(uint32_t aIndex, const char* aValue) {
|
||||
MOZ_RELEASE_ASSERT(mStrings);
|
||||
FillUpTo(aIndex);
|
||||
mStrings->WriteElement(mJSONWriter, aValue);
|
||||
}
|
||||
};
|
||||
|
||||
class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp
|
||||
{
|
||||
SpliceableJSONWriter& mWriter;
|
||||
UniqueJSONStrings& mUniqueStrings;
|
||||
|
||||
public:
|
||||
StreamOptimizationAttemptsOp(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
|
||||
: mWriter(aWriter),
|
||||
mUniqueStrings(aUniqueStrings)
|
||||
{ }
|
||||
|
||||
void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
|
||||
enum Schema : uint32_t {
|
||||
STRATEGY = 0,
|
||||
OUTCOME = 1
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(mWriter, mUniqueStrings);
|
||||
writer.StringElement(STRATEGY, JS::TrackedStrategyString(strategy));
|
||||
writer.StringElement(OUTCOME, JS::TrackedOutcomeString(outcome));
|
||||
}
|
||||
};
|
||||
|
||||
class StreamJSFramesOp : public JS::ForEachProfiledFrameOp
|
||||
{
|
||||
void* mReturnAddress;
|
||||
UniqueStacks::Stack& mStack;
|
||||
unsigned mDepth;
|
||||
|
||||
public:
|
||||
StreamJSFramesOp(void* aReturnAddr, UniqueStacks::Stack& aStack)
|
||||
: mReturnAddress(aReturnAddr)
|
||||
, mStack(aStack)
|
||||
, mDepth(0)
|
||||
{ }
|
||||
|
||||
unsigned depth() const {
|
||||
MOZ_ASSERT(mDepth > 0);
|
||||
return mDepth;
|
||||
}
|
||||
|
||||
void operator()(const JS::ForEachProfiledFrameOp::FrameHandle& aFrameHandle) override {
|
||||
UniqueStacks::OnStackFrameKey frameKey(mReturnAddress, mDepth, aFrameHandle);
|
||||
mStack.AppendFrame(frameKey);
|
||||
mDepth++;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
uint32_t UniqueJSONStrings::GetOrAddIndex(const char* aStr)
|
||||
{
|
||||
uint32_t index;
|
||||
StringKey key(aStr);
|
||||
|
||||
auto it = mStringToIndexMap.find(key);
|
||||
|
||||
if (it != mStringToIndexMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
index = mStringToIndexMap.size();
|
||||
mStringToIndexMap[key] = index;
|
||||
mStringTableWriter.StringElement(aStr);
|
||||
return index;
|
||||
}
|
||||
|
||||
bool UniqueStacks::FrameKey::operator==(const FrameKey& aOther) const
|
||||
{
|
||||
return mLocation == aOther.mLocation &&
|
||||
mLine == aOther.mLine &&
|
||||
mCategory == aOther.mCategory &&
|
||||
mJITAddress == aOther.mJITAddress &&
|
||||
mJITDepth == aOther.mJITDepth;
|
||||
}
|
||||
|
||||
bool UniqueStacks::StackKey::operator==(const StackKey& aOther) const
|
||||
{
|
||||
MOZ_ASSERT_IF(mPrefix == aOther.mPrefix, mPrefixHash == aOther.mPrefixHash);
|
||||
return mPrefix == aOther.mPrefix && mFrame == aOther.mFrame;
|
||||
}
|
||||
|
||||
UniqueStacks::Stack::Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot)
|
||||
: mUniqueStacks(aUniqueStacks)
|
||||
, mStack(aUniqueStacks.GetOrAddFrameIndex(aRoot))
|
||||
{
|
||||
}
|
||||
|
||||
void UniqueStacks::Stack::AppendFrame(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
// Compute the prefix hash and index before mutating mStack.
|
||||
uint32_t prefixHash = mStack.Hash();
|
||||
uint32_t prefix = mUniqueStacks.GetOrAddStackIndex(mStack);
|
||||
mStack.UpdateHash(prefixHash, prefix, mUniqueStacks.GetOrAddFrameIndex(aFrame));
|
||||
}
|
||||
|
||||
uint32_t UniqueStacks::Stack::GetOrAddIndex() const
|
||||
{
|
||||
return mUniqueStacks.GetOrAddStackIndex(mStack);
|
||||
}
|
||||
|
||||
uint32_t UniqueStacks::FrameKey::Hash() const
|
||||
{
|
||||
uint32_t hash = 0;
|
||||
if (!mLocation.IsEmpty()) {
|
||||
#ifdef SPS_STANDALONE
|
||||
hash = mozilla::HashString(mLocation.c_str());
|
||||
#else
|
||||
hash = mozilla::HashString(mLocation.get());
|
||||
#endif
|
||||
}
|
||||
if (mLine.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mLine);
|
||||
}
|
||||
if (mCategory.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mCategory);
|
||||
}
|
||||
if (mJITAddress.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mJITAddress);
|
||||
if (mJITDepth.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mJITDepth);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t UniqueStacks::StackKey::Hash() const
|
||||
{
|
||||
if (mPrefix.isNothing()) {
|
||||
return mozilla::HashGeneric(mFrame);
|
||||
}
|
||||
return mozilla::AddToHash(*mPrefixHash, mFrame);
|
||||
}
|
||||
|
||||
UniqueStacks::Stack UniqueStacks::BeginStack(const OnStackFrameKey& aRoot)
|
||||
{
|
||||
return Stack(*this, aRoot);
|
||||
}
|
||||
|
||||
UniqueStacks::UniqueStacks(JSContext* aContext)
|
||||
: mContext(aContext)
|
||||
, mFrameCount(0)
|
||||
{
|
||||
mFrameTableWriter.StartBareList();
|
||||
mStackTableWriter.StartBareList();
|
||||
}
|
||||
|
||||
#ifdef SPS_STANDALONE
|
||||
uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack)
|
||||
{
|
||||
uint32_t index;
|
||||
auto it = mStackToIndexMap.find(aStack);
|
||||
|
||||
if (it != mStackToIndexMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
index = mStackToIndexMap.size();
|
||||
mStackToIndexMap[aStack] = index;
|
||||
StreamStack(aStack);
|
||||
return index;
|
||||
}
|
||||
#else
|
||||
uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack)
|
||||
{
|
||||
uint32_t index;
|
||||
if (mStackToIndexMap.Get(aStack, &index)) {
|
||||
MOZ_ASSERT(index < mStackToIndexMap.Count());
|
||||
return index;
|
||||
}
|
||||
|
||||
index = mStackToIndexMap.Count();
|
||||
mStackToIndexMap.Put(aStack, index);
|
||||
StreamStack(aStack);
|
||||
return index;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SPS_STANDALONE
|
||||
uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
uint32_t index;
|
||||
auto it = mFrameToIndexMap.find(aFrame);
|
||||
if (it != mFrameToIndexMap.end()) {
|
||||
MOZ_ASSERT(it->second < mFrameCount);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// A manual count is used instead of mFrameToIndexMap.Count() due to
|
||||
// forwarding of canonical JIT frames above.
|
||||
index = mFrameCount++;
|
||||
mFrameToIndexMap[aFrame] = index;
|
||||
StreamFrame(aFrame);
|
||||
return index;
|
||||
}
|
||||
#else
|
||||
uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
uint32_t index;
|
||||
if (mFrameToIndexMap.Get(aFrame, &index)) {
|
||||
MOZ_ASSERT(index < mFrameCount);
|
||||
return index;
|
||||
}
|
||||
|
||||
// If aFrame isn't canonical, forward it to the canonical frame's index.
|
||||
if (aFrame.mJITFrameHandle) {
|
||||
void* canonicalAddr = aFrame.mJITFrameHandle->canonicalAddress();
|
||||
if (canonicalAddr != *aFrame.mJITAddress) {
|
||||
OnStackFrameKey canonicalKey(canonicalAddr, *aFrame.mJITDepth, *aFrame.mJITFrameHandle);
|
||||
uint32_t canonicalIndex = GetOrAddFrameIndex(canonicalKey);
|
||||
mFrameToIndexMap.Put(aFrame, canonicalIndex);
|
||||
return canonicalIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// A manual count is used instead of mFrameToIndexMap.Count() due to
|
||||
// forwarding of canonical JIT frames above.
|
||||
index = mFrameCount++;
|
||||
mFrameToIndexMap.Put(aFrame, index);
|
||||
StreamFrame(aFrame);
|
||||
return index;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t UniqueStacks::LookupJITFrameDepth(void* aAddr)
|
||||
{
|
||||
uint32_t depth;
|
||||
|
||||
auto it = mJITFrameDepthMap.find(aAddr);
|
||||
if (it != mJITFrameDepthMap.end()) {
|
||||
depth = it->second;
|
||||
MOZ_ASSERT(depth > 0);
|
||||
return depth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UniqueStacks::AddJITFrameDepth(void* aAddr, unsigned depth)
|
||||
{
|
||||
mJITFrameDepthMap[aAddr] = depth;
|
||||
}
|
||||
|
||||
void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter)
|
||||
{
|
||||
mFrameTableWriter.EndBareList();
|
||||
aWriter.TakeAndSplice(mFrameTableWriter.WriteFunc());
|
||||
}
|
||||
|
||||
void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter)
|
||||
{
|
||||
mStackTableWriter.EndBareList();
|
||||
aWriter.TakeAndSplice(mStackTableWriter.WriteFunc());
|
||||
}
|
||||
|
||||
void UniqueStacks::StreamStack(const StackKey& aStack)
|
||||
{
|
||||
enum Schema : uint32_t {
|
||||
PREFIX = 0,
|
||||
FRAME = 1
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(mStackTableWriter, mUniqueStrings);
|
||||
if (aStack.mPrefix.isSome()) {
|
||||
writer.IntElement(PREFIX, *aStack.mPrefix);
|
||||
}
|
||||
writer.IntElement(FRAME, aStack.mFrame);
|
||||
}
|
||||
|
||||
void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
enum Schema : uint32_t {
|
||||
LOCATION = 0,
|
||||
IMPLEMENTATION = 1,
|
||||
OPTIMIZATIONS = 2,
|
||||
LINE = 3,
|
||||
CATEGORY = 4
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(mFrameTableWriter, mUniqueStrings);
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
if (!aFrame.mJITFrameHandle) {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
#ifdef SPS_STANDALONE
|
||||
writer.StringElement(LOCATION, aFrame.mLocation.c_str());
|
||||
#else
|
||||
writer.StringElement(LOCATION, aFrame.mLocation.get());
|
||||
#endif
|
||||
if (aFrame.mLine.isSome()) {
|
||||
writer.IntElement(LINE, *aFrame.mLine);
|
||||
}
|
||||
if (aFrame.mCategory.isSome()) {
|
||||
writer.IntElement(CATEGORY, *aFrame.mCategory);
|
||||
}
|
||||
}
|
||||
#ifndef SPS_STANDALONE
|
||||
else {
|
||||
const JS::ForEachProfiledFrameOp::FrameHandle& jitFrame = *aFrame.mJITFrameHandle;
|
||||
|
||||
writer.StringElement(LOCATION, jitFrame.label());
|
||||
|
||||
JS::ProfilingFrameIterator::FrameKind frameKind = jitFrame.frameKind();
|
||||
MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion ||
|
||||
frameKind == JS::ProfilingFrameIterator::Frame_Baseline);
|
||||
writer.StringElement(IMPLEMENTATION,
|
||||
frameKind == JS::ProfilingFrameIterator::Frame_Ion
|
||||
? "ion"
|
||||
: "baseline");
|
||||
|
||||
if (jitFrame.hasTrackedOptimizations()) {
|
||||
writer.FillUpTo(OPTIMIZATIONS);
|
||||
mFrameTableWriter.StartObjectElement();
|
||||
{
|
||||
mFrameTableWriter.StartArrayProperty("types");
|
||||
{
|
||||
StreamOptimizationTypeInfoOp typeInfoOp(mFrameTableWriter, mUniqueStrings);
|
||||
jitFrame.forEachOptimizationTypeInfo(typeInfoOp);
|
||||
}
|
||||
mFrameTableWriter.EndArray();
|
||||
|
||||
JS::Rooted<JSScript*> script(mContext);
|
||||
jsbytecode* pc;
|
||||
mFrameTableWriter.StartObjectProperty("attempts");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(mFrameTableWriter);
|
||||
schema.WriteField("strategy");
|
||||
schema.WriteField("outcome");
|
||||
}
|
||||
|
||||
mFrameTableWriter.StartArrayProperty("data");
|
||||
{
|
||||
StreamOptimizationAttemptsOp attemptOp(mFrameTableWriter, mUniqueStrings);
|
||||
jitFrame.forEachOptimizationAttempt(attemptOp, script.address(), &pc);
|
||||
}
|
||||
mFrameTableWriter.EndArray();
|
||||
}
|
||||
mFrameTableWriter.EndObject();
|
||||
|
||||
if (JSAtom* name = js::GetPropertyNameFromPC(script, pc)) {
|
||||
char buf[512];
|
||||
JS_PutEscapedFlatString(buf, mozilla::ArrayLength(buf), js::AtomToFlatString(name), 0);
|
||||
mUniqueStrings.WriteProperty(mFrameTableWriter, "propertyName", buf);
|
||||
}
|
||||
|
||||
unsigned line, column;
|
||||
line = JS_PCToLineNumber(script, pc, &column);
|
||||
mFrameTableWriter.IntProperty("line", line);
|
||||
mFrameTableWriter.IntProperty("column", column);
|
||||
}
|
||||
mFrameTableWriter.EndObject();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct ProfileSample
|
||||
{
|
||||
uint32_t mStack;
|
||||
Maybe<double> mTime;
|
||||
Maybe<double> mResponsiveness;
|
||||
Maybe<double> mRSS;
|
||||
Maybe<double> mUSS;
|
||||
Maybe<int> mFrameNumber;
|
||||
Maybe<double> mPower;
|
||||
};
|
||||
|
||||
static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample)
|
||||
{
|
||||
enum Schema : uint32_t {
|
||||
STACK = 0,
|
||||
TIME = 1,
|
||||
RESPONSIVENESS = 2,
|
||||
RSS = 3,
|
||||
USS = 4,
|
||||
FRAME_NUMBER = 5,
|
||||
POWER = 6
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(aWriter);
|
||||
|
||||
writer.IntElement(STACK, aSample.mStack);
|
||||
|
||||
if (aSample.mTime.isSome()) {
|
||||
writer.DoubleElement(TIME, *aSample.mTime);
|
||||
}
|
||||
|
||||
if (aSample.mResponsiveness.isSome()) {
|
||||
writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness);
|
||||
}
|
||||
|
||||
if (aSample.mRSS.isSome()) {
|
||||
writer.DoubleElement(RSS, *aSample.mRSS);
|
||||
}
|
||||
|
||||
if (aSample.mUSS.isSome()) {
|
||||
writer.DoubleElement(USS, *aSample.mUSS);
|
||||
}
|
||||
|
||||
if (aSample.mFrameNumber.isSome()) {
|
||||
writer.IntElement(FRAME_NUMBER, *aSample.mFrameNumber);
|
||||
}
|
||||
|
||||
if (aSample.mPower.isSome()) {
|
||||
writer.DoubleElement(POWER, *aSample.mPower);
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
|
||||
double aSinceTime, JSContext* aContext,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
Maybe<ProfileSample> sample;
|
||||
int readPos = mReadPos;
|
||||
int currentThreadID = -1;
|
||||
Maybe<double> currentTime;
|
||||
UniquePtr<char[]> tagBuff = MakeUnique<char[]>(DYNAMIC_MAX_STRING);
|
||||
|
||||
while (readPos != mWritePos) {
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
if (entry.mTagName == 'T') {
|
||||
currentThreadID = entry.mTagInt;
|
||||
currentTime.reset();
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
if (readAheadPos != mWritePos) {
|
||||
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
||||
if (readAheadEntry.mTagName == 't') {
|
||||
currentTime = Some(readAheadEntry.mTagDouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentThreadID == aThreadId && (currentTime.isNothing() || *currentTime >= aSinceTime)) {
|
||||
switch (entry.mTagName) {
|
||||
case 'r':
|
||||
if (sample.isSome()) {
|
||||
sample->mResponsiveness = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
if (sample.isSome()) {
|
||||
sample->mPower = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
if (sample.isSome()) {
|
||||
sample->mRSS = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'U':
|
||||
if (sample.isSome()) {
|
||||
sample->mUSS = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (sample.isSome()) {
|
||||
sample->mFrameNumber = Some(entry.mTagInt);
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
{
|
||||
// end the previous sample if there was one
|
||||
if (sample.isSome()) {
|
||||
WriteSample(aWriter, *sample);
|
||||
sample.reset();
|
||||
}
|
||||
// begin the next sample
|
||||
sample.emplace();
|
||||
sample->mTime = currentTime;
|
||||
|
||||
// Seek forward through the entire sample, looking for frames
|
||||
// this is an easier approach to reason about than adding more
|
||||
// control variables and cases to the loop that goes through the buffer once
|
||||
|
||||
UniqueStacks::Stack stack =
|
||||
aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)"));
|
||||
|
||||
int framePos = (readPos + 1) % mEntrySize;
|
||||
ProfileEntry frame = mEntries[framePos];
|
||||
while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') {
|
||||
int incBy = 1;
|
||||
frame = mEntries[framePos];
|
||||
|
||||
// Read ahead to the next tag, if it's a 'd' tag process it now
|
||||
const char* tagStringData = frame.mTagData;
|
||||
int readAheadPos = (framePos + 1) % mEntrySize;
|
||||
// Make sure the string is always null terminated if it fills up
|
||||
// DYNAMIC_MAX_STRING-2
|
||||
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
||||
|
||||
if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') {
|
||||
tagStringData = processDynamicTag(framePos, &incBy, tagBuff.get());
|
||||
}
|
||||
|
||||
// Write one frame. It can have either
|
||||
// 1. only location - 'l' containing a memory address
|
||||
// 2. location and line number - 'c' followed by 'd's,
|
||||
// an optional 'n' and an optional 'y'
|
||||
// 3. a JIT return address - 'j' containing native code address
|
||||
if (frame.mTagName == 'l') {
|
||||
// Bug 753041
|
||||
// We need a double cast here to tell GCC that we don't want to sign
|
||||
// extend 32-bit addresses starting with 0xFXXXXXX.
|
||||
unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
|
||||
snprintf(tagBuff.get(), DYNAMIC_MAX_STRING, "%#llx", pc);
|
||||
stack.AppendFrame(UniqueStacks::OnStackFrameKey(tagBuff.get()));
|
||||
} else if (frame.mTagName == 'c') {
|
||||
UniqueStacks::OnStackFrameKey frameKey(tagStringData);
|
||||
readAheadPos = (framePos + incBy) % mEntrySize;
|
||||
if (readAheadPos != mWritePos &&
|
||||
mEntries[readAheadPos].mTagName == 'n') {
|
||||
frameKey.mLine = Some((unsigned) mEntries[readAheadPos].mTagInt);
|
||||
incBy++;
|
||||
}
|
||||
readAheadPos = (framePos + incBy) % mEntrySize;
|
||||
if (readAheadPos != mWritePos &&
|
||||
mEntries[readAheadPos].mTagName == 'y') {
|
||||
frameKey.mCategory = Some((unsigned) mEntries[readAheadPos].mTagInt);
|
||||
incBy++;
|
||||
}
|
||||
stack.AppendFrame(frameKey);
|
||||
#ifndef SPS_STANDALONE
|
||||
} else if (frame.mTagName == 'J') {
|
||||
// A JIT frame may expand to multiple frames due to inlining.
|
||||
void* pc = frame.mTagPtr;
|
||||
unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc);
|
||||
if (depth == 0) {
|
||||
StreamJSFramesOp framesOp(pc, stack);
|
||||
JS::ForEachProfiledFrame(aContext, pc, framesOp);
|
||||
aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth());
|
||||
} else {
|
||||
for (unsigned i = 0; i < depth; i++) {
|
||||
UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i);
|
||||
stack.AppendFrame(inlineFrameKey);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
framePos = (framePos + incBy) % mEntrySize;
|
||||
}
|
||||
|
||||
sample->mStack = stack.GetOrAddIndex();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
readPos = (readPos + 1) % mEntrySize;
|
||||
}
|
||||
if (sample.isSome()) {
|
||||
WriteSample(aWriter, *sample);
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
|
||||
double aSinceTime, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
int readPos = mReadPos;
|
||||
int currentThreadID = -1;
|
||||
while (readPos != mWritePos) {
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
if (entry.mTagName == 'T') {
|
||||
currentThreadID = entry.mTagInt;
|
||||
} else if (currentThreadID == aThreadId && entry.mTagName == 'm') {
|
||||
const ProfilerMarker* marker = entry.getMarker();
|
||||
if (marker->GetTime() >= aSinceTime) {
|
||||
entry.getMarker()->StreamJSON(aWriter, aUniqueStacks);
|
||||
}
|
||||
}
|
||||
readPos = (readPos + 1) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
int ProfileBuffer::FindLastSampleOfThread(int aThreadId)
|
||||
{
|
||||
// We search backwards from mWritePos-1 to mReadPos.
|
||||
// Adding mEntrySize makes the result of the modulus positive.
|
||||
for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize;
|
||||
readPos != (mReadPos + mEntrySize - 1) % mEntrySize;
|
||||
readPos = (readPos + mEntrySize - 1) % mEntrySize) {
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) {
|
||||
return readPos;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ProfileBuffer::DuplicateLastSample(int aThreadId)
|
||||
{
|
||||
int lastSampleStartPos = FindLastSampleOfThread(aThreadId);
|
||||
if (lastSampleStartPos == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T');
|
||||
|
||||
addTag(mEntries[lastSampleStartPos]);
|
||||
|
||||
// Go through the whole entry and duplicate it, until we find the next one.
|
||||
for (int readPos = (lastSampleStartPos + 1) % mEntrySize;
|
||||
readPos != mWritePos;
|
||||
readPos = (readPos + 1) % mEntrySize) {
|
||||
switch (mEntries[readPos].mTagName) {
|
||||
case 'T':
|
||||
// We're done.
|
||||
return;
|
||||
case 't':
|
||||
// Copy with new time
|
||||
addTag(ProfileEntry('t', (mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()));
|
||||
break;
|
||||
case 'm':
|
||||
// Don't copy markers
|
||||
break;
|
||||
// Copy anything else we don't know about
|
||||
// L, B, S, c, s, d, l, f, h, r, t, p
|
||||
default:
|
||||
addTag(mEntries[readPos]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// END ProfileBuffer
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN ThreadProfile
|
||||
|
||||
// END ThreadProfile
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_PROFILE_ENTRY_H
|
||||
#define MOZ_PROFILE_ENTRY_H
|
||||
|
||||
#include <ostream>
|
||||
#include "GeckoProfiler.h"
|
||||
#include "platform.h"
|
||||
#include "ProfileJSONWriter.h"
|
||||
#include "ProfilerBacktrace.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "js/ProfilingFrameIterator.h"
|
||||
#include "js/TrackedOptimizationInfo.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#endif
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "gtest/MozGtestFriend.h"
|
||||
#else
|
||||
#define FRIEND_TEST(a, b) // TODO Support standalone gtest
|
||||
#endif
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
class ThreadProfile;
|
||||
|
||||
// NB: Packing this structure has been shown to cause SIGBUS issues on ARM.
|
||||
#ifndef __arm__
|
||||
#pragma pack(push, 1)
|
||||
#endif
|
||||
|
||||
class ProfileEntry
|
||||
{
|
||||
public:
|
||||
ProfileEntry();
|
||||
|
||||
// aTagData must not need release (i.e. be a string from the text segment)
|
||||
ProfileEntry(char aTagName, const char *aTagData);
|
||||
ProfileEntry(char aTagName, void *aTagPtr);
|
||||
ProfileEntry(char aTagName, ProfilerMarker *aTagMarker);
|
||||
ProfileEntry(char aTagName, double aTagDouble);
|
||||
ProfileEntry(char aTagName, uintptr_t aTagOffset);
|
||||
ProfileEntry(char aTagName, Address aTagAddress);
|
||||
ProfileEntry(char aTagName, int aTagLine);
|
||||
ProfileEntry(char aTagName, char aTagChar);
|
||||
bool is_ent_hint(char hintChar);
|
||||
bool is_ent_hint();
|
||||
bool is_ent(char tagName);
|
||||
void* get_tagPtr();
|
||||
const ProfilerMarker* getMarker() {
|
||||
MOZ_ASSERT(mTagName == 'm');
|
||||
return mTagMarker;
|
||||
}
|
||||
|
||||
char getTagName() const { return mTagName; }
|
||||
|
||||
private:
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTag);
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsNoWrap);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsWrap);
|
||||
FRIEND_TEST(ThreadProfile, MemoryMeasure);
|
||||
friend class ProfileBuffer;
|
||||
union {
|
||||
const char* mTagData;
|
||||
char mTagChars[sizeof(void*)];
|
||||
void* mTagPtr;
|
||||
ProfilerMarker* mTagMarker;
|
||||
double mTagDouble;
|
||||
Address mTagAddress;
|
||||
uintptr_t mTagOffset;
|
||||
int mTagInt;
|
||||
char mTagChar;
|
||||
};
|
||||
char mTagName;
|
||||
};
|
||||
|
||||
#ifndef __arm__
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
class UniqueJSONStrings
|
||||
{
|
||||
public:
|
||||
UniqueJSONStrings() {
|
||||
mStringTableWriter.StartBareList();
|
||||
}
|
||||
|
||||
void SpliceStringTableElements(SpliceableJSONWriter& aWriter) {
|
||||
aWriter.TakeAndSplice(mStringTableWriter.WriteFunc());
|
||||
}
|
||||
|
||||
void WriteProperty(mozilla::JSONWriter& aWriter, const char* aName, const char* aStr) {
|
||||
aWriter.IntProperty(aName, GetOrAddIndex(aStr));
|
||||
}
|
||||
|
||||
void WriteElement(mozilla::JSONWriter& aWriter, const char* aStr) {
|
||||
aWriter.IntElement(GetOrAddIndex(aStr));
|
||||
}
|
||||
|
||||
uint32_t GetOrAddIndex(const char* aStr);
|
||||
|
||||
struct StringKey {
|
||||
|
||||
explicit StringKey(const char* aStr)
|
||||
: mStr(strdup(aStr))
|
||||
{
|
||||
mHash = mozilla::HashString(mStr);
|
||||
}
|
||||
|
||||
StringKey(const StringKey& aOther)
|
||||
: mStr(strdup(aOther.mStr))
|
||||
{
|
||||
mHash = aOther.mHash;
|
||||
}
|
||||
|
||||
~StringKey() {
|
||||
free(mStr);
|
||||
}
|
||||
|
||||
uint32_t Hash() const;
|
||||
bool operator==(const StringKey& aOther) const {
|
||||
return strcmp(mStr, aOther.mStr) == 0;
|
||||
}
|
||||
bool operator<(const StringKey& aOther) const {
|
||||
return mHash < aOther.mHash;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mHash;
|
||||
char* mStr;
|
||||
};
|
||||
private:
|
||||
SpliceableChunkedJSONWriter mStringTableWriter;
|
||||
std::map<StringKey, uint32_t> mStringToIndexMap;
|
||||
};
|
||||
|
||||
class UniqueStacks
|
||||
{
|
||||
public:
|
||||
struct FrameKey {
|
||||
#ifdef SPS_STANDALONE
|
||||
std::string mLocation;
|
||||
#else
|
||||
// This cannot be a std::string, as it is not memmove compatible, which
|
||||
// is used by nsHashTable
|
||||
nsCString mLocation;
|
||||
#endif
|
||||
mozilla::Maybe<unsigned> mLine;
|
||||
mozilla::Maybe<unsigned> mCategory;
|
||||
mozilla::Maybe<void*> mJITAddress;
|
||||
mozilla::Maybe<uint32_t> mJITDepth;
|
||||
|
||||
explicit FrameKey(const char* aLocation)
|
||||
: mLocation(aLocation)
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
FrameKey(const FrameKey& aToCopy)
|
||||
: mLocation(aToCopy.mLocation)
|
||||
, mLine(aToCopy.mLine)
|
||||
, mCategory(aToCopy.mCategory)
|
||||
, mJITAddress(aToCopy.mJITAddress)
|
||||
, mJITDepth(aToCopy.mJITDepth)
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
FrameKey(void* aJITAddress, uint32_t aJITDepth)
|
||||
: mJITAddress(mozilla::Some(aJITAddress))
|
||||
, mJITDepth(mozilla::Some(aJITDepth))
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
uint32_t Hash() const;
|
||||
bool operator==(const FrameKey& aOther) const;
|
||||
bool operator<(const FrameKey& aOther) const {
|
||||
return mHash < aOther.mHash;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mHash;
|
||||
};
|
||||
|
||||
// A FrameKey that holds a scoped reference to a JIT FrameHandle.
|
||||
struct MOZ_STACK_CLASS OnStackFrameKey : public FrameKey {
|
||||
explicit OnStackFrameKey(const char* aLocation)
|
||||
: FrameKey(aLocation)
|
||||
#ifndef SPS_STANDALONE
|
||||
, mJITFrameHandle(nullptr)
|
||||
#endif
|
||||
{ }
|
||||
|
||||
OnStackFrameKey(const OnStackFrameKey& aToCopy)
|
||||
: FrameKey(aToCopy)
|
||||
#ifndef SPS_STANDALONE
|
||||
, mJITFrameHandle(aToCopy.mJITFrameHandle)
|
||||
#endif
|
||||
{ }
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
const JS::ForEachProfiledFrameOp::FrameHandle* mJITFrameHandle;
|
||||
|
||||
OnStackFrameKey(void* aJITAddress, unsigned aJITDepth)
|
||||
: FrameKey(aJITAddress, aJITDepth)
|
||||
, mJITFrameHandle(nullptr)
|
||||
{ }
|
||||
|
||||
OnStackFrameKey(void* aJITAddress, unsigned aJITDepth,
|
||||
const JS::ForEachProfiledFrameOp::FrameHandle& aJITFrameHandle)
|
||||
: FrameKey(aJITAddress, aJITDepth)
|
||||
, mJITFrameHandle(&aJITFrameHandle)
|
||||
{ }
|
||||
#endif
|
||||
};
|
||||
|
||||
struct StackKey {
|
||||
mozilla::Maybe<uint32_t> mPrefixHash;
|
||||
mozilla::Maybe<uint32_t> mPrefix;
|
||||
uint32_t mFrame;
|
||||
|
||||
explicit StackKey(uint32_t aFrame)
|
||||
: mFrame(aFrame)
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
uint32_t Hash() const;
|
||||
bool operator==(const StackKey& aOther) const;
|
||||
bool operator<(const StackKey& aOther) const {
|
||||
return mHash < aOther.mHash;
|
||||
}
|
||||
|
||||
void UpdateHash(uint32_t aPrefixHash, uint32_t aPrefix, uint32_t aFrame) {
|
||||
mPrefixHash = mozilla::Some(aPrefixHash);
|
||||
mPrefix = mozilla::Some(aPrefix);
|
||||
mFrame = aFrame;
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mHash;
|
||||
};
|
||||
|
||||
class Stack {
|
||||
public:
|
||||
Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot);
|
||||
|
||||
void AppendFrame(const OnStackFrameKey& aFrame);
|
||||
uint32_t GetOrAddIndex() const;
|
||||
|
||||
private:
|
||||
UniqueStacks& mUniqueStacks;
|
||||
StackKey mStack;
|
||||
};
|
||||
|
||||
explicit UniqueStacks(JSContext* aContext);
|
||||
|
||||
Stack BeginStack(const OnStackFrameKey& aRoot);
|
||||
uint32_t LookupJITFrameDepth(void* aAddr);
|
||||
void AddJITFrameDepth(void* aAddr, unsigned depth);
|
||||
void SpliceFrameTableElements(SpliceableJSONWriter& aWriter);
|
||||
void SpliceStackTableElements(SpliceableJSONWriter& aWriter);
|
||||
|
||||
private:
|
||||
uint32_t GetOrAddFrameIndex(const OnStackFrameKey& aFrame);
|
||||
uint32_t GetOrAddStackIndex(const StackKey& aStack);
|
||||
void StreamFrame(const OnStackFrameKey& aFrame);
|
||||
void StreamStack(const StackKey& aStack);
|
||||
|
||||
public:
|
||||
UniqueJSONStrings mUniqueStrings;
|
||||
|
||||
private:
|
||||
JSContext* mContext;
|
||||
|
||||
// To avoid incurring JitcodeGlobalTable lookup costs for every JIT frame,
|
||||
// we cache the depth of frames keyed by JIT code address. If an address a
|
||||
// maps to a depth d, then frames keyed by a for depths 0 to d are
|
||||
// guaranteed to be in mFrameToIndexMap.
|
||||
std::map<void*, uint32_t> mJITFrameDepthMap;
|
||||
|
||||
uint32_t mFrameCount;
|
||||
SpliceableChunkedJSONWriter mFrameTableWriter;
|
||||
#ifdef SPS_STANDALNOE
|
||||
std::map<FrameKey, uint32_t> mFrameToIndexMap;
|
||||
#else
|
||||
nsDataHashtable<nsGenericHashKey<FrameKey>, uint32_t> mFrameToIndexMap;
|
||||
#endif
|
||||
|
||||
SpliceableChunkedJSONWriter mStackTableWriter;
|
||||
|
||||
// This sucks but this is really performance critical, nsDataHashtable is way faster
|
||||
// than map/unordered_map but nsDataHashtable is tied to xpcom so we ifdef
|
||||
// until we can find a better solution.
|
||||
#ifdef SPS_STANDALONE
|
||||
std::map<StackKey, uint32_t> mStackToIndexMap;
|
||||
#else
|
||||
nsDataHashtable<nsGenericHashKey<StackKey>, uint32_t> mStackToIndexMap;
|
||||
#endif
|
||||
};
|
||||
|
||||
//
|
||||
// ThreadProfile JSON Format
|
||||
// -------------------------
|
||||
//
|
||||
// The profile contains much duplicate information. The output JSON of the
|
||||
// profile attempts to deduplicate strings, frames, and stack prefixes, to cut
|
||||
// down on size and to increase JSON streaming speed. Deduplicated values are
|
||||
// streamed as indices into their respective tables.
|
||||
//
|
||||
// Further, arrays of objects with the same set of properties (e.g., samples,
|
||||
// frames) are output as arrays according to a schema instead of an object
|
||||
// with property names. A property that is not present is represented in the
|
||||
// array as null or undefined.
|
||||
//
|
||||
// The format of the thread profile JSON is shown by the following example
|
||||
// with 1 sample and 1 marker:
|
||||
//
|
||||
// {
|
||||
// "name": "Foo",
|
||||
// "tid": 42,
|
||||
// "samples":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "stack": 0, /* index into stackTable */
|
||||
// "time": 1, /* number */
|
||||
// "responsiveness": 2, /* number */
|
||||
// "rss": 3, /* number */
|
||||
// "uss": 4, /* number */
|
||||
// "frameNumber": 5, /* number */
|
||||
// "power": 6 /* number */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, responsiveness: 0.0 } */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "markers":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "name": 0, /* index into stringTable */
|
||||
// "time": 1, /* number */
|
||||
// "data": 2 /* arbitrary JSON */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "stackTable":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "prefix": 0, /* index into stackTable */
|
||||
// "frame": 1 /* index into frameTable */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ null, 0 ], /* (root) */
|
||||
// [ 0, 1 ] /* (root) > foo.js */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "frameTable":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "location": 0, /* index into stringTable */
|
||||
// "implementation": 1, /* index into stringTable */
|
||||
// "optimizations": 2, /* arbitrary JSON */
|
||||
// "line": 3, /* number */
|
||||
// "category": 4 /* number */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ 0 ], /* { location: '(root)' } */
|
||||
// [ 1, 2 ] /* { location: 'foo.js', implementation: 'baseline' } */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "stringTable":
|
||||
// [
|
||||
// "(root)",
|
||||
// "foo.js",
|
||||
// "baseline",
|
||||
// "example marker"
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
|
||||
#endif /* ndef MOZ_PROFILE_ENTRY_H */
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/HashFunctions.h"
|
||||
|
||||
#include "ProfileJSONWriter.h"
|
||||
|
||||
void
|
||||
ChunkedJSONWriteFunc::Write(const char* aStr)
|
||||
{
|
||||
MOZ_ASSERT(mChunkPtr >= mChunkList.back().get() && mChunkPtr <= mChunkEnd);
|
||||
MOZ_ASSERT(mChunkEnd >= mChunkList.back().get() + mChunkLengths.back());
|
||||
MOZ_ASSERT(*mChunkPtr == '\0');
|
||||
|
||||
size_t len = strlen(aStr);
|
||||
|
||||
// Most strings to be written are small, but subprocess profiles (e.g.,
|
||||
// from the content process in e10s) may be huge. If the string is larger
|
||||
// than a chunk, allocate its own chunk.
|
||||
char* newPtr;
|
||||
if (len >= kChunkSize) {
|
||||
AllocChunk(len + 1);
|
||||
newPtr = mChunkPtr + len;
|
||||
} else {
|
||||
newPtr = mChunkPtr + len;
|
||||
if (newPtr >= mChunkEnd) {
|
||||
AllocChunk(kChunkSize);
|
||||
newPtr = mChunkPtr + len;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(mChunkPtr, aStr, len);
|
||||
*newPtr = '\0';
|
||||
mChunkPtr = newPtr;
|
||||
mChunkLengths.back() += len;
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<char[]>
|
||||
ChunkedJSONWriteFunc::CopyData() const
|
||||
{
|
||||
MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
|
||||
size_t totalLen = 1;
|
||||
for (size_t i = 0; i < mChunkLengths.length(); i++) {
|
||||
MOZ_ASSERT(strlen(mChunkList[i].get()) == mChunkLengths[i]);
|
||||
totalLen += mChunkLengths[i];
|
||||
}
|
||||
mozilla::UniquePtr<char[]> c = mozilla::MakeUnique<char[]>(totalLen);
|
||||
char* ptr = c.get();
|
||||
for (size_t i = 0; i < mChunkList.length(); i++) {
|
||||
size_t len = mChunkLengths[i];
|
||||
memcpy(ptr, mChunkList[i].get(), len);
|
||||
ptr += len;
|
||||
}
|
||||
*ptr = '\0';
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
ChunkedJSONWriteFunc::Take(ChunkedJSONWriteFunc&& aOther)
|
||||
{
|
||||
for (size_t i = 0; i < aOther.mChunkList.length(); i++) {
|
||||
MOZ_ALWAYS_TRUE(mChunkLengths.append(aOther.mChunkLengths[i]));
|
||||
MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(aOther.mChunkList[i])));
|
||||
}
|
||||
mChunkPtr = mChunkList.back().get() + mChunkLengths.back();
|
||||
mChunkEnd = mChunkPtr;
|
||||
aOther.mChunkPtr = nullptr;
|
||||
aOther.mChunkEnd = nullptr;
|
||||
aOther.mChunkList.clear();
|
||||
aOther.mChunkLengths.clear();
|
||||
}
|
||||
|
||||
void
|
||||
ChunkedJSONWriteFunc::AllocChunk(size_t aChunkSize)
|
||||
{
|
||||
MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
|
||||
mozilla::UniquePtr<char[]> newChunk = mozilla::MakeUnique<char[]>(aChunkSize);
|
||||
mChunkPtr = newChunk.get();
|
||||
mChunkEnd = mChunkPtr + aChunkSize;
|
||||
*mChunkPtr = '\0';
|
||||
MOZ_ALWAYS_TRUE(mChunkLengths.append(0));
|
||||
MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(newChunk)));
|
||||
}
|
||||
|
||||
void
|
||||
SpliceableJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc)
|
||||
{
|
||||
Separator();
|
||||
for (size_t i = 0; i < aFunc->mChunkList.length(); i++) {
|
||||
WriteFunc()->Write(aFunc->mChunkList[i].get());
|
||||
}
|
||||
aFunc->mChunkPtr = nullptr;
|
||||
aFunc->mChunkEnd = nullptr;
|
||||
aFunc->mChunkList.clear();
|
||||
aFunc->mChunkLengths.clear();
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
|
||||
void
|
||||
SpliceableJSONWriter::Splice(const char* aStr)
|
||||
{
|
||||
Separator();
|
||||
WriteFunc()->Write(aStr);
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
|
||||
void
|
||||
SpliceableChunkedJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc)
|
||||
{
|
||||
Separator();
|
||||
WriteFunc()->Take(mozilla::Move(*aFunc));
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef PROFILEJSONWRITER_H
|
||||
#define PROFILEJSONWRITER_H
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
#include "mozilla/JSONWriter.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
class SpliceableChunkedJSONWriter;
|
||||
|
||||
// On average, profile JSONs are large enough such that we want to avoid
|
||||
// reallocating its buffer when expanding. Additionally, the contents of the
|
||||
// profile are not accessed until the profile is entirely written. For these
|
||||
// reasons we use a chunked writer that keeps an array of chunks, which is
|
||||
// concatenated together after writing is finished.
|
||||
class ChunkedJSONWriteFunc : public mozilla::JSONWriteFunc
|
||||
{
|
||||
public:
|
||||
friend class SpliceableJSONWriter;
|
||||
|
||||
ChunkedJSONWriteFunc() {
|
||||
AllocChunk(kChunkSize);
|
||||
}
|
||||
|
||||
bool IsEmpty() const {
|
||||
MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd &&
|
||||
mChunkList.length() == 0 &&
|
||||
mChunkLengths.length() == 0);
|
||||
return !mChunkPtr;
|
||||
}
|
||||
|
||||
void Write(const char* aStr) override;
|
||||
mozilla::UniquePtr<char[]> CopyData() const;
|
||||
void Take(ChunkedJSONWriteFunc&& aOther);
|
||||
|
||||
private:
|
||||
void AllocChunk(size_t aChunkSize);
|
||||
|
||||
static const size_t kChunkSize = 4096 * 512;
|
||||
|
||||
// Pointer for writing inside the current chunk.
|
||||
//
|
||||
// The current chunk is always at the back of mChunkList, i.e.,
|
||||
// mChunkList.back() <= mChunkPtr <= mChunkEnd.
|
||||
char* mChunkPtr;
|
||||
|
||||
// Pointer to the end of the current chunk.
|
||||
//
|
||||
// The current chunk is always at the back of mChunkList, i.e.,
|
||||
// mChunkEnd >= mChunkList.back() + mChunkLengths.back().
|
||||
char* mChunkEnd;
|
||||
|
||||
// List of chunks and their lengths.
|
||||
//
|
||||
// For all i, the length of the string in mChunkList[i] is
|
||||
// mChunkLengths[i].
|
||||
mozilla::Vector<mozilla::UniquePtr<char[]>> mChunkList;
|
||||
mozilla::Vector<size_t> mChunkLengths;
|
||||
};
|
||||
|
||||
struct OStreamJSONWriteFunc : public mozilla::JSONWriteFunc
|
||||
{
|
||||
explicit OStreamJSONWriteFunc(std::ostream& aStream)
|
||||
: mStream(aStream)
|
||||
{ }
|
||||
|
||||
void Write(const char* aStr) override {
|
||||
mStream << aStr;
|
||||
}
|
||||
|
||||
std::ostream& mStream;
|
||||
};
|
||||
|
||||
class SpliceableJSONWriter : public mozilla::JSONWriter
|
||||
{
|
||||
public:
|
||||
explicit SpliceableJSONWriter(mozilla::UniquePtr<mozilla::JSONWriteFunc> aWriter)
|
||||
: JSONWriter(mozilla::Move(aWriter))
|
||||
{ }
|
||||
|
||||
void StartBareList(CollectionStyle aStyle = SingleLineStyle) {
|
||||
StartCollection(nullptr, "", aStyle);
|
||||
}
|
||||
|
||||
void EndBareList() {
|
||||
EndCollection("");
|
||||
}
|
||||
|
||||
void NullElements(uint32_t aCount) {
|
||||
for (uint32_t i = 0; i < aCount; i++) {
|
||||
NullElement();
|
||||
}
|
||||
}
|
||||
|
||||
void Splice(const ChunkedJSONWriteFunc* aFunc);
|
||||
void Splice(const char* aStr);
|
||||
|
||||
// Takes the chunks from aFunc and write them. If move is not possible
|
||||
// (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its
|
||||
// storage cleared.
|
||||
virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc);
|
||||
};
|
||||
|
||||
class SpliceableChunkedJSONWriter : public SpliceableJSONWriter
|
||||
{
|
||||
public:
|
||||
explicit SpliceableChunkedJSONWriter()
|
||||
: SpliceableJSONWriter(mozilla::MakeUnique<ChunkedJSONWriteFunc>())
|
||||
{ }
|
||||
|
||||
ChunkedJSONWriteFunc* WriteFunc() const {
|
||||
return static_cast<ChunkedJSONWriteFunc*>(JSONWriter::WriteFunc());
|
||||
}
|
||||
|
||||
// Adopts the chunks from aFunc without copying.
|
||||
virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc) override;
|
||||
};
|
||||
|
||||
#endif // PROFILEJSONWRITER_H
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ProfilerBacktrace.h"
|
||||
|
||||
#include "ProfileJSONWriter.h"
|
||||
#include "SyncProfile.h"
|
||||
|
||||
ProfilerBacktrace::ProfilerBacktrace(SyncProfile* aProfile)
|
||||
: mProfile(aProfile)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ProfilerBacktrace);
|
||||
MOZ_ASSERT(aProfile);
|
||||
}
|
||||
|
||||
ProfilerBacktrace::~ProfilerBacktrace()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ProfilerBacktrace);
|
||||
if (mProfile->ShouldDestroy()) {
|
||||
delete mProfile;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerBacktrace::StreamJSON(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
::MutexAutoLock lock(mProfile->GetMutex());
|
||||
mProfile->StreamJSON(aWriter, aUniqueStacks);
|
||||
}
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
#include "ProfilerBacktrace.h"
|
||||
#include "ProfilerMarkers.h"
|
||||
#include "SyncProfile.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "gfxASurface.h"
|
||||
#include "Layers.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#endif
|
||||
|
||||
ProfilerMarkerPayload::ProfilerMarkerPayload(ProfilerBacktrace* aStack)
|
||||
: mStack(aStack)
|
||||
{}
|
||||
|
||||
ProfilerMarkerPayload::ProfilerMarkerPayload(const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack)
|
||||
: mStartTime(aStartTime)
|
||||
, mEndTime(aEndTime)
|
||||
, mStack(aStack)
|
||||
{}
|
||||
|
||||
ProfilerMarkerPayload::~ProfilerMarkerPayload()
|
||||
{
|
||||
profiler_free_backtrace(mStack);
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerMarkerPayload::streamCommonProps(const char* aMarkerType,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
MOZ_ASSERT(aMarkerType);
|
||||
aWriter.StringProperty("type", aMarkerType);
|
||||
if (!mStartTime.IsNull()) {
|
||||
aWriter.DoubleProperty("startTime", profiler_time(mStartTime));
|
||||
}
|
||||
if (!mEndTime.IsNull()) {
|
||||
aWriter.DoubleProperty("endTime", profiler_time(mEndTime));
|
||||
}
|
||||
if (mStack) {
|
||||
aWriter.StartObjectProperty("stack");
|
||||
{
|
||||
mStack->StreamJSON(aWriter, aUniqueStacks);
|
||||
}
|
||||
aWriter.EndObject();
|
||||
}
|
||||
}
|
||||
|
||||
ProfilerMarkerTracing::ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData)
|
||||
: mCategory(aCategory)
|
||||
, mMetaData(aMetaData)
|
||||
{
|
||||
if (aMetaData == TRACING_EVENT_BACKTRACE) {
|
||||
SetStack(profiler_get_backtrace());
|
||||
}
|
||||
}
|
||||
|
||||
ProfilerMarkerTracing::ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData,
|
||||
ProfilerBacktrace* aCause)
|
||||
: mCategory(aCategory)
|
||||
, mMetaData(aMetaData)
|
||||
{
|
||||
if (aCause) {
|
||||
SetStack(aCause);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerMarkerTracing::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("tracing", aWriter, aUniqueStacks);
|
||||
|
||||
if (GetCategory()) {
|
||||
aWriter.StringProperty("category", GetCategory());
|
||||
}
|
||||
if (GetMetaData() != TRACING_DEFAULT) {
|
||||
if (GetMetaData() == TRACING_INTERVAL_START) {
|
||||
aWriter.StringProperty("interval", "start");
|
||||
} else if (GetMetaData() == TRACING_INTERVAL_END) {
|
||||
aWriter.StringProperty("interval", "end");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
GPUMarkerPayload::GPUMarkerPayload(
|
||||
const mozilla::TimeStamp& aCpuTimeStart,
|
||||
const mozilla::TimeStamp& aCpuTimeEnd,
|
||||
uint64_t aGpuTimeStart,
|
||||
uint64_t aGpuTimeEnd)
|
||||
|
||||
: ProfilerMarkerPayload(aCpuTimeStart, aCpuTimeEnd)
|
||||
, mCpuTimeStart(aCpuTimeStart)
|
||||
, mCpuTimeEnd(aCpuTimeEnd)
|
||||
, mGpuTimeStart(aGpuTimeStart)
|
||||
, mGpuTimeEnd(aGpuTimeEnd)
|
||||
{ }
|
||||
|
||||
void
|
||||
GPUMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("gpu_timer_query", aWriter, aUniqueStacks);
|
||||
|
||||
aWriter.DoubleProperty("cpustart", profiler_time(mCpuTimeStart));
|
||||
aWriter.DoubleProperty("cpuend", profiler_time(mCpuTimeEnd));
|
||||
aWriter.IntProperty("gpustart", (int)mGpuTimeStart);
|
||||
aWriter.IntProperty("gpuend", (int)mGpuTimeEnd);
|
||||
}
|
||||
|
||||
ProfilerMarkerImagePayload::ProfilerMarkerImagePayload(gfxASurface *aImg)
|
||||
: mImg(aImg)
|
||||
{ }
|
||||
|
||||
void
|
||||
ProfilerMarkerImagePayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("innerHTML", aWriter, aUniqueStacks);
|
||||
// TODO: Finish me
|
||||
//aWriter.NameValue("innerHTML", "<img src=''/>");
|
||||
}
|
||||
|
||||
IOMarkerPayload::IOMarkerPayload(const char* aSource,
|
||||
const char* aFilename,
|
||||
const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack)
|
||||
: ProfilerMarkerPayload(aStartTime, aEndTime, aStack),
|
||||
mSource(aSource)
|
||||
{
|
||||
mFilename = aFilename ? strdup(aFilename) : nullptr;
|
||||
MOZ_ASSERT(aSource);
|
||||
}
|
||||
|
||||
IOMarkerPayload::~IOMarkerPayload(){
|
||||
free(mFilename);
|
||||
}
|
||||
|
||||
void
|
||||
IOMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("io", aWriter, aUniqueStacks);
|
||||
aWriter.StringProperty("source", mSource);
|
||||
if (mFilename != nullptr) {
|
||||
aWriter.StringProperty("filename", mFilename);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerJSEventMarker(const char *event)
|
||||
{
|
||||
PROFILER_MARKER(event);
|
||||
}
|
||||
|
||||
LayerTranslationPayload::LayerTranslationPayload(mozilla::layers::Layer* aLayer,
|
||||
mozilla::gfx::Point aPoint)
|
||||
: ProfilerMarkerPayload(mozilla::TimeStamp::Now(), mozilla::TimeStamp::Now(), nullptr)
|
||||
, mLayer(aLayer)
|
||||
, mPoint(aPoint)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
LayerTranslationPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
const size_t bufferSize = 32;
|
||||
char buffer[bufferSize];
|
||||
SprintfLiteral(buffer, "%p", mLayer);
|
||||
|
||||
aWriter.StringProperty("layer", buffer);
|
||||
aWriter.IntProperty("x", mPoint.x);
|
||||
aWriter.IntProperty("y", mPoint.y);
|
||||
aWriter.StringProperty("category", "LayerTranslation");
|
||||
}
|
||||
|
||||
TouchDataPayload::TouchDataPayload(const mozilla::ScreenIntPoint& aPoint)
|
||||
: ProfilerMarkerPayload(mozilla::TimeStamp::Now(), mozilla::TimeStamp::Now(), nullptr)
|
||||
{
|
||||
mPoint = aPoint;
|
||||
}
|
||||
|
||||
void
|
||||
TouchDataPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
aWriter.IntProperty("x", mPoint.x);
|
||||
aWriter.IntProperty("y", mPoint.y);
|
||||
}
|
||||
|
||||
VsyncPayload::VsyncPayload(mozilla::TimeStamp aVsyncTimestamp)
|
||||
: ProfilerMarkerPayload(aVsyncTimestamp, aVsyncTimestamp, nullptr)
|
||||
, mVsyncTimestamp(aVsyncTimestamp)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
VsyncPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
aWriter.DoubleProperty("vsync", profiler_time(mVsyncTimestamp));
|
||||
aWriter.StringProperty("category", "VsyncTimestamp");
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#include <mach/task.h>
|
||||
#include <mach/thread_act.h>
|
||||
#include <pthread.h>
|
||||
#elif XP_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "StackTop.h"
|
||||
|
||||
void *GetStackTop(void *guess) {
|
||||
#if defined(XP_MACOSX)
|
||||
pthread_t thread = pthread_self();
|
||||
return pthread_get_stackaddr_np(thread);
|
||||
#elif defined(XP_WIN)
|
||||
#if defined(_MSC_VER) && defined(_M_IX86)
|
||||
// offset 0x18 from the FS segment register gives a pointer to
|
||||
// the thread information block for the current thread
|
||||
NT_TIB* pTib;
|
||||
__asm {
|
||||
MOV EAX, FS:[18h]
|
||||
MOV pTib, EAX
|
||||
}
|
||||
return static_cast<void*>(pTib->StackBase);
|
||||
#elif defined(__GNUC__) && defined(i386)
|
||||
// offset 0x18 from the FS segment register gives a pointer to
|
||||
// the thread information block for the current thread
|
||||
NT_TIB* pTib;
|
||||
asm ( "movl %%fs:0x18, %0\n"
|
||||
: "=r" (pTib)
|
||||
);
|
||||
return static_cast<void*>(pTib->StackBase);
|
||||
#elif defined(_M_X64) || defined(__x86_64)
|
||||
PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb());
|
||||
return reinterpret_cast<void*>(pTib->StackBase);
|
||||
#else
|
||||
#error Need a way to get the stack bounds on this platform (Windows)
|
||||
#endif
|
||||
#else
|
||||
return guess;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_STACK_TOP_H
|
||||
#define MOZ_STACK_TOP_H
|
||||
void *GetStackTop(void *guess);
|
||||
#endif
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "SyncProfile.h"
|
||||
|
||||
SyncProfile::SyncProfile(ThreadInfo* aInfo, int aEntrySize)
|
||||
: ThreadProfile(aInfo, new ProfileBuffer(aEntrySize))
|
||||
, mOwnerState(REFERENCED)
|
||||
{
|
||||
MOZ_COUNT_CTOR(SyncProfile);
|
||||
}
|
||||
|
||||
SyncProfile::~SyncProfile()
|
||||
{
|
||||
MOZ_COUNT_DTOR(SyncProfile);
|
||||
|
||||
// SyncProfile owns the ThreadInfo; see NewSyncProfile.
|
||||
ThreadInfo* info = GetThreadInfo();
|
||||
delete info;
|
||||
}
|
||||
|
||||
bool
|
||||
SyncProfile::ShouldDestroy()
|
||||
{
|
||||
::MutexAutoLock lock(GetMutex());
|
||||
if (mOwnerState == OWNED) {
|
||||
mOwnerState = OWNER_DESTROYING;
|
||||
return true;
|
||||
}
|
||||
mOwnerState = ORPHANED;
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
SyncProfile::EndUnwind()
|
||||
{
|
||||
if (mOwnerState != ORPHANED) {
|
||||
mOwnerState = OWNED;
|
||||
}
|
||||
// Save mOwnerState before we release the mutex
|
||||
OwnerState ownerState = mOwnerState;
|
||||
ThreadProfile::EndUnwind();
|
||||
if (ownerState == ORPHANED) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
// SyncProfiles' stacks are deduplicated in the context of the containing
|
||||
// profile in which the backtrace is as a marker payload.
|
||||
void
|
||||
SyncProfile::StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
ThreadProfile::StreamSamplesAndMarkers(aWriter, /* aSinceTime = */ 0, aUniqueStacks);
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef __SYNCPROFILE_H
|
||||
#define __SYNCPROFILE_H
|
||||
|
||||
#include "ProfileEntry.h"
|
||||
#include "ThreadProfile.h"
|
||||
|
||||
class SyncProfile : public ThreadProfile
|
||||
{
|
||||
public:
|
||||
SyncProfile(ThreadInfo* aInfo, int aEntrySize);
|
||||
~SyncProfile();
|
||||
|
||||
// SyncProfiles' stacks are deduplicated in the context of the containing
|
||||
// profile in which the backtrace is as a marker payload.
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks);
|
||||
|
||||
virtual void EndUnwind();
|
||||
virtual SyncProfile* AsSyncProfile() { return this; }
|
||||
|
||||
private:
|
||||
friend class ProfilerBacktrace;
|
||||
|
||||
enum OwnerState
|
||||
{
|
||||
REFERENCED, // ProfilerBacktrace has a pointer to this but doesn't own
|
||||
OWNED, // ProfilerBacktrace is responsible for destroying this
|
||||
OWNER_DESTROYING, // ProfilerBacktrace owns this and is destroying
|
||||
ORPHANED // No owner, we must destroy ourselves
|
||||
};
|
||||
|
||||
bool ShouldDestroy();
|
||||
|
||||
OwnerState mOwnerState;
|
||||
};
|
||||
|
||||
#endif // __SYNCPROFILE_H
|
||||
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ThreadInfo.h"
|
||||
#include "ThreadProfile.h"
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
ThreadInfo::ThreadInfo(const char* aName, int aThreadId,
|
||||
bool aIsMainThread, PseudoStack* aPseudoStack,
|
||||
void* aStackTop)
|
||||
: mName(strdup(aName))
|
||||
, mThreadId(aThreadId)
|
||||
, mIsMainThread(aIsMainThread)
|
||||
, mPseudoStack(aPseudoStack)
|
||||
, mPlatformData(Sampler::AllocPlatformData(aThreadId))
|
||||
, mProfile(nullptr)
|
||||
, mStackTop(aStackTop)
|
||||
, mPendingDelete(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ThreadInfo);
|
||||
#ifndef SPS_STANDALONE
|
||||
mThread = NS_GetCurrentThread();
|
||||
#endif
|
||||
|
||||
// We don't have to guess on mac
|
||||
#ifdef XP_MACOSX
|
||||
pthread_t self = pthread_self();
|
||||
mStackTop = pthread_get_stackaddr_np(self);
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadInfo::~ThreadInfo() {
|
||||
MOZ_COUNT_DTOR(ThreadInfo);
|
||||
free(mName);
|
||||
|
||||
if (mProfile)
|
||||
delete mProfile;
|
||||
|
||||
Sampler::FreePlatformData(mPlatformData);
|
||||
}
|
||||
|
||||
void
|
||||
ThreadInfo::SetPendingDelete()
|
||||
{
|
||||
mPendingDelete = true;
|
||||
// We don't own the pseudostack so disconnect it.
|
||||
mPseudoStack = nullptr;
|
||||
if (mProfile) {
|
||||
mProfile->SetPendingDelete();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ThreadInfo::CanInvokeJS() const
|
||||
{
|
||||
#ifdef SPS_STANDALONE
|
||||
return false;
|
||||
#else
|
||||
nsIThread* thread = GetThread();
|
||||
if (!thread) {
|
||||
MOZ_ASSERT(IsMainThread());
|
||||
return true;
|
||||
}
|
||||
bool result;
|
||||
mozilla::DebugOnly<nsresult> rv = thread->GetCanInvokeJS(&result);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_THREAD_INFO_H
|
||||
#define MOZ_THREAD_INFO_H
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
class ThreadInfo {
|
||||
public:
|
||||
ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack, void* aStackTop);
|
||||
|
||||
virtual ~ThreadInfo();
|
||||
|
||||
const char* Name() const { return mName; }
|
||||
int ThreadId() const { return mThreadId; }
|
||||
|
||||
bool IsMainThread() const { return mIsMainThread; }
|
||||
PseudoStack* Stack() const { return mPseudoStack; }
|
||||
|
||||
void SetProfile(ThreadProfile* aProfile) { mProfile = aProfile; }
|
||||
ThreadProfile* Profile() const { return mProfile; }
|
||||
|
||||
PlatformData* GetPlatformData() const { return mPlatformData; }
|
||||
void* StackTop() const { return mStackTop; }
|
||||
|
||||
virtual void SetPendingDelete();
|
||||
bool IsPendingDelete() const { return mPendingDelete; }
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
/**
|
||||
* May be null for the main thread if the profiler was started during startup
|
||||
*/
|
||||
nsIThread* GetThread() const { return mThread.get(); }
|
||||
|
||||
#endif
|
||||
|
||||
bool CanInvokeJS() const;
|
||||
|
||||
private:
|
||||
char* mName;
|
||||
int mThreadId;
|
||||
const bool mIsMainThread;
|
||||
PseudoStack* mPseudoStack;
|
||||
PlatformData* mPlatformData;
|
||||
ThreadProfile* mProfile;
|
||||
void* mStackTop;
|
||||
#ifndef SPS_STANDALONE
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
#endif
|
||||
bool mPendingDelete;
|
||||
};
|
||||
|
||||
// Just like ThreadInfo, but owns a reference to the PseudoStack.
|
||||
class StackOwningThreadInfo : public ThreadInfo {
|
||||
public:
|
||||
StackOwningThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack, void* aStackTop);
|
||||
virtual ~StackOwningThreadInfo();
|
||||
|
||||
virtual void SetPendingDelete();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
ThreadProfile::ThreadProfile(ThreadInfo* aInfo, ProfileBuffer* aBuffer)
|
||||
: mThreadInfo(aInfo)
|
||||
, mBuffer(aBuffer)
|
||||
, mPseudoStack(aInfo->Stack())
|
||||
, mMutex(OS::CreateMutex("ThreadProfile::mMutex"))
|
||||
, mThreadId(int(aInfo->ThreadId()))
|
||||
, mIsMainThread(aInfo->IsMainThread())
|
||||
, mPlatformData(aInfo->GetPlatformData())
|
||||
, mStackTop(aInfo->StackTop())
|
||||
#ifndef SPS_STANDALONE
|
||||
, mRespInfo(this)
|
||||
#endif
|
||||
#ifdef XP_LINUX
|
||||
, mRssMemory(0)
|
||||
, mUssMemory(0)
|
||||
#endif
|
||||
{
|
||||
MOZ_COUNT_CTOR(ThreadProfile);
|
||||
MOZ_ASSERT(aBuffer);
|
||||
|
||||
// I don't know if we can assert this. But we should warn.
|
||||
MOZ_ASSERT(aInfo->ThreadId() >= 0, "native thread ID is < 0");
|
||||
MOZ_ASSERT(aInfo->ThreadId() <= INT32_MAX, "native thread ID is > INT32_MAX");
|
||||
}
|
||||
|
||||
ThreadProfile::~ThreadProfile()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ThreadProfile);
|
||||
}
|
||||
|
||||
void ThreadProfile::addTag(const ProfileEntry& aTag)
|
||||
{
|
||||
mBuffer->addTag(aTag);
|
||||
}
|
||||
|
||||
void ThreadProfile::addStoredMarker(ProfilerMarker *aStoredMarker) {
|
||||
mBuffer->addStoredMarker(aStoredMarker);
|
||||
}
|
||||
|
||||
void ThreadProfile::StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime)
|
||||
{
|
||||
// mUniqueStacks may already be emplaced from FlushSamplesAndMarkers.
|
||||
if (!mUniqueStacks.isSome()) {
|
||||
#ifndef SPS_STANDALONE
|
||||
mUniqueStacks.emplace(mPseudoStack->mContext);
|
||||
#else
|
||||
mUniqueStacks.emplace(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
aWriter.Start(SpliceableJSONWriter::SingleLineStyle);
|
||||
{
|
||||
StreamSamplesAndMarkers(aWriter, aSinceTime, *mUniqueStacks);
|
||||
|
||||
aWriter.StartObjectProperty("stackTable");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("prefix");
|
||||
schema.WriteField("frame");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
mUniqueStacks->SpliceStackTableElements(aWriter);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
|
||||
aWriter.StartObjectProperty("frameTable");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("location");
|
||||
schema.WriteField("implementation");
|
||||
schema.WriteField("optimizations");
|
||||
schema.WriteField("line");
|
||||
schema.WriteField("category");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
mUniqueStacks->SpliceFrameTableElements(aWriter);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
|
||||
aWriter.StartArrayProperty("stringTable");
|
||||
{
|
||||
mUniqueStacks->mUniqueStrings.SpliceStringTableElements(aWriter);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.End();
|
||||
|
||||
mUniqueStacks.reset();
|
||||
}
|
||||
|
||||
void ThreadProfile::StreamSamplesAndMarkers(SpliceableJSONWriter& aWriter, double aSinceTime,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
#ifndef SPS_STANDALONE
|
||||
// Thread meta data
|
||||
if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
|
||||
// TODO Add the proper plugin name
|
||||
aWriter.StringProperty("name", "Plugin");
|
||||
} else if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
||||
// This isn't going to really help once we have multiple content
|
||||
// processes, but it'll do for now.
|
||||
aWriter.StringProperty("name", "Content");
|
||||
} else {
|
||||
aWriter.StringProperty("name", Name());
|
||||
}
|
||||
#else
|
||||
aWriter.StringProperty("name", Name());
|
||||
#endif
|
||||
|
||||
aWriter.IntProperty("tid", static_cast<int>(mThreadId));
|
||||
|
||||
aWriter.StartObjectProperty("samples");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("stack");
|
||||
schema.WriteField("time");
|
||||
schema.WriteField("responsiveness");
|
||||
schema.WriteField("rss");
|
||||
schema.WriteField("uss");
|
||||
schema.WriteField("frameNumber");
|
||||
schema.WriteField("power");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
if (mSavedStreamedSamples) {
|
||||
// We would only have saved streamed samples during shutdown
|
||||
// streaming, which cares about dumping the entire buffer, and thus
|
||||
// should have passed in 0 for aSinceTime.
|
||||
MOZ_ASSERT(aSinceTime == 0);
|
||||
aWriter.Splice(mSavedStreamedSamples.get());
|
||||
mSavedStreamedSamples.reset();
|
||||
}
|
||||
mBuffer->StreamSamplesToJSON(aWriter, mThreadId, aSinceTime,
|
||||
#ifndef SPS_STANDALONE
|
||||
mPseudoStack->mContext,
|
||||
#else
|
||||
nullptr,
|
||||
#endif
|
||||
aUniqueStacks);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
|
||||
aWriter.StartObjectProperty("markers");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("name");
|
||||
schema.WriteField("time");
|
||||
schema.WriteField("data");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
if (mSavedStreamedMarkers) {
|
||||
MOZ_ASSERT(aSinceTime == 0);
|
||||
aWriter.Splice(mSavedStreamedMarkers.get());
|
||||
mSavedStreamedMarkers.reset();
|
||||
}
|
||||
mBuffer->StreamMarkersToJSON(aWriter, mThreadId, aSinceTime, aUniqueStacks);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
}
|
||||
|
||||
void ThreadProfile::FlushSamplesAndMarkers()
|
||||
{
|
||||
// This function is used to serialize the current buffer just before
|
||||
// JSContext destruction.
|
||||
MOZ_ASSERT(mPseudoStack->mContext);
|
||||
|
||||
// Unlike StreamJSObject, do not surround the samples in brackets by calling
|
||||
// aWriter.{Start,End}BareList. The result string will be a comma-separated
|
||||
// list of JSON object literals that will prepended by StreamJSObject into
|
||||
// an existing array.
|
||||
//
|
||||
// Note that the UniqueStacks instance is persisted so that the frame-index
|
||||
// mapping is stable across JS shutdown.
|
||||
#ifndef SPS_STANDALONE
|
||||
mUniqueStacks.emplace(mPseudoStack->mContext);
|
||||
#else
|
||||
mUniqueStacks.emplace(nullptr);
|
||||
#endif
|
||||
|
||||
{
|
||||
SpliceableChunkedJSONWriter b;
|
||||
b.StartBareList();
|
||||
{
|
||||
mBuffer->StreamSamplesToJSON(b, mThreadId, /* aSinceTime = */ 0,
|
||||
#ifndef SPS_STANDALONE
|
||||
mPseudoStack->mContext,
|
||||
#else
|
||||
nullptr,
|
||||
#endif
|
||||
*mUniqueStacks);
|
||||
}
|
||||
b.EndBareList();
|
||||
mSavedStreamedSamples = b.WriteFunc()->CopyData();
|
||||
}
|
||||
|
||||
{
|
||||
SpliceableChunkedJSONWriter b;
|
||||
b.StartBareList();
|
||||
{
|
||||
mBuffer->StreamMarkersToJSON(b, mThreadId, /* aSinceTime = */ 0, *mUniqueStacks);
|
||||
}
|
||||
b.EndBareList();
|
||||
mSavedStreamedMarkers = b.WriteFunc()->CopyData();
|
||||
}
|
||||
|
||||
// Reset the buffer. Attempting to symbolicate JS samples after mContext has
|
||||
// gone away will crash.
|
||||
mBuffer->reset();
|
||||
}
|
||||
|
||||
PseudoStack* ThreadProfile::GetPseudoStack()
|
||||
{
|
||||
return mPseudoStack;
|
||||
}
|
||||
|
||||
void ThreadProfile::BeginUnwind()
|
||||
{
|
||||
mMutex->Lock();
|
||||
}
|
||||
|
||||
void ThreadProfile::EndUnwind()
|
||||
{
|
||||
mMutex->Unlock();
|
||||
}
|
||||
|
||||
::Mutex& ThreadProfile::GetMutex()
|
||||
{
|
||||
return *mMutex.get();
|
||||
}
|
||||
|
||||
void ThreadProfile::DuplicateLastSample()
|
||||
{
|
||||
mBuffer->DuplicateLastSample(mThreadId);
|
||||
}
|
||||
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_THREAD_PROFILE_H
|
||||
#define MOZ_THREAD_PROFILE_H
|
||||
|
||||
#include "ProfileBuffer.h"
|
||||
#include "ThreadInfo.h"
|
||||
|
||||
class ThreadProfile
|
||||
{
|
||||
public:
|
||||
ThreadProfile(ThreadInfo* aThreadInfo, ProfileBuffer* aBuffer);
|
||||
virtual ~ThreadProfile();
|
||||
void addTag(const ProfileEntry& aTag);
|
||||
|
||||
/**
|
||||
* Track a marker which has been inserted into the ThreadProfile.
|
||||
* This marker can safely be deleted once the generation has
|
||||
* expired.
|
||||
*/
|
||||
void addStoredMarker(ProfilerMarker *aStoredMarker);
|
||||
PseudoStack* GetPseudoStack();
|
||||
::Mutex& GetMutex();
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime = 0);
|
||||
|
||||
/**
|
||||
* Call this method when the JS entries inside the buffer are about to
|
||||
* become invalid, i.e., just before JS shutdown.
|
||||
*/
|
||||
void FlushSamplesAndMarkers();
|
||||
|
||||
void BeginUnwind();
|
||||
virtual void EndUnwind();
|
||||
virtual SyncProfile* AsSyncProfile() { return nullptr; }
|
||||
|
||||
bool IsMainThread() const { return mIsMainThread; }
|
||||
const char* Name() const { return mThreadInfo->Name(); }
|
||||
int ThreadId() const { return mThreadId; }
|
||||
|
||||
PlatformData* GetPlatformData() const { return mPlatformData; }
|
||||
void* GetStackTop() const { return mStackTop; }
|
||||
void DuplicateLastSample();
|
||||
|
||||
ThreadInfo* GetThreadInfo() const { return mThreadInfo; }
|
||||
#ifndef SPS_STANDALONE
|
||||
ThreadResponsiveness* GetThreadResponsiveness() { return &mRespInfo; }
|
||||
#endif
|
||||
|
||||
bool CanInvokeJS() const { return mThreadInfo->CanInvokeJS(); }
|
||||
|
||||
void SetPendingDelete()
|
||||
{
|
||||
mPseudoStack = nullptr;
|
||||
mPlatformData = nullptr;
|
||||
}
|
||||
|
||||
uint32_t bufferGeneration() const {
|
||||
return mBuffer->mGeneration;
|
||||
}
|
||||
|
||||
protected:
|
||||
void StreamSamplesAndMarkers(SpliceableJSONWriter& aWriter, double aSinceTime,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
|
||||
private:
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTag);
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsNoWrap);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsWrap);
|
||||
FRIEND_TEST(ThreadProfile, MemoryMeasure);
|
||||
ThreadInfo* mThreadInfo;
|
||||
|
||||
const RefPtr<ProfileBuffer> mBuffer;
|
||||
|
||||
// JS frames in the buffer may require a live JSRuntime to stream (e.g.,
|
||||
// stringifying JIT frames). In the case of JSRuntime destruction,
|
||||
// FlushSamplesAndMarkers should be called to save them. These are spliced
|
||||
// into the final stream.
|
||||
mozilla::UniquePtr<char[]> mSavedStreamedSamples;
|
||||
mozilla::UniquePtr<char[]> mSavedStreamedMarkers;
|
||||
mozilla::Maybe<UniqueStacks> mUniqueStacks;
|
||||
|
||||
PseudoStack* mPseudoStack;
|
||||
mozilla::UniquePtr<Mutex> mMutex;
|
||||
int mThreadId;
|
||||
bool mIsMainThread;
|
||||
PlatformData* mPlatformData; // Platform specific data.
|
||||
void* const mStackTop;
|
||||
#ifndef SPS_STANDALONE
|
||||
ThreadResponsiveness mRespInfo;
|
||||
#endif
|
||||
|
||||
// Only Linux is using a signal sender, instead of stopping the thread, so we
|
||||
// need some space to store the data which cannot be collected in the signal
|
||||
// handler code.
|
||||
#ifdef XP_LINUX
|
||||
public:
|
||||
int64_t mRssMemory;
|
||||
int64_t mUssMemory;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,715 +0,0 @@
|
|||
// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google, Inc. nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
/*
|
||||
# vim: sw=2
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/prctl.h> // set name
|
||||
#include <stdlib.h>
|
||||
#include <sched.h>
|
||||
#ifdef ANDROID
|
||||
#include <android/log.h>
|
||||
#else
|
||||
#define __android_log_print(a, ...)
|
||||
#endif
|
||||
#include <ucontext.h>
|
||||
// Ubuntu Dapper requires memory pages to be marked as
|
||||
// executable. Otherwise, OS raises an exception when executing code
|
||||
// in that page.
|
||||
#include <sys/types.h> // mmap & munmap
|
||||
#include <sys/mman.h> // mmap & munmap
|
||||
#include <sys/stat.h> // open
|
||||
#include <fcntl.h> // open
|
||||
#include <unistd.h> // sysconf
|
||||
#include <semaphore.h>
|
||||
#ifdef __GLIBC__
|
||||
#include <execinfo.h> // backtrace, backtrace_symbols
|
||||
#endif // def __GLIBC__
|
||||
#include <strings.h> // index
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include "prenv.h"
|
||||
#include "platform.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/LinuxSignal.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "GeckoSampler.h"
|
||||
#include "ThreadResponsiveness.h"
|
||||
|
||||
#if defined(__ARM_EABI__) && defined(ANDROID)
|
||||
// Should also work on other Android and ARM Linux, but not tested there yet.
|
||||
# define USE_EHABI_STACKWALK
|
||||
# include "EHABIStackWalk.h"
|
||||
#elif defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux)
|
||||
# define USE_LUL_STACKWALK
|
||||
# include "lul/LulMain.h"
|
||||
# include "lul/platform-linux-lul.h"
|
||||
#endif
|
||||
|
||||
// Memory profile
|
||||
#include "nsMemoryReporterManager.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <list>
|
||||
|
||||
#define SIGNAL_SAVE_PROFILE SIGUSR2
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
// A singleton instance of the library. It is initialised at first
|
||||
// use. Currently only the main thread can call Sampler::Start, so
|
||||
// there is no need for a mechanism to ensure that it is only
|
||||
// created once in a multi-thread-use situation.
|
||||
lul::LUL* sLUL = nullptr;
|
||||
|
||||
// This is the sLUL initialization routine.
|
||||
static void sLUL_initialization_routine(void)
|
||||
{
|
||||
MOZ_ASSERT(!sLUL);
|
||||
MOZ_ASSERT(gettid() == getpid()); /* "this is the main thread" */
|
||||
sLUL = new lul::LUL(logging_sink_for_LUL);
|
||||
// Read all the unwind info currently available.
|
||||
read_procmaps(sLUL);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* static */ Thread::tid_t
|
||||
Thread::GetCurrentId()
|
||||
{
|
||||
return gettid();
|
||||
}
|
||||
|
||||
#if !defined(ANDROID)
|
||||
// Keep track of when any of our threads calls fork(), so we can
|
||||
// temporarily disable signal delivery during the fork() call. Not
|
||||
// doing so appears to cause a kind of race, in which signals keep
|
||||
// getting delivered to the thread doing fork(), which keeps causing
|
||||
// it to fail and be restarted; hence forward progress is delayed a
|
||||
// great deal. A side effect of this is to permanently disable
|
||||
// sampling in the child process. See bug 837390.
|
||||
|
||||
// Unfortunately this is only doable on non-Android, since Bionic
|
||||
// doesn't have pthread_atfork.
|
||||
|
||||
// This records the current state at the time we paused it.
|
||||
static bool was_paused = false;
|
||||
|
||||
// In the parent, just before the fork, record the pausedness state,
|
||||
// and then pause.
|
||||
static void paf_prepare(void) {
|
||||
if (Sampler::GetActiveSampler()) {
|
||||
was_paused = Sampler::GetActiveSampler()->IsPaused();
|
||||
Sampler::GetActiveSampler()->SetPaused(true);
|
||||
} else {
|
||||
was_paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
// In the parent, just after the fork, return pausedness to the
|
||||
// pre-fork state.
|
||||
static void paf_parent(void) {
|
||||
if (Sampler::GetActiveSampler())
|
||||
Sampler::GetActiveSampler()->SetPaused(was_paused);
|
||||
}
|
||||
|
||||
// Set up the fork handlers.
|
||||
static void* setup_atfork() {
|
||||
pthread_atfork(paf_prepare, paf_parent, NULL);
|
||||
return NULL;
|
||||
}
|
||||
#endif /* !defined(ANDROID) */
|
||||
|
||||
struct SamplerRegistry {
|
||||
static void AddActiveSampler(Sampler *sampler) {
|
||||
ASSERT(!SamplerRegistry::sampler);
|
||||
SamplerRegistry::sampler = sampler;
|
||||
}
|
||||
static void RemoveActiveSampler(Sampler *sampler) {
|
||||
SamplerRegistry::sampler = NULL;
|
||||
}
|
||||
static Sampler *sampler;
|
||||
};
|
||||
|
||||
Sampler *SamplerRegistry::sampler = NULL;
|
||||
|
||||
static mozilla::Atomic<ThreadProfile*> sCurrentThreadProfile;
|
||||
static sem_t sSignalHandlingDone;
|
||||
|
||||
static void ProfilerSaveSignalHandler(int signal, siginfo_t* info, void* context) {
|
||||
Sampler::GetActiveSampler()->RequestSave();
|
||||
}
|
||||
|
||||
static void SetSampleContext(TickSample* sample, void* context)
|
||||
{
|
||||
// Extracting the sample from the context is extremely machine dependent.
|
||||
ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context);
|
||||
mcontext_t& mcontext = ucontext->uc_mcontext;
|
||||
#if V8_HOST_ARCH_IA32
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_EIP]);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_ESP]);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_EBP]);
|
||||
#elif V8_HOST_ARCH_X64
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_RIP]);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_RSP]);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_RBP]);
|
||||
#elif V8_HOST_ARCH_ARM
|
||||
// An undefined macro evaluates to 0, so this applies to Android's Bionic also.
|
||||
#if !defined(ANDROID) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 3))
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.gregs[R15]);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.gregs[R13]);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.gregs[R11]);
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
sample->lr = reinterpret_cast<Address>(mcontext.gregs[R14]);
|
||||
#endif
|
||||
#else
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.arm_pc);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.arm_sp);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.arm_fp);
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
sample->lr = reinterpret_cast<Address>(mcontext.arm_lr);
|
||||
#endif
|
||||
#endif
|
||||
#elif V8_HOST_ARCH_MIPS
|
||||
// Implement this on MIPS.
|
||||
UNIMPLEMENTED();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
#define V8_HOST_ARCH_ARM 1
|
||||
#define SYS_gettid __NR_gettid
|
||||
#define SYS_tgkill __NR_tgkill
|
||||
#else
|
||||
#define V8_HOST_ARCH_X64 1
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) {
|
||||
// Avoid TSan warning about clobbering errno.
|
||||
int savedErrno = errno;
|
||||
|
||||
if (!Sampler::GetActiveSampler()) {
|
||||
sem_post(&sSignalHandlingDone);
|
||||
errno = savedErrno;
|
||||
return;
|
||||
}
|
||||
|
||||
TickSample sample_obj;
|
||||
TickSample* sample = &sample_obj;
|
||||
sample->context = context;
|
||||
|
||||
// If profiling, we extract the current pc and sp.
|
||||
if (Sampler::GetActiveSampler()->IsProfiling()) {
|
||||
SetSampleContext(sample, context);
|
||||
}
|
||||
sample->threadProfile = sCurrentThreadProfile;
|
||||
sample->timestamp = mozilla::TimeStamp::Now();
|
||||
sample->rssMemory = sample->threadProfile->mRssMemory;
|
||||
sample->ussMemory = sample->threadProfile->mUssMemory;
|
||||
|
||||
Sampler::GetActiveSampler()->Tick(sample);
|
||||
|
||||
sCurrentThreadProfile = NULL;
|
||||
sem_post(&sSignalHandlingDone);
|
||||
errno = savedErrno;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static void ProfilerSignalThread(ThreadProfile *profile,
|
||||
bool isFirstProfiledThread)
|
||||
{
|
||||
if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) {
|
||||
profile->mRssMemory = nsMemoryReporterManager::ResidentFast();
|
||||
profile->mUssMemory = nsMemoryReporterManager::ResidentUnique();
|
||||
} else {
|
||||
profile->mRssMemory = 0;
|
||||
profile->mUssMemory = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int tgkill(pid_t tgid, pid_t tid, int signalno) {
|
||||
return syscall(SYS_tgkill, tgid, tid, signalno);
|
||||
}
|
||||
|
||||
class PlatformData {
|
||||
public:
|
||||
PlatformData()
|
||||
{
|
||||
MOZ_COUNT_CTOR(PlatformData);
|
||||
}
|
||||
|
||||
~PlatformData()
|
||||
{
|
||||
MOZ_COUNT_DTOR(PlatformData);
|
||||
}
|
||||
};
|
||||
|
||||
/* static */ PlatformData*
|
||||
Sampler::AllocPlatformData(int aThreadId)
|
||||
{
|
||||
return new PlatformData;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Sampler::FreePlatformData(PlatformData* aData)
|
||||
{
|
||||
delete aData;
|
||||
}
|
||||
|
||||
static void* SignalSender(void* arg) {
|
||||
// Taken from platform_thread_posix.cc
|
||||
prctl(PR_SET_NAME, "SamplerThread", 0, 0, 0);
|
||||
|
||||
int vm_tgid_ = getpid();
|
||||
DebugOnly<int> my_tid = gettid();
|
||||
|
||||
unsigned int nSignalsSent = 0;
|
||||
|
||||
TimeDuration lastSleepOverhead = 0;
|
||||
TimeStamp sampleStart = TimeStamp::Now();
|
||||
while (SamplerRegistry::sampler->IsActive()) {
|
||||
|
||||
SamplerRegistry::sampler->HandleSaveRequest();
|
||||
SamplerRegistry::sampler->DeleteExpiredMarkers();
|
||||
|
||||
if (!SamplerRegistry::sampler->IsPaused()) {
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
std::vector<ThreadInfo*> threads =
|
||||
SamplerRegistry::sampler->GetRegisteredThreads();
|
||||
|
||||
bool isFirstProfiledThread = true;
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
ThreadInfo* info = threads[i];
|
||||
|
||||
// This will be null if we're not interested in profiling this thread.
|
||||
if (!info->Profile() || info->IsPendingDelete())
|
||||
continue;
|
||||
|
||||
PseudoStack::SleepState sleeping = info->Stack()->observeSleeping();
|
||||
if (sleeping == PseudoStack::SLEEPING_AGAIN) {
|
||||
info->Profile()->DuplicateLastSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
info->Profile()->GetThreadResponsiveness()->Update();
|
||||
|
||||
// We use sCurrentThreadProfile the ThreadProfile for the
|
||||
// thread we're profiling to the signal handler
|
||||
sCurrentThreadProfile = info->Profile();
|
||||
|
||||
int threadId = info->ThreadId();
|
||||
MOZ_ASSERT(threadId != my_tid);
|
||||
|
||||
// Profile from the signal sender for information which is not signal
|
||||
// safe, and will have low variation between the emission of the signal
|
||||
// and the signal handler catch.
|
||||
ProfilerSignalThread(sCurrentThreadProfile, isFirstProfiledThread);
|
||||
|
||||
// Profile from the signal handler for information which is signal safe
|
||||
// and needs to be precise too, such as the stack of the interrupted
|
||||
// thread.
|
||||
if (tgkill(vm_tgid_, threadId, SIGPROF) != 0) {
|
||||
printf_stderr("profiler failed to signal tid=%d\n", threadId);
|
||||
#ifdef DEBUG
|
||||
abort();
|
||||
#else
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Wait for the signal handler to run before moving on to the next one
|
||||
sem_wait(&sSignalHandlingDone);
|
||||
isFirstProfiledThread = false;
|
||||
|
||||
// The LUL unwind object accumulates frame statistics.
|
||||
// Periodically we should poke it to give it a chance to print
|
||||
// those statistics. This involves doing I/O (fprintf,
|
||||
// __android_log_print, etc) and so can't safely be done from
|
||||
// the unwinder threads, which is why it is done here.
|
||||
if ((++nSignalsSent & 0xF) == 0) {
|
||||
# if defined(USE_LUL_STACKWALK)
|
||||
sLUL->MaybeShowStats();
|
||||
# endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeStamp targetSleepEndTime = sampleStart + TimeDuration::FromMicroseconds(SamplerRegistry::sampler->interval() * 1000);
|
||||
TimeStamp beforeSleep = TimeStamp::Now();
|
||||
TimeDuration targetSleepDuration = targetSleepEndTime - beforeSleep;
|
||||
double sleepTime = std::max(0.0, (targetSleepDuration - lastSleepOverhead).ToMicroseconds());
|
||||
OS::SleepMicro(sleepTime);
|
||||
sampleStart = TimeStamp::Now();
|
||||
lastSleepOverhead = sampleStart - (beforeSleep + TimeDuration::FromMicroseconds(sleepTime));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Sampler::Sampler(double interval, bool profiling, int entrySize)
|
||||
: interval_(interval),
|
||||
profiling_(profiling),
|
||||
paused_(false),
|
||||
active_(false),
|
||||
entrySize_(entrySize) {
|
||||
MOZ_COUNT_CTOR(Sampler);
|
||||
}
|
||||
|
||||
Sampler::~Sampler() {
|
||||
MOZ_COUNT_DTOR(Sampler);
|
||||
ASSERT(!signal_sender_launched_);
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Start() {
|
||||
LOG("Sampler started");
|
||||
|
||||
#if defined(USE_EHABI_STACKWALK)
|
||||
mozilla::EHABIStackWalkInit();
|
||||
#elif defined(USE_LUL_STACKWALK)
|
||||
// NOTE: this isn't thread-safe. But we expect Sampler::Start to be
|
||||
// called only from the main thread, so this is OK in general.
|
||||
if (!sLUL) {
|
||||
sLUL_initialization_routine();
|
||||
}
|
||||
#endif
|
||||
|
||||
SamplerRegistry::AddActiveSampler(this);
|
||||
|
||||
// Initialize signal handler communication
|
||||
sCurrentThreadProfile = NULL;
|
||||
if (sem_init(&sSignalHandlingDone, /* pshared: */ 0, /* value: */ 0) != 0) {
|
||||
LOG("Error initializing semaphore");
|
||||
return;
|
||||
}
|
||||
|
||||
// Request profiling signals.
|
||||
LOG("Request signal");
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = MOZ_SIGNAL_TRAMPOLINE(ProfilerSignalHandler);
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
if (sigaction(SIGPROF, &sa, &old_sigprof_signal_handler_) != 0) {
|
||||
LOG("Error installing signal");
|
||||
return;
|
||||
}
|
||||
|
||||
// Request save profile signals
|
||||
struct sigaction sa2;
|
||||
sa2.sa_sigaction = ProfilerSaveSignalHandler;
|
||||
sigemptyset(&sa2.sa_mask);
|
||||
sa2.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
if (sigaction(SIGNAL_SAVE_PROFILE, &sa2, &old_sigsave_signal_handler_) != 0) {
|
||||
LOG("Error installing start signal");
|
||||
return;
|
||||
}
|
||||
LOG("Signal installed");
|
||||
signal_handler_installed_ = true;
|
||||
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
// Switch into unwind mode. After this point, we can't add or
|
||||
// remove any unwind info to/from this LUL instance. The only thing
|
||||
// we can do with it is Unwind() calls.
|
||||
sLUL->EnableUnwinding();
|
||||
|
||||
// Has a test been requested?
|
||||
if (PR_GetEnv("MOZ_PROFILER_LUL_TEST")) {
|
||||
int nTests = 0, nTestsPassed = 0;
|
||||
RunLulUnitTests(&nTests, &nTestsPassed, sLUL);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start a thread that sends SIGPROF signal to VM thread.
|
||||
// Sending the signal ourselves instead of relying on itimer provides
|
||||
// much better accuracy.
|
||||
SetActive(true);
|
||||
if (pthread_create(
|
||||
&signal_sender_thread_, NULL, SignalSender, NULL) == 0) {
|
||||
signal_sender_launched_ = true;
|
||||
}
|
||||
LOG("Profiler thread started");
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Stop() {
|
||||
SetActive(false);
|
||||
|
||||
// Wait for signal sender termination (it will exit after setting
|
||||
// active_ to false).
|
||||
if (signal_sender_launched_) {
|
||||
pthread_join(signal_sender_thread_, NULL);
|
||||
signal_sender_launched_ = false;
|
||||
}
|
||||
|
||||
SamplerRegistry::RemoveActiveSampler(this);
|
||||
|
||||
// Restore old signal handler
|
||||
if (signal_handler_installed_) {
|
||||
sigaction(SIGNAL_SAVE_PROFILE, &old_sigsave_signal_handler_, 0);
|
||||
sigaction(SIGPROF, &old_sigprof_signal_handler_, 0);
|
||||
signal_handler_installed_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Sampler::RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop)
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return false;
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
// Thread already registered. This means the first unregister will be
|
||||
// too early.
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_tls_stack_top(stackTop);
|
||||
|
||||
ThreadInfo* info = new StackOwningThreadInfo(aName, id,
|
||||
aIsMainThread, aPseudoStack, stackTop);
|
||||
|
||||
if (sActiveSampler) {
|
||||
sActiveSampler->RegisterThread(info);
|
||||
}
|
||||
|
||||
sRegisteredThreads->push_back(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sampler::UnregisterCurrentThread()
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return;
|
||||
|
||||
tlsStackTop.set(nullptr);
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
if (profiler_is_active()) {
|
||||
// We still want to show the results of this thread if you
|
||||
// save the profile shortly after a thread is terminated.
|
||||
// For now we will defer the delete to profile stop.
|
||||
info->SetPendingDelete();
|
||||
break;
|
||||
} else {
|
||||
delete info;
|
||||
sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
static struct sigaction old_sigstart_signal_handler;
|
||||
const int SIGSTART = SIGUSR2;
|
||||
|
||||
static void freeArray(const char** array, int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
free((void*) array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t readCSVArray(char* csvList, const char** buffer) {
|
||||
uint32_t count;
|
||||
char* savePtr;
|
||||
int newlinePos = strlen(csvList) - 1;
|
||||
if (csvList[newlinePos] == '\n') {
|
||||
csvList[newlinePos] = '\0';
|
||||
}
|
||||
|
||||
char* item = strtok_r(csvList, ",", &savePtr);
|
||||
for (count = 0; item; item = strtok_r(NULL, ",", &savePtr)) {
|
||||
int length = strlen(item) + 1; // Include \0
|
||||
char* newBuf = (char*) malloc(sizeof(char) * length);
|
||||
buffer[count] = newBuf;
|
||||
strncpy(newBuf, item, length);
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Currently support only the env variables
|
||||
// reported in read_profiler_env
|
||||
static void ReadProfilerVars(const char* fileName, const char** features,
|
||||
uint32_t* featureCount, const char** threadNames, uint32_t* threadCount) {
|
||||
FILE* file = fopen(fileName, "r");
|
||||
const int bufferSize = 1024;
|
||||
char line[bufferSize];
|
||||
char* feature;
|
||||
char* value;
|
||||
char* savePtr;
|
||||
|
||||
if (file) {
|
||||
while (fgets(line, bufferSize, file) != NULL) {
|
||||
feature = strtok_r(line, "=", &savePtr);
|
||||
value = strtok_r(NULL, "", &savePtr);
|
||||
|
||||
if (strncmp(feature, PROFILER_INTERVAL, bufferSize) == 0) {
|
||||
set_profiler_interval(value);
|
||||
} else if (strncmp(feature, PROFILER_ENTRIES, bufferSize) == 0) {
|
||||
set_profiler_entries(value);
|
||||
} else if (strncmp(feature, PROFILER_STACK, bufferSize) == 0) {
|
||||
set_profiler_scan(value);
|
||||
} else if (strncmp(feature, PROFILER_FEATURES, bufferSize) == 0) {
|
||||
*featureCount = readCSVArray(value, features);
|
||||
} else if (strncmp(feature, "threads", bufferSize) == 0) {
|
||||
*threadCount = readCSVArray(value, threadNames);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
static void DoStartTask() {
|
||||
uint32_t featureCount = 0;
|
||||
uint32_t threadCount = 0;
|
||||
|
||||
// Just allocate 10 features for now
|
||||
// FIXME: these don't really point to const chars*
|
||||
// So we free them later, but we don't want to change the const char**
|
||||
// declaration in profiler_start. Annoying but ok for now.
|
||||
const char* threadNames[10];
|
||||
const char* features[10];
|
||||
const char* profilerConfigFile = "/data/local/tmp/profiler.options";
|
||||
|
||||
ReadProfilerVars(profilerConfigFile, features, &featureCount, threadNames, &threadCount);
|
||||
MOZ_ASSERT(featureCount < 10);
|
||||
MOZ_ASSERT(threadCount < 10);
|
||||
|
||||
profiler_start(PROFILE_DEFAULT_ENTRY, 1,
|
||||
features, featureCount,
|
||||
threadNames, threadCount);
|
||||
|
||||
freeArray(threadNames, threadCount);
|
||||
freeArray(features, featureCount);
|
||||
}
|
||||
|
||||
static void StartSignalHandler(int signal, siginfo_t* info, void* context) {
|
||||
class StartTask : public Runnable {
|
||||
public:
|
||||
NS_IMETHOD Run() override {
|
||||
DoStartTask();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
// XXX: technically NS_DispatchToMainThread is NOT async signal safe. We risk
|
||||
// nasty things like deadlocks, but the probability is very low and we
|
||||
// typically only do this once so it tends to be ok. See bug 909403.
|
||||
NS_DispatchToMainThread(new StartTask());
|
||||
}
|
||||
|
||||
void OS::Startup()
|
||||
{
|
||||
LOG("Registering start signal");
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = StartSignalHandler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
if (sigaction(SIGSTART, &sa, &old_sigstart_signal_handler) != 0) {
|
||||
LOG("Error installing signal");
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void OS::Startup() {
|
||||
// Set up the fork handlers.
|
||||
setup_atfork();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
void TickSample::PopulateContext(void* aContext)
|
||||
{
|
||||
MOZ_ASSERT(aContext);
|
||||
ucontext_t* pContext = reinterpret_cast<ucontext_t*>(aContext);
|
||||
if (!getcontext(pContext)) {
|
||||
context = pContext;
|
||||
SetSampleContext(this, aContext);
|
||||
}
|
||||
}
|
||||
|
||||
void OS::SleepMicro(int microseconds)
|
||||
{
|
||||
if (MOZ_UNLIKELY(microseconds >= 1000000)) {
|
||||
// Use usleep for larger intervals, because the nanosleep
|
||||
// code below only supports intervals < 1 second.
|
||||
MOZ_ALWAYS_TRUE(!::usleep(microseconds));
|
||||
return;
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = microseconds * 1000UL;
|
||||
|
||||
int rv = ::nanosleep(&ts, &ts);
|
||||
|
||||
while (rv != 0 && errno == EINTR) {
|
||||
// Keep waiting in case of interrupt.
|
||||
// nanosleep puts the remaining time back into ts.
|
||||
rv = ::nanosleep(&ts, &ts);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!rv, "nanosleep call failed");
|
||||
}
|
||||
|
|
@ -1,469 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/getsect.h>
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <libkern/OSAtomic.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/semaphore.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/vm_statistics.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "ThreadResponsiveness.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
// Memory profile
|
||||
#include "nsMemoryReporterManager.h"
|
||||
#endif
|
||||
|
||||
#include "platform.h"
|
||||
#include "GeckoSampler.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
using mozilla::TimeStamp;
|
||||
using mozilla::TimeDuration;
|
||||
|
||||
// this port is based off of v8 svn revision 9837
|
||||
|
||||
// XXX: this is a very stubbed out implementation
|
||||
// that only supports a single Sampler
|
||||
struct SamplerRegistry {
|
||||
static void AddActiveSampler(Sampler *sampler) {
|
||||
ASSERT(!SamplerRegistry::sampler);
|
||||
SamplerRegistry::sampler = sampler;
|
||||
}
|
||||
static void RemoveActiveSampler(Sampler *sampler) {
|
||||
SamplerRegistry::sampler = NULL;
|
||||
}
|
||||
static Sampler *sampler;
|
||||
};
|
||||
|
||||
Sampler *SamplerRegistry::sampler = NULL;
|
||||
|
||||
#ifdef DEBUG
|
||||
// 0 is never a valid thread id on MacOSX since a pthread_t is a pointer.
|
||||
static const pthread_t kNoThread = (pthread_t) 0;
|
||||
#endif
|
||||
|
||||
void OS::Startup() {
|
||||
}
|
||||
|
||||
void OS::Sleep(int milliseconds) {
|
||||
usleep(1000 * milliseconds);
|
||||
}
|
||||
|
||||
void OS::SleepMicro(int microseconds) {
|
||||
usleep(microseconds);
|
||||
}
|
||||
|
||||
Thread::Thread(const char* name)
|
||||
: stack_size_(0) {
|
||||
set_name(name);
|
||||
}
|
||||
|
||||
|
||||
Thread::~Thread() {
|
||||
}
|
||||
|
||||
|
||||
static void SetThreadName(const char* name) {
|
||||
// pthread_setname_np is only available in 10.6 or later, so test
|
||||
// for it at runtime.
|
||||
int (*dynamic_pthread_setname_np)(const char*);
|
||||
*reinterpret_cast<void**>(&dynamic_pthread_setname_np) =
|
||||
dlsym(RTLD_DEFAULT, "pthread_setname_np");
|
||||
if (!dynamic_pthread_setname_np)
|
||||
return;
|
||||
|
||||
// Mac OS X does not expose the length limit of the name, so hardcode it.
|
||||
static const int kMaxNameLength = 63;
|
||||
USE(kMaxNameLength);
|
||||
ASSERT(Thread::kMaxThreadNameLength <= kMaxNameLength);
|
||||
dynamic_pthread_setname_np(name);
|
||||
}
|
||||
|
||||
|
||||
static void* ThreadEntry(void* arg) {
|
||||
Thread* thread = reinterpret_cast<Thread*>(arg);
|
||||
|
||||
thread->thread_ = pthread_self();
|
||||
SetThreadName(thread->name());
|
||||
ASSERT(thread->thread_ != kNoThread);
|
||||
thread->Run();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void Thread::set_name(const char* name) {
|
||||
strncpy(name_, name, sizeof(name_));
|
||||
name_[sizeof(name_) - 1] = '\0';
|
||||
}
|
||||
|
||||
|
||||
void Thread::Start() {
|
||||
pthread_attr_t* attr_ptr = NULL;
|
||||
pthread_attr_t attr;
|
||||
if (stack_size_ > 0) {
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setstacksize(&attr, static_cast<size_t>(stack_size_));
|
||||
attr_ptr = &attr;
|
||||
}
|
||||
pthread_create(&thread_, attr_ptr, ThreadEntry, this);
|
||||
ASSERT(thread_ != kNoThread);
|
||||
}
|
||||
|
||||
void Thread::Join() {
|
||||
pthread_join(thread_, NULL);
|
||||
}
|
||||
|
||||
class PlatformData {
|
||||
public:
|
||||
PlatformData() : profiled_thread_(mach_thread_self())
|
||||
{
|
||||
profiled_pthread_ = pthread_from_mach_thread_np(profiled_thread_);
|
||||
}
|
||||
|
||||
~PlatformData() {
|
||||
// Deallocate Mach port for thread.
|
||||
mach_port_deallocate(mach_task_self(), profiled_thread_);
|
||||
}
|
||||
|
||||
thread_act_t profiled_thread() { return profiled_thread_; }
|
||||
pthread_t profiled_pthread() { return profiled_pthread_; }
|
||||
|
||||
private:
|
||||
// Note: for profiled_thread_ Mach primitives are used instead of PThread's
|
||||
// because the latter doesn't provide thread manipulation primitives required.
|
||||
// For details, consult "Mac OS X Internals" book, Section 7.3.
|
||||
thread_act_t profiled_thread_;
|
||||
// we also store the pthread because Mach threads have no concept of stack
|
||||
// and we want to be able to get the stack size when we need to unwind the
|
||||
// stack using frame pointers.
|
||||
pthread_t profiled_pthread_;
|
||||
};
|
||||
|
||||
/* static */ PlatformData*
|
||||
Sampler::AllocPlatformData(int aThreadId)
|
||||
{
|
||||
return new PlatformData;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Sampler::FreePlatformData(PlatformData* aData)
|
||||
{
|
||||
delete aData;
|
||||
}
|
||||
|
||||
class SamplerThread : public Thread {
|
||||
public:
|
||||
explicit SamplerThread(double interval)
|
||||
: Thread("SamplerThread")
|
||||
, intervalMicro_(floor(interval * 1000 + 0.5))
|
||||
{
|
||||
if (intervalMicro_ <= 0) {
|
||||
intervalMicro_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void AddActiveSampler(Sampler* sampler) {
|
||||
SamplerRegistry::AddActiveSampler(sampler);
|
||||
if (instance_ == NULL) {
|
||||
instance_ = new SamplerThread(sampler->interval());
|
||||
instance_->Start();
|
||||
}
|
||||
}
|
||||
|
||||
static void RemoveActiveSampler(Sampler* sampler) {
|
||||
instance_->Join();
|
||||
//XXX: unlike v8 we need to remove the active sampler after doing the Join
|
||||
// because we drop the sampler immediately
|
||||
SamplerRegistry::RemoveActiveSampler(sampler);
|
||||
delete instance_;
|
||||
instance_ = NULL;
|
||||
}
|
||||
|
||||
// Implement Thread::Run().
|
||||
virtual void Run() {
|
||||
TimeDuration lastSleepOverhead = 0;
|
||||
TimeStamp sampleStart = TimeStamp::Now();
|
||||
while (SamplerRegistry::sampler->IsActive()) {
|
||||
SamplerRegistry::sampler->DeleteExpiredMarkers();
|
||||
if (!SamplerRegistry::sampler->IsPaused()) {
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
std::vector<ThreadInfo*> threads =
|
||||
SamplerRegistry::sampler->GetRegisteredThreads();
|
||||
bool isFirstProfiledThread = true;
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
ThreadInfo* info = threads[i];
|
||||
|
||||
// This will be null if we're not interested in profiling this thread.
|
||||
if (!info->Profile() || info->IsPendingDelete())
|
||||
continue;
|
||||
|
||||
PseudoStack::SleepState sleeping = info->Stack()->observeSleeping();
|
||||
if (sleeping == PseudoStack::SLEEPING_AGAIN) {
|
||||
info->Profile()->DuplicateLastSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
info->Profile()->GetThreadResponsiveness()->Update();
|
||||
#endif
|
||||
|
||||
ThreadProfile* thread_profile = info->Profile();
|
||||
|
||||
SampleContext(SamplerRegistry::sampler, thread_profile,
|
||||
isFirstProfiledThread);
|
||||
isFirstProfiledThread = false;
|
||||
}
|
||||
}
|
||||
|
||||
TimeStamp targetSleepEndTime = sampleStart + TimeDuration::FromMicroseconds(intervalMicro_);
|
||||
TimeStamp beforeSleep = TimeStamp::Now();
|
||||
TimeDuration targetSleepDuration = targetSleepEndTime - beforeSleep;
|
||||
double sleepTime = std::max(0.0, (targetSleepDuration - lastSleepOverhead).ToMicroseconds());
|
||||
OS::SleepMicro(sleepTime);
|
||||
sampleStart = TimeStamp::Now();
|
||||
lastSleepOverhead = sampleStart - (beforeSleep + TimeDuration::FromMicroseconds(sleepTime));
|
||||
}
|
||||
}
|
||||
|
||||
void SampleContext(Sampler* sampler, ThreadProfile* thread_profile,
|
||||
bool isFirstProfiledThread)
|
||||
{
|
||||
thread_act_t profiled_thread =
|
||||
thread_profile->GetPlatformData()->profiled_thread();
|
||||
|
||||
TickSample sample_obj;
|
||||
TickSample* sample = &sample_obj;
|
||||
|
||||
// Unique Set Size is not supported on Mac.
|
||||
sample->ussMemory = 0;
|
||||
sample->rssMemory = 0;
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) {
|
||||
sample->rssMemory = nsMemoryReporterManager::ResidentFast();
|
||||
}
|
||||
#endif
|
||||
|
||||
// We're using thread_suspend on OS X because pthread_kill (which is what
|
||||
// we're using on Linux) has less consistent performance and causes
|
||||
// strange crashes, see bug 1166778 and bug 1166808.
|
||||
|
||||
if (KERN_SUCCESS != thread_suspend(profiled_thread)) return;
|
||||
|
||||
#if V8_HOST_ARCH_X64
|
||||
thread_state_flavor_t flavor = x86_THREAD_STATE64;
|
||||
x86_thread_state64_t state;
|
||||
mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT;
|
||||
#if __DARWIN_UNIX03
|
||||
#define REGISTER_FIELD(name) __r ## name
|
||||
#else
|
||||
#define REGISTER_FIELD(name) r ## name
|
||||
#endif // __DARWIN_UNIX03
|
||||
#elif V8_HOST_ARCH_IA32
|
||||
thread_state_flavor_t flavor = i386_THREAD_STATE;
|
||||
i386_thread_state_t state;
|
||||
mach_msg_type_number_t count = i386_THREAD_STATE_COUNT;
|
||||
#if __DARWIN_UNIX03
|
||||
#define REGISTER_FIELD(name) __e ## name
|
||||
#else
|
||||
#define REGISTER_FIELD(name) e ## name
|
||||
#endif // __DARWIN_UNIX03
|
||||
#else
|
||||
#error Unsupported Mac OS X host architecture.
|
||||
#endif // V8_HOST_ARCH
|
||||
|
||||
if (thread_get_state(profiled_thread,
|
||||
flavor,
|
||||
reinterpret_cast<natural_t*>(&state),
|
||||
&count) == KERN_SUCCESS) {
|
||||
sample->pc = reinterpret_cast<Address>(state.REGISTER_FIELD(ip));
|
||||
sample->sp = reinterpret_cast<Address>(state.REGISTER_FIELD(sp));
|
||||
sample->fp = reinterpret_cast<Address>(state.REGISTER_FIELD(bp));
|
||||
sample->timestamp = mozilla::TimeStamp::Now();
|
||||
sample->threadProfile = thread_profile;
|
||||
|
||||
sampler->Tick(sample);
|
||||
}
|
||||
thread_resume(profiled_thread);
|
||||
}
|
||||
|
||||
int intervalMicro_;
|
||||
//RuntimeProfilerRateLimiter rate_limiter_;
|
||||
|
||||
static SamplerThread* instance_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SamplerThread);
|
||||
};
|
||||
|
||||
#undef REGISTER_FIELD
|
||||
|
||||
SamplerThread* SamplerThread::instance_ = NULL;
|
||||
|
||||
Sampler::Sampler(double interval, bool profiling, int entrySize)
|
||||
: // isolate_(isolate),
|
||||
interval_(interval),
|
||||
profiling_(profiling),
|
||||
paused_(false),
|
||||
active_(false),
|
||||
entrySize_(entrySize) /*,
|
||||
samples_taken_(0)*/ {
|
||||
}
|
||||
|
||||
|
||||
Sampler::~Sampler() {
|
||||
ASSERT(!IsActive());
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Start() {
|
||||
ASSERT(!IsActive());
|
||||
SetActive(true);
|
||||
SamplerThread::AddActiveSampler(this);
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Stop() {
|
||||
ASSERT(IsActive());
|
||||
SetActive(false);
|
||||
SamplerThread::RemoveActiveSampler(this);
|
||||
}
|
||||
|
||||
pthread_t
|
||||
Sampler::GetProfiledThread(PlatformData* aData)
|
||||
{
|
||||
return aData->profiled_pthread();
|
||||
}
|
||||
|
||||
#include <sys/syscall.h>
|
||||
pid_t gettid()
|
||||
{
|
||||
return (pid_t) syscall(SYS_thread_selfid);
|
||||
}
|
||||
|
||||
/* static */ Thread::tid_t
|
||||
Thread::GetCurrentId()
|
||||
{
|
||||
return gettid();
|
||||
}
|
||||
|
||||
bool Sampler::RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop)
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return false;
|
||||
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
// Thread already registered. This means the first unregister will be
|
||||
// too early.
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_tls_stack_top(stackTop);
|
||||
|
||||
ThreadInfo* info = new StackOwningThreadInfo(aName, id,
|
||||
aIsMainThread, aPseudoStack, stackTop);
|
||||
|
||||
if (sActiveSampler) {
|
||||
sActiveSampler->RegisterThread(info);
|
||||
}
|
||||
|
||||
sRegisteredThreads->push_back(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sampler::UnregisterCurrentThread()
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return;
|
||||
|
||||
tlsStackTop.set(nullptr);
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
if (profiler_is_active()) {
|
||||
// We still want to show the results of this thread if you
|
||||
// save the profile shortly after a thread is terminated.
|
||||
// For now we will defer the delete to profile stop.
|
||||
info->SetPendingDelete();
|
||||
break;
|
||||
} else {
|
||||
delete info;
|
||||
sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TickSample::PopulateContext(void* aContext)
|
||||
{
|
||||
// Note that this asm changes if PopulateContext's parameter list is altered
|
||||
#if defined(SPS_PLAT_amd64_darwin)
|
||||
asm (
|
||||
// Compute caller's %rsp by adding to %rbp:
|
||||
// 8 bytes for previous %rbp, 8 bytes for return address
|
||||
"leaq 0x10(%%rbp), %0\n\t"
|
||||
// Dereference %rbp to get previous %rbp
|
||||
"movq (%%rbp), %1\n\t"
|
||||
:
|
||||
"=r"(sp),
|
||||
"=r"(fp)
|
||||
);
|
||||
#elif defined(SPS_PLAT_x86_darwin)
|
||||
asm (
|
||||
// Compute caller's %esp by adding to %ebp:
|
||||
// 4 bytes for aContext + 4 bytes for return address +
|
||||
// 4 bytes for previous %ebp
|
||||
"leal 0xc(%%ebp), %0\n\t"
|
||||
// Dereference %ebp to get previous %ebp
|
||||
"movl (%%ebp), %1\n\t"
|
||||
:
|
||||
"=r"(sp),
|
||||
"=r"(fp)
|
||||
);
|
||||
#else
|
||||
# error "Unsupported architecture"
|
||||
#endif
|
||||
pc = reinterpret_cast<Address>(__builtin_extract_return_addr(
|
||||
__builtin_return_address(0)));
|
||||
}
|
||||
|
||||
|
|
@ -1,431 +0,0 @@
|
|||
// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google, Inc. nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <process.h>
|
||||
#include "platform.h"
|
||||
#include "GeckoSampler.h"
|
||||
#include "ThreadResponsiveness.h"
|
||||
#include "ProfileEntry.h"
|
||||
|
||||
// Memory profile
|
||||
#include "nsMemoryReporterManager.h"
|
||||
|
||||
#include "mozilla/StackWalk_windows.h"
|
||||
|
||||
|
||||
class PlatformData {
|
||||
public:
|
||||
// Get a handle to the calling thread. This is the thread that we are
|
||||
// going to profile. We need to make a copy of the handle because we are
|
||||
// going to use it in the sampler thread. Using GetThreadHandle() will
|
||||
// not work in this case. We're using OpenThread because DuplicateHandle
|
||||
// for some reason doesn't work in Chrome's sandbox.
|
||||
PlatformData(int aThreadId) : profiled_thread_(OpenThread(THREAD_GET_CONTEXT |
|
||||
THREAD_SUSPEND_RESUME |
|
||||
THREAD_QUERY_INFORMATION,
|
||||
false,
|
||||
aThreadId)) {}
|
||||
|
||||
~PlatformData() {
|
||||
if (profiled_thread_ != NULL) {
|
||||
CloseHandle(profiled_thread_);
|
||||
profiled_thread_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE profiled_thread() { return profiled_thread_; }
|
||||
|
||||
private:
|
||||
HANDLE profiled_thread_;
|
||||
};
|
||||
|
||||
/* static */ PlatformData*
|
||||
Sampler::AllocPlatformData(int aThreadId)
|
||||
{
|
||||
return new PlatformData(aThreadId);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Sampler::FreePlatformData(PlatformData* aData)
|
||||
{
|
||||
delete aData;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
Sampler::GetThreadHandle(PlatformData* aData)
|
||||
{
|
||||
return (uintptr_t) aData->profiled_thread();
|
||||
}
|
||||
|
||||
class SamplerThread : public Thread {
|
||||
public:
|
||||
SamplerThread(double interval, Sampler* sampler)
|
||||
: Thread("SamplerThread")
|
||||
, sampler_(sampler)
|
||||
, interval_(interval)
|
||||
{
|
||||
interval_ = floor(interval + 0.5);
|
||||
if (interval_ <= 0) {
|
||||
interval_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void StartSampler(Sampler* sampler) {
|
||||
if (instance_ == NULL) {
|
||||
instance_ = new SamplerThread(sampler->interval(), sampler);
|
||||
instance_->Start();
|
||||
} else {
|
||||
ASSERT(instance_->interval_ == sampler->interval());
|
||||
}
|
||||
}
|
||||
|
||||
static void StopSampler() {
|
||||
instance_->Join();
|
||||
delete instance_;
|
||||
instance_ = NULL;
|
||||
}
|
||||
|
||||
// Implement Thread::Run().
|
||||
virtual void Run() {
|
||||
|
||||
// By default we'll not adjust the timer resolution which tends to be around
|
||||
// 16ms. However, if the requested interval is sufficiently low we'll try to
|
||||
// adjust the resolution to match.
|
||||
if (interval_ < 10)
|
||||
::timeBeginPeriod(interval_);
|
||||
|
||||
while (sampler_->IsActive()) {
|
||||
sampler_->DeleteExpiredMarkers();
|
||||
|
||||
if (!sampler_->IsPaused()) {
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
std::vector<ThreadInfo*> threads =
|
||||
sampler_->GetRegisteredThreads();
|
||||
bool isFirstProfiledThread = true;
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
ThreadInfo* info = threads[i];
|
||||
|
||||
// This will be null if we're not interested in profiling this thread.
|
||||
if (!info->Profile() || info->IsPendingDelete())
|
||||
continue;
|
||||
|
||||
PseudoStack::SleepState sleeping = info->Stack()->observeSleeping();
|
||||
if (sleeping == PseudoStack::SLEEPING_AGAIN) {
|
||||
info->Profile()->DuplicateLastSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
info->Profile()->GetThreadResponsiveness()->Update();
|
||||
|
||||
ThreadProfile* thread_profile = info->Profile();
|
||||
|
||||
SampleContext(sampler_, thread_profile, isFirstProfiledThread);
|
||||
isFirstProfiledThread = false;
|
||||
}
|
||||
}
|
||||
OS::Sleep(interval_);
|
||||
}
|
||||
|
||||
// disable any timer resolution changes we've made
|
||||
if (interval_ < 10)
|
||||
::timeEndPeriod(interval_);
|
||||
}
|
||||
|
||||
void SampleContext(Sampler* sampler, ThreadProfile* thread_profile,
|
||||
bool isFirstProfiledThread)
|
||||
{
|
||||
uintptr_t thread = Sampler::GetThreadHandle(
|
||||
thread_profile->GetPlatformData());
|
||||
HANDLE profiled_thread = reinterpret_cast<HANDLE>(thread);
|
||||
if (profiled_thread == NULL)
|
||||
return;
|
||||
|
||||
// Context used for sampling the register state of the profiled thread.
|
||||
CONTEXT context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
TickSample sample_obj;
|
||||
TickSample* sample = &sample_obj;
|
||||
|
||||
// Grab the timestamp before pausing the thread, to avoid deadlocks.
|
||||
sample->timestamp = mozilla::TimeStamp::Now();
|
||||
sample->threadProfile = thread_profile;
|
||||
|
||||
if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) {
|
||||
sample->rssMemory = nsMemoryReporterManager::ResidentFast();
|
||||
} else {
|
||||
sample->rssMemory = 0;
|
||||
}
|
||||
|
||||
// Unique Set Size is not supported on Windows.
|
||||
sample->ussMemory = 0;
|
||||
|
||||
static const DWORD kSuspendFailed = static_cast<DWORD>(-1);
|
||||
if (SuspendThread(profiled_thread) == kSuspendFailed)
|
||||
return;
|
||||
|
||||
// SuspendThread is asynchronous, so the thread may still be running.
|
||||
// Call GetThreadContext first to ensure the thread is really suspended.
|
||||
// See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.
|
||||
|
||||
// Using only CONTEXT_CONTROL is faster but on 64-bit it causes crashes in
|
||||
// RtlVirtualUnwind (see bug 1120126) so we set all the flags.
|
||||
#if V8_HOST_ARCH_X64
|
||||
context.ContextFlags = CONTEXT_FULL;
|
||||
#else
|
||||
context.ContextFlags = CONTEXT_CONTROL;
|
||||
#endif
|
||||
if (!GetThreadContext(profiled_thread, &context)) {
|
||||
ResumeThread(profiled_thread);
|
||||
return;
|
||||
}
|
||||
|
||||
// Threads that may invoke JS require extra attention. Since, on windows,
|
||||
// the jits also need to modify the same dynamic function table that we need
|
||||
// to get a stack trace, we have to be wary of that to avoid deadlock.
|
||||
//
|
||||
// When embedded in Gecko, for threads that aren't the main thread,
|
||||
// CanInvokeJS consults an unlocked value in the nsIThread, so we must
|
||||
// consult this after suspending the profiled thread to avoid racing
|
||||
// against a value change.
|
||||
if (thread_profile->CanInvokeJS()) {
|
||||
if (!TryAcquireStackWalkWorkaroundLock()) {
|
||||
ResumeThread(profiled_thread);
|
||||
return;
|
||||
}
|
||||
|
||||
// It is safe to immediately drop the lock. We only need to contend with
|
||||
// the case in which the profiled thread held needed system resources.
|
||||
// If the profiled thread had held those resources, the trylock would have
|
||||
// failed. Anyone else who grabs those resources will continue to make
|
||||
// progress, since those threads are not suspended. Because of this,
|
||||
// we cannot deadlock with them, and should let them run as they please.
|
||||
ReleaseStackWalkWorkaroundLock();
|
||||
}
|
||||
|
||||
#if V8_HOST_ARCH_X64
|
||||
sample->pc = reinterpret_cast<Address>(context.Rip);
|
||||
sample->sp = reinterpret_cast<Address>(context.Rsp);
|
||||
sample->fp = reinterpret_cast<Address>(context.Rbp);
|
||||
#else
|
||||
sample->pc = reinterpret_cast<Address>(context.Eip);
|
||||
sample->sp = reinterpret_cast<Address>(context.Esp);
|
||||
sample->fp = reinterpret_cast<Address>(context.Ebp);
|
||||
#endif
|
||||
|
||||
sample->context = &context;
|
||||
sampler->Tick(sample);
|
||||
|
||||
ResumeThread(profiled_thread);
|
||||
}
|
||||
|
||||
Sampler* sampler_;
|
||||
int interval_; // units: ms
|
||||
|
||||
// Protects the process wide state below.
|
||||
static SamplerThread* instance_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SamplerThread);
|
||||
};
|
||||
|
||||
SamplerThread* SamplerThread::instance_ = NULL;
|
||||
|
||||
|
||||
Sampler::Sampler(double interval, bool profiling, int entrySize)
|
||||
: interval_(interval),
|
||||
profiling_(profiling),
|
||||
paused_(false),
|
||||
active_(false),
|
||||
entrySize_(entrySize) {
|
||||
}
|
||||
|
||||
Sampler::~Sampler() {
|
||||
ASSERT(!IsActive());
|
||||
}
|
||||
|
||||
void Sampler::Start() {
|
||||
ASSERT(!IsActive());
|
||||
SetActive(true);
|
||||
SamplerThread::StartSampler(this);
|
||||
}
|
||||
|
||||
void Sampler::Stop() {
|
||||
ASSERT(IsActive());
|
||||
SetActive(false);
|
||||
SamplerThread::StopSampler();
|
||||
}
|
||||
|
||||
|
||||
static const HANDLE kNoThread = INVALID_HANDLE_VALUE;
|
||||
|
||||
static unsigned int __stdcall ThreadEntry(void* arg) {
|
||||
Thread* thread = reinterpret_cast<Thread*>(arg);
|
||||
thread->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initialize a Win32 thread object. The thread has an invalid thread
|
||||
// handle until it is started.
|
||||
Thread::Thread(const char* name)
|
||||
: stack_size_(0) {
|
||||
thread_ = kNoThread;
|
||||
set_name(name);
|
||||
}
|
||||
|
||||
void Thread::set_name(const char* name) {
|
||||
strncpy(name_, name, sizeof(name_));
|
||||
name_[sizeof(name_) - 1] = '\0';
|
||||
}
|
||||
|
||||
// Close our own handle for the thread.
|
||||
Thread::~Thread() {
|
||||
if (thread_ != kNoThread) CloseHandle(thread_);
|
||||
}
|
||||
|
||||
// Create a new thread. It is important to use _beginthreadex() instead of
|
||||
// the Win32 function CreateThread(), because the CreateThread() does not
|
||||
// initialize thread specific structures in the C runtime library.
|
||||
void Thread::Start() {
|
||||
thread_ = reinterpret_cast<HANDLE>(
|
||||
_beginthreadex(NULL,
|
||||
static_cast<unsigned>(stack_size_),
|
||||
ThreadEntry,
|
||||
this,
|
||||
0,
|
||||
(unsigned int*) &thread_id_));
|
||||
}
|
||||
|
||||
// Wait for thread to terminate.
|
||||
void Thread::Join() {
|
||||
if (thread_id_ != GetCurrentId()) {
|
||||
WaitForSingleObject(thread_, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ Thread::tid_t
|
||||
Thread::GetCurrentId()
|
||||
{
|
||||
return GetCurrentThreadId();
|
||||
}
|
||||
|
||||
void OS::Startup() {
|
||||
}
|
||||
|
||||
void OS::Sleep(int milliseconds) {
|
||||
::Sleep(milliseconds);
|
||||
}
|
||||
|
||||
bool Sampler::RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop)
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return false;
|
||||
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = GetCurrentThreadId();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
// Thread already registered. This means the first unregister will be
|
||||
// too early.
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_tls_stack_top(stackTop);
|
||||
|
||||
ThreadInfo* info = new StackOwningThreadInfo(aName, id,
|
||||
aIsMainThread, aPseudoStack, stackTop);
|
||||
|
||||
if (sActiveSampler) {
|
||||
sActiveSampler->RegisterThread(info);
|
||||
}
|
||||
|
||||
sRegisteredThreads->push_back(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sampler::UnregisterCurrentThread()
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return;
|
||||
|
||||
tlsStackTop.set(nullptr);
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = GetCurrentThreadId();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
if (profiler_is_active()) {
|
||||
// We still want to show the results of this thread if you
|
||||
// save the profile shortly after a thread is terminated.
|
||||
// For now we will defer the delete to profile stop.
|
||||
info->SetPendingDelete();
|
||||
break;
|
||||
} else {
|
||||
delete info;
|
||||
sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TickSample::PopulateContext(void* aContext)
|
||||
{
|
||||
MOZ_ASSERT(aContext);
|
||||
CONTEXT* pContext = reinterpret_cast<CONTEXT*>(aContext);
|
||||
context = pContext;
|
||||
RtlCaptureContext(pContext);
|
||||
|
||||
#if defined(SPS_PLAT_amd64_windows)
|
||||
|
||||
pc = reinterpret_cast<Address>(pContext->Rip);
|
||||
sp = reinterpret_cast<Address>(pContext->Rsp);
|
||||
fp = reinterpret_cast<Address>(pContext->Rbp);
|
||||
|
||||
#elif defined(SPS_PLAT_x86_windows)
|
||||
|
||||
pc = reinterpret_cast<Address>(pContext->Eip);
|
||||
sp = reinterpret_cast<Address>(pContext->Esp);
|
||||
fp = reinterpret_cast<Address>(pContext->Ebp);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,428 +0,0 @@
|
|||
// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google, Inc. nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
#ifndef TOOLS_PLATFORM_H_
|
||||
#define TOOLS_PLATFORM_H_
|
||||
|
||||
#ifdef SPS_STANDALONE
|
||||
#define MOZ_COUNT_CTOR(name)
|
||||
#define MOZ_COUNT_DTOR(name)
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <android/log.h>
|
||||
#else
|
||||
#define __android_log_print(a, ...)
|
||||
#endif
|
||||
|
||||
#ifdef XP_UNIX
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "ThreadResponsiveness.h"
|
||||
#endif
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "v8-support.h"
|
||||
#include <vector>
|
||||
#include "StackTop.h"
|
||||
|
||||
// We need a definition of gettid(), but Linux libc implementations don't
|
||||
// provide a wrapper for it (except for Bionic)
|
||||
#if defined(__linux__)
|
||||
#include <unistd.h>
|
||||
#if !defined(__BIONIC__)
|
||||
#include <sys/syscall.h>
|
||||
static inline pid_t gettid()
|
||||
{
|
||||
return (pid_t) syscall(SYS_gettid);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#define ASSERT(a) MOZ_ASSERT(a)
|
||||
|
||||
bool moz_profiler_verbose();
|
||||
|
||||
#ifdef ANDROID
|
||||
# if defined(__arm__) || defined(__thumb__)
|
||||
# define ENABLE_SPS_LEAF_DATA
|
||||
# define ENABLE_ARM_LR_SAVING
|
||||
# endif
|
||||
# define LOG(text) \
|
||||
do { if (moz_profiler_verbose()) \
|
||||
__android_log_write(ANDROID_LOG_ERROR, "Profiler", text); \
|
||||
} while (0)
|
||||
# define LOGF(format, ...) \
|
||||
do { if (moz_profiler_verbose()) \
|
||||
__android_log_print(ANDROID_LOG_ERROR, "Profiler", format, \
|
||||
__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
# define LOG(text) \
|
||||
do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: %s\n", text); \
|
||||
} while (0)
|
||||
# define LOGF(format, ...) \
|
||||
do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: " format \
|
||||
"\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
|
||||
#define ENABLE_SPS_LEAF_DATA
|
||||
#endif
|
||||
|
||||
typedef int32_t Atomic32;
|
||||
|
||||
extern mozilla::TimeStamp sStartTime;
|
||||
|
||||
typedef uint8_t* Address;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Mutex
|
||||
//
|
||||
// Mutexes are used for serializing access to non-reentrant sections of code.
|
||||
// The implementations of mutex should allow for nested/recursive locking.
|
||||
|
||||
class Mutex {
|
||||
public:
|
||||
virtual ~Mutex() {}
|
||||
|
||||
// Locks the given mutex. If the mutex is currently unlocked, it becomes
|
||||
// locked and owned by the calling thread, and immediately. If the mutex
|
||||
// is already locked by another thread, suspends the calling thread until
|
||||
// the mutex is unlocked.
|
||||
virtual int Lock() = 0;
|
||||
|
||||
// Unlocks the given mutex. The mutex is assumed to be locked and owned by
|
||||
// the calling thread on entrance.
|
||||
virtual int Unlock() = 0;
|
||||
};
|
||||
|
||||
class MutexAutoLock {
|
||||
public:
|
||||
explicit MutexAutoLock(::Mutex& aMutex)
|
||||
: mMutex(&aMutex)
|
||||
{
|
||||
mMutex->Lock();
|
||||
}
|
||||
|
||||
~MutexAutoLock() {
|
||||
mMutex->Unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
Mutex* mMutex;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OS
|
||||
//
|
||||
// This class has static methods for the different platform specific
|
||||
// functions. Add methods here to cope with differences between the
|
||||
// supported platforms.
|
||||
|
||||
class OS {
|
||||
public:
|
||||
|
||||
// Sleep for a number of milliseconds.
|
||||
static void Sleep(const int milliseconds);
|
||||
|
||||
// Sleep for a number of microseconds.
|
||||
static void SleepMicro(const int microseconds);
|
||||
|
||||
// Called on startup to initialize platform specific things
|
||||
static void Startup();
|
||||
|
||||
static mozilla::UniquePtr< ::Mutex> CreateMutex(const char* aDesc);
|
||||
|
||||
private:
|
||||
static const int msPerSecond = 1000;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Thread
|
||||
//
|
||||
// Thread objects are used for creating and running threads. When the start()
|
||||
// method is called the new thread starts running the run() method in the new
|
||||
// thread. The Thread object should not be deallocated before the thread has
|
||||
// terminated.
|
||||
|
||||
class Thread {
|
||||
public:
|
||||
// Create new thread.
|
||||
explicit Thread(const char* name);
|
||||
virtual ~Thread();
|
||||
|
||||
// Start new thread by calling the Run() method in the new thread.
|
||||
void Start();
|
||||
|
||||
void Join();
|
||||
|
||||
inline const char* name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
// Abstract method for run handler.
|
||||
virtual void Run() = 0;
|
||||
|
||||
// The thread name length is limited to 16 based on Linux's implementation of
|
||||
// prctl().
|
||||
static const int kMaxThreadNameLength = 16;
|
||||
|
||||
#ifdef XP_WIN
|
||||
HANDLE thread_;
|
||||
typedef DWORD tid_t;
|
||||
tid_t thread_id_;
|
||||
#else
|
||||
typedef ::pid_t tid_t;
|
||||
#endif
|
||||
#if defined(XP_MACOSX)
|
||||
pthread_t thread_;
|
||||
#endif
|
||||
|
||||
static tid_t GetCurrentId();
|
||||
|
||||
private:
|
||||
void set_name(const char *name);
|
||||
|
||||
char name_[kMaxThreadNameLength];
|
||||
int stack_size_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Thread);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// HAVE_NATIVE_UNWIND
|
||||
//
|
||||
// Pseudo backtraces are available on all platforms. Native
|
||||
// backtraces are available only on selected platforms. Breakpad is
|
||||
// the only supported native unwinder. HAVE_NATIVE_UNWIND is set at
|
||||
// build time to indicate whether native unwinding is possible on this
|
||||
// platform.
|
||||
|
||||
#undef HAVE_NATIVE_UNWIND
|
||||
#if defined(MOZ_PROFILING) \
|
||||
&& (defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||||
|| (defined(MOZ_WIDGET_ANDROID) && defined(__arm__)) \
|
||||
|| defined(SPS_PLAT_x86_linux) \
|
||||
|| defined(SPS_OS_windows) \
|
||||
|| defined(SPS_OS_darwin))
|
||||
# define HAVE_NATIVE_UNWIND
|
||||
#endif
|
||||
|
||||
/* Some values extracted at startup from environment variables, that
|
||||
control the behaviour of the breakpad unwinder. */
|
||||
extern const char* PROFILER_INTERVAL;
|
||||
extern const char* PROFILER_ENTRIES;
|
||||
extern const char* PROFILER_STACK;
|
||||
extern const char* PROFILER_FEATURES;
|
||||
|
||||
void read_profiler_env_vars();
|
||||
void profiler_usage();
|
||||
|
||||
// Helper methods to expose modifying profiler behavior
|
||||
bool set_profiler_interval(const char*);
|
||||
bool set_profiler_entries(const char*);
|
||||
bool set_profiler_scan(const char*);
|
||||
bool is_native_unwinding_avail();
|
||||
|
||||
void set_tls_stack_top(void* stackTop);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sampler
|
||||
//
|
||||
// A sampler periodically samples the state of the VM and optionally
|
||||
// (if used for profiling) the program counter and stack pointer for
|
||||
// the thread that created it.
|
||||
|
||||
struct PseudoStack;
|
||||
class ThreadProfile;
|
||||
|
||||
// TickSample captures the information collected for each sample.
|
||||
class TickSample {
|
||||
public:
|
||||
TickSample()
|
||||
: pc(NULL)
|
||||
, sp(NULL)
|
||||
, fp(NULL)
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
, lr(NULL)
|
||||
#endif
|
||||
, context(NULL)
|
||||
, isSamplingCurrentThread(false)
|
||||
, threadProfile(nullptr)
|
||||
, rssMemory(0)
|
||||
, ussMemory(0)
|
||||
{}
|
||||
|
||||
void PopulateContext(void* aContext);
|
||||
|
||||
Address pc; // Instruction pointer.
|
||||
Address sp; // Stack pointer.
|
||||
Address fp; // Frame pointer.
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
Address lr; // ARM link register
|
||||
#endif
|
||||
void* context; // The context from the signal handler, if available. On
|
||||
// Win32 this may contain the windows thread context.
|
||||
bool isSamplingCurrentThread;
|
||||
ThreadProfile* threadProfile;
|
||||
mozilla::TimeStamp timestamp;
|
||||
int64_t rssMemory;
|
||||
int64_t ussMemory;
|
||||
};
|
||||
|
||||
class ThreadInfo;
|
||||
class PlatformData;
|
||||
class GeckoSampler;
|
||||
class SyncProfile;
|
||||
class Sampler {
|
||||
public:
|
||||
// Initialize sampler.
|
||||
explicit Sampler(double interval, bool profiling, int entrySize);
|
||||
virtual ~Sampler();
|
||||
|
||||
double interval() const { return interval_; }
|
||||
|
||||
// This method is called for each sampling period with the current
|
||||
// program counter.
|
||||
virtual void Tick(TickSample* sample) = 0;
|
||||
|
||||
// Immediately captures the calling thread's call stack and returns it.
|
||||
virtual SyncProfile* GetBacktrace() = 0;
|
||||
|
||||
// Request a save from a signal handler
|
||||
virtual void RequestSave() = 0;
|
||||
// Process any outstanding request outside a signal handler.
|
||||
virtual void HandleSaveRequest() = 0;
|
||||
// Delete markers which are no longer part of the profile due to buffer wraparound.
|
||||
virtual void DeleteExpiredMarkers() = 0;
|
||||
|
||||
// Start and stop sampler.
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
// Is the sampler used for profiling?
|
||||
bool IsProfiling() const { return profiling_; }
|
||||
|
||||
// Whether the sampler is running (that is, consumes resources).
|
||||
bool IsActive() const { return active_; }
|
||||
|
||||
// Low overhead way to stop the sampler from ticking
|
||||
bool IsPaused() const { return paused_; }
|
||||
void SetPaused(bool value) { NoBarrier_Store(&paused_, value); }
|
||||
|
||||
virtual bool ProfileThreads() const = 0;
|
||||
|
||||
int EntrySize() { return entrySize_; }
|
||||
|
||||
// We can't new/delete the type safely without defining it
|
||||
// (-Wdelete-incomplete). Use these Alloc/Free functions instead.
|
||||
static PlatformData* AllocPlatformData(int aThreadId);
|
||||
static void FreePlatformData(PlatformData*);
|
||||
|
||||
// If we move the backtracing code into the platform files we won't
|
||||
// need to have these hacks
|
||||
#ifdef XP_WIN
|
||||
// xxxehsan sucky hack :(
|
||||
static uintptr_t GetThreadHandle(PlatformData*);
|
||||
#endif
|
||||
#ifdef XP_MACOSX
|
||||
static pthread_t GetProfiledThread(PlatformData*);
|
||||
#endif
|
||||
|
||||
static std::vector<ThreadInfo*> GetRegisteredThreads() {
|
||||
return *sRegisteredThreads;
|
||||
}
|
||||
|
||||
static bool RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop);
|
||||
static void UnregisterCurrentThread();
|
||||
|
||||
static void Startup();
|
||||
// Should only be called on shutdown
|
||||
static void Shutdown();
|
||||
|
||||
static GeckoSampler* GetActiveSampler() { return sActiveSampler; }
|
||||
static void SetActiveSampler(GeckoSampler* sampler) { sActiveSampler = sampler; }
|
||||
|
||||
static mozilla::UniquePtr<Mutex> sRegisteredThreadsMutex;
|
||||
|
||||
static bool CanNotifyObservers() {
|
||||
#if defined(SPS_OS_android)
|
||||
// Android ANR reporter uses the profiler off the main thread
|
||||
return NS_IsMainThread();
|
||||
#else
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
static std::vector<ThreadInfo*>* sRegisteredThreads;
|
||||
static GeckoSampler* sActiveSampler;
|
||||
|
||||
private:
|
||||
void SetActive(bool value) { NoBarrier_Store(&active_, value); }
|
||||
|
||||
const double interval_;
|
||||
const bool profiling_;
|
||||
Atomic32 paused_;
|
||||
Atomic32 active_;
|
||||
const int entrySize_;
|
||||
|
||||
// Refactor me!
|
||||
#if defined(SPS_OS_linux) || defined(SPS_OS_android)
|
||||
bool signal_handler_installed_;
|
||||
struct sigaction old_sigprof_signal_handler_;
|
||||
struct sigaction old_sigsave_signal_handler_;
|
||||
bool signal_sender_launched_;
|
||||
pthread_t signal_sender_thread_;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* ndef TOOLS_PLATFORM_H_ */
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "shared-libraries.h"
|
||||
|
||||
#define PATH_MAX_TOSTRING(x) #x
|
||||
#define PATH_MAX_STRING(x) PATH_MAX_TOSTRING(x)
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
#include "platform.h"
|
||||
#include "shared-libraries.h"
|
||||
|
||||
#include "common/linux/file_id.h"
|
||||
#include <algorithm>
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
// Get the breakpad Id for the binary file pointed by bin_name
|
||||
static std::string getId(const char *bin_name)
|
||||
{
|
||||
using namespace google_breakpad;
|
||||
using namespace std;
|
||||
|
||||
PageAllocator allocator;
|
||||
auto_wasteful_vector<uint8_t, sizeof(MDGUID)> identifier(&allocator);
|
||||
|
||||
FileID file_id(bin_name);
|
||||
if (file_id.ElfFileIdentifier(identifier)) {
|
||||
return FileID::ConvertIdentifierToUUIDString(identifier) + "0";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// TODO fix me with proper include
|
||||
#include "nsDebug.h"
|
||||
#ifdef ANDROID
|
||||
#include "ElfLoader.h" // dl_phdr_info
|
||||
#else
|
||||
#include <link.h> // dl_phdr_info
|
||||
#endif
|
||||
#include <features.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef ANDROID
|
||||
extern "C" MOZ_EXPORT __attribute__((weak))
|
||||
int dl_iterate_phdr(
|
||||
int (*callback) (struct dl_phdr_info *info,
|
||||
size_t size, void *data),
|
||||
void *data);
|
||||
#endif
|
||||
|
||||
static int
|
||||
dl_iterate_callback(struct dl_phdr_info *dl_info, size_t size, void *data)
|
||||
{
|
||||
SharedLibraryInfo& info = *reinterpret_cast<SharedLibraryInfo*>(data);
|
||||
|
||||
if (dl_info->dlpi_phnum <= 0)
|
||||
return 0;
|
||||
|
||||
unsigned long libStart = -1;
|
||||
unsigned long libEnd = 0;
|
||||
|
||||
for (size_t i = 0; i < dl_info->dlpi_phnum; i++) {
|
||||
if (dl_info->dlpi_phdr[i].p_type != PT_LOAD) {
|
||||
continue;
|
||||
}
|
||||
unsigned long start = dl_info->dlpi_addr + dl_info->dlpi_phdr[i].p_vaddr;
|
||||
unsigned long end = start + dl_info->dlpi_phdr[i].p_memsz;
|
||||
if (start < libStart)
|
||||
libStart = start;
|
||||
if (end > libEnd)
|
||||
libEnd = end;
|
||||
}
|
||||
const char *name = dl_info->dlpi_name;
|
||||
SharedLibrary shlib(libStart, libEnd, 0, getId(name), name);
|
||||
info.AddSharedLibrary(shlib);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo info;
|
||||
|
||||
#ifdef ANDROID
|
||||
if (!dl_iterate_phdr) {
|
||||
// On ARM Android, dl_iterate_phdr is provided by the custom linker.
|
||||
// So if libxul was loaded by the system linker (e.g. as part of
|
||||
// xpcshell when running tests), it won't be available and we should
|
||||
// not call it.
|
||||
return info;
|
||||
}
|
||||
#endif // ANDROID
|
||||
|
||||
dl_iterate_phdr(dl_iterate_callback, &info);
|
||||
|
||||
#if defined(ANDROID)
|
||||
pid_t pid = getpid();
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, PATH_MAX, "/proc/%d/maps", pid);
|
||||
std::ifstream maps(path);
|
||||
std::string line;
|
||||
int count = 0;
|
||||
while (std::getline(maps, line)) {
|
||||
int ret;
|
||||
//XXX: needs input sanitizing
|
||||
unsigned long start;
|
||||
unsigned long end;
|
||||
char perm[6] = "";
|
||||
unsigned long offset;
|
||||
char name[PATH_MAX] = "";
|
||||
ret = sscanf(line.c_str(),
|
||||
"%lx-%lx %6s %lx %*s %*x %" PATH_MAX_STRING(PATH_MAX) "s\n",
|
||||
&start, &end, perm, &offset, name);
|
||||
if (!strchr(perm, 'x')) {
|
||||
// Ignore non executable entries
|
||||
continue;
|
||||
}
|
||||
if (ret != 5 && ret != 4) {
|
||||
LOG("Get maps line failed");
|
||||
continue;
|
||||
}
|
||||
// Use proc/pid/maps to get the dalvik-jit section since it has
|
||||
// no associated phdrs
|
||||
if (strcmp(name, "/dev/ashmem/dalvik-jit-code-cache") != 0)
|
||||
continue;
|
||||
SharedLibrary shlib(start, end, offset, getId(name), name);
|
||||
info.AddSharedLibrary(shlib);
|
||||
if (count > 10000) {
|
||||
LOG("Get maps failed");
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
#endif // ANDROID
|
||||
|
||||
return info;
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/dyld_images.h>
|
||||
#include <mach/task_info.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_traps.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include "shared-libraries.h"
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
|
||||
// borrowed from Breakpad
|
||||
// Fallback declarations for TASK_DYLD_INFO and friends, introduced in
|
||||
// <mach/task_info.h> in the Mac OS X 10.6 SDK.
|
||||
#define TASK_DYLD_INFO 17
|
||||
struct task_dyld_info {
|
||||
mach_vm_address_t all_image_info_addr;
|
||||
mach_vm_size_t all_image_info_size;
|
||||
};
|
||||
typedef struct task_dyld_info task_dyld_info_data_t;
|
||||
typedef struct task_dyld_info *task_dyld_info_t;
|
||||
#define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t))
|
||||
|
||||
#endif
|
||||
|
||||
// Architecture specific abstraction.
|
||||
#ifdef __i386__
|
||||
typedef mach_header platform_mach_header;
|
||||
typedef segment_command mach_segment_command_type;
|
||||
#define MACHO_MAGIC_NUMBER MH_MAGIC
|
||||
#define CMD_SEGMENT LC_SEGMENT
|
||||
#define seg_size uint32_t
|
||||
#else
|
||||
typedef mach_header_64 platform_mach_header;
|
||||
typedef segment_command_64 mach_segment_command_type;
|
||||
#define MACHO_MAGIC_NUMBER MH_MAGIC_64
|
||||
#define CMD_SEGMENT LC_SEGMENT_64
|
||||
#define seg_size uint64_t
|
||||
#endif
|
||||
|
||||
static
|
||||
void addSharedLibrary(const platform_mach_header* header, char *name, SharedLibraryInfo &info) {
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
seg_size size = 0;
|
||||
unsigned long long start = reinterpret_cast<unsigned long long>(header);
|
||||
// Find the cmd segment in the macho image. It will contain the offset we care about.
|
||||
const uint8_t *uuid_bytes = nullptr;
|
||||
for (unsigned int i = 0;
|
||||
cmd && (i < header->ncmds) && (uuid_bytes == nullptr || size == 0);
|
||||
++i) {
|
||||
if (cmd->cmd == CMD_SEGMENT) {
|
||||
const mach_segment_command_type *seg =
|
||||
reinterpret_cast<const mach_segment_command_type *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
size = seg->vmsize;
|
||||
}
|
||||
} else if (cmd->cmd == LC_UUID) {
|
||||
const uuid_command *ucmd = reinterpret_cast<const uuid_command *>(cmd);
|
||||
uuid_bytes = ucmd->uuid;
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<const struct load_command *>
|
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize);
|
||||
}
|
||||
|
||||
std::stringstream uuid;
|
||||
uuid << std::hex << std::uppercase;
|
||||
if (uuid_bytes != nullptr) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
uuid << ((uuid_bytes[i] & 0xf0) >> 4);
|
||||
uuid << (uuid_bytes[i] & 0xf);
|
||||
}
|
||||
uuid << '0';
|
||||
}
|
||||
|
||||
info.AddSharedLibrary(SharedLibrary(start, start + size, 0, uuid.str(),
|
||||
name));
|
||||
}
|
||||
|
||||
// Use dyld to inspect the macho image information. We can build the SharedLibraryEntry structure
|
||||
// giving us roughtly the same info as /proc/PID/maps in Linux.
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo sharedLibraryInfo;
|
||||
|
||||
task_dyld_info_data_t task_dyld_info;
|
||||
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
|
||||
if (task_info(mach_task_self (), TASK_DYLD_INFO, (task_info_t)&task_dyld_info,
|
||||
&count) != KERN_SUCCESS) {
|
||||
return sharedLibraryInfo;
|
||||
}
|
||||
|
||||
struct dyld_all_image_infos* aii = (struct dyld_all_image_infos*)task_dyld_info.all_image_info_addr;
|
||||
size_t infoCount = aii->infoArrayCount;
|
||||
|
||||
// Iterate through all dyld images (loaded libraries) to get their names
|
||||
// and offests.
|
||||
for (size_t i = 0; i < infoCount; ++i) {
|
||||
const dyld_image_info *info = &aii->infoArray[i];
|
||||
|
||||
// If the magic number doesn't match then go no further
|
||||
// since we're not pointing to where we think we are.
|
||||
if (info->imageLoadAddress->magic != MACHO_MAGIC_NUMBER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const platform_mach_header* header =
|
||||
reinterpret_cast<const platform_mach_header*>(info->imageLoadAddress);
|
||||
|
||||
// Add the entry for this image.
|
||||
addSharedLibrary(header, (char*)info->imageFilePath, sharedLibraryInfo);
|
||||
|
||||
}
|
||||
return sharedLibraryInfo;
|
||||
}
|
||||
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <dbghelp.h>
|
||||
#include <sstream>
|
||||
|
||||
#include "shared-libraries.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#define CV_SIGNATURE 0x53445352 // 'SDSR'
|
||||
|
||||
struct CodeViewRecord70
|
||||
{
|
||||
uint32_t signature;
|
||||
GUID pdbSignature;
|
||||
uint32_t pdbAge;
|
||||
char pdbFileName[1];
|
||||
};
|
||||
|
||||
static bool GetPdbInfo(uintptr_t aStart, nsID& aSignature, uint32_t& aAge, char** aPdbName)
|
||||
{
|
||||
if (!aStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(aStart);
|
||||
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_NT_HEADERS ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(
|
||||
aStart + dosHeader->e_lfanew);
|
||||
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t relativeVirtualAddress =
|
||||
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
|
||||
if (!relativeVirtualAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_DEBUG_DIRECTORY debugDirectory =
|
||||
reinterpret_cast<PIMAGE_DEBUG_DIRECTORY>(aStart + relativeVirtualAddress);
|
||||
if (!debugDirectory || debugDirectory->Type != IMAGE_DEBUG_TYPE_CODEVIEW) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CodeViewRecord70 *debugInfo = reinterpret_cast<CodeViewRecord70 *>(
|
||||
aStart + debugDirectory->AddressOfRawData);
|
||||
if (!debugInfo || debugInfo->signature != CV_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aAge = debugInfo->pdbAge;
|
||||
GUID& pdbSignature = debugInfo->pdbSignature;
|
||||
aSignature.m0 = pdbSignature.Data1;
|
||||
aSignature.m1 = pdbSignature.Data2;
|
||||
aSignature.m2 = pdbSignature.Data3;
|
||||
memcpy(aSignature.m3, pdbSignature.Data4, sizeof(pdbSignature.Data4));
|
||||
|
||||
// The PDB file name could be different from module filename, so report both
|
||||
// e.g. The PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb
|
||||
char * leafName = strrchr(debugInfo->pdbFileName, '\\');
|
||||
if (leafName) {
|
||||
// Only report the file portion of the path
|
||||
*aPdbName = leafName + 1;
|
||||
} else {
|
||||
*aPdbName = debugInfo->pdbFileName;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsDashOrBraces(char c)
|
||||
{
|
||||
return c == '-' || c == '{' || c == '}';
|
||||
}
|
||||
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo sharedLibraryInfo;
|
||||
|
||||
nsAutoHandle snap(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()));
|
||||
|
||||
MODULEENTRY32 module = {0};
|
||||
module.dwSize = sizeof(MODULEENTRY32);
|
||||
if (Module32First(snap, &module)) {
|
||||
do {
|
||||
nsID pdbSig;
|
||||
uint32_t pdbAge;
|
||||
char *pdbName = NULL;
|
||||
|
||||
// Load the module again to make sure that its handle will remain remain
|
||||
// valid as we attempt to read the PDB information from it. We load the
|
||||
// DLL as a datafile so that if the module actually gets unloaded between
|
||||
// the call to Module32Next and the following LoadLibraryEx, we don't end
|
||||
// up running the now newly loaded module's DllMain function. If the
|
||||
// module is already loaded, LoadLibraryEx just increments its refcount.
|
||||
//
|
||||
// Note that because of the race condition above, merely loading the DLL
|
||||
// again is not safe enough, therefore we also need to make sure that we
|
||||
// can read the memory mapped at the base address before we can safely
|
||||
// proceed to actually access those pages.
|
||||
HMODULE handleLock = LoadLibraryEx(module.szExePath, NULL, LOAD_LIBRARY_AS_DATAFILE);
|
||||
MEMORY_BASIC_INFORMATION vmemInfo = {0};
|
||||
if (handleLock &&
|
||||
sizeof(vmemInfo) == VirtualQuery(module.modBaseAddr, &vmemInfo, sizeof(vmemInfo)) &&
|
||||
vmemInfo.State == MEM_COMMIT &&
|
||||
GetPdbInfo((uintptr_t)module.modBaseAddr, pdbSig, pdbAge, &pdbName)) {
|
||||
std::ostringstream stream;
|
||||
stream << pdbSig.ToString() << std::hex << pdbAge;
|
||||
std::string breakpadId = stream.str();
|
||||
std::string::iterator end =
|
||||
std::remove_if(breakpadId.begin(), breakpadId.end(), IsDashOrBraces);
|
||||
breakpadId.erase(end, breakpadId.end());
|
||||
std::transform(breakpadId.begin(), breakpadId.end(),
|
||||
breakpadId.begin(), toupper);
|
||||
|
||||
SharedLibrary shlib((uintptr_t)module.modBaseAddr,
|
||||
(uintptr_t)module.modBaseAddr+module.modBaseSize,
|
||||
0, // DLLs are always mapped at offset 0 on Windows
|
||||
breakpadId,
|
||||
pdbName);
|
||||
sharedLibraryInfo.AddSharedLibrary(shlib);
|
||||
}
|
||||
FreeLibrary(handleLock); // ok to free null handles
|
||||
} while (Module32Next(snap, &module));
|
||||
}
|
||||
|
||||
return sharedLibraryInfo;
|
||||
}
|
||||
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/ProfileGatherer.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "GeckoSampler.h"
|
||||
|
||||
using mozilla::dom::AutoJSAPI;
|
||||
using mozilla::dom::Promise;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* When a subprocess exits before we've gathered profiles, we'll
|
||||
* store profiles for those processes until gathering starts. We'll
|
||||
* only store up to MAX_SUBPROCESS_EXIT_PROFILES. The buffer is
|
||||
* circular, so as soon as we receive another exit profile, we'll
|
||||
* bump the oldest one out of the buffer.
|
||||
*/
|
||||
static const uint32_t MAX_SUBPROCESS_EXIT_PROFILES = 5;
|
||||
|
||||
NS_IMPL_ISUPPORTS(ProfileGatherer, nsIObserver)
|
||||
|
||||
ProfileGatherer::ProfileGatherer(GeckoSampler* aTicker)
|
||||
: mTicker(aTicker)
|
||||
, mSinceTime(0)
|
||||
, mPendingProfiles(0)
|
||||
, mGathering(false)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::GatheredOOPProfile()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mGathering) {
|
||||
// If we're not actively gathering, then we don't actually
|
||||
// care that we gathered a profile here. This can happen for
|
||||
// processes that exit while profiling.
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!mPromise)) {
|
||||
// If we're not holding on to a Promise, then someone is
|
||||
// calling us erroneously.
|
||||
return;
|
||||
}
|
||||
|
||||
mPendingProfiles--;
|
||||
|
||||
if (mPendingProfiles == 0) {
|
||||
// We've got all of the async profiles now. Let's
|
||||
// finish off the profile and resolve the Promise.
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::WillGatherOOPProfile()
|
||||
{
|
||||
mPendingProfiles++;
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Start(double aSinceTime,
|
||||
Promise* aPromise)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mGathering) {
|
||||
// If we're already gathering, reject the promise - this isn't going
|
||||
// to end well.
|
||||
if (aPromise) {
|
||||
aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mSinceTime = aSinceTime;
|
||||
mPromise = aPromise;
|
||||
mGathering = true;
|
||||
mPendingProfiles = 0;
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
DebugOnly<nsresult> rv =
|
||||
os->AddObserver(this, "profiler-subprocess", false);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddObserver failed");
|
||||
rv = os->NotifyObservers(this, "profiler-subprocess-gather", nullptr);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NotifyObservers failed");
|
||||
}
|
||||
|
||||
if (!mPendingProfiles) {
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Finish()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mTicker) {
|
||||
// We somehow got called after we were cancelled! This shouldn't
|
||||
// be possible, but doing a belt-and-suspenders check to be sure.
|
||||
return;
|
||||
}
|
||||
|
||||
UniquePtr<char[]> buf = mTicker->ToJSON(mSinceTime);
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
DebugOnly<nsresult> rv = os->RemoveObserver(this, "profiler-subprocess");
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveObserver failed");
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mPromise->GlobalJSObject()))) {
|
||||
// We're really hosed if we can't get a JS context for some reason.
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
// Now parse the JSON so that we resolve with a JS Object.
|
||||
JS::RootedValue val(cx);
|
||||
{
|
||||
NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
|
||||
if (!JS_ParseJSON(cx, static_cast<const char16_t*>(js_string.get()),
|
||||
js_string.Length(), &val)) {
|
||||
if (!jsapi.HasException()) {
|
||||
mPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
} else {
|
||||
JS::RootedValue exn(cx);
|
||||
DebugOnly<bool> gotException = jsapi.StealException(&exn);
|
||||
MOZ_ASSERT(gotException);
|
||||
|
||||
jsapi.ClearException();
|
||||
mPromise->MaybeReject(cx, exn);
|
||||
}
|
||||
} else {
|
||||
mPromise->MaybeResolve(val);
|
||||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Reset()
|
||||
{
|
||||
mSinceTime = 0;
|
||||
mPromise = nullptr;
|
||||
mPendingProfiles = 0;
|
||||
mGathering = false;
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Cancel()
|
||||
{
|
||||
// The GeckoSampler is going away. If we have a Promise in flight, we
|
||||
// should reject it.
|
||||
if (mPromise) {
|
||||
mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
|
||||
// Clear out the GeckoSampler reference, since it's being destroyed.
|
||||
mTicker = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::OOPExitProfile(const nsCString& aProfile)
|
||||
{
|
||||
if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) {
|
||||
mExitProfiles.RemoveElementAt(0);
|
||||
}
|
||||
mExitProfiles.AppendElement(aProfile);
|
||||
|
||||
// If a process exited while gathering, we need to make
|
||||
// sure we decrement the counter.
|
||||
if (mGathering) {
|
||||
GatheredOOPProfile();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ProfileGatherer::Observe(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
const char16_t *someData)
|
||||
{
|
||||
if (!strcmp(aTopic, "profiler-subprocess")) {
|
||||
nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
|
||||
if (pse) {
|
||||
for (size_t i = 0; i < mExitProfiles.Length(); ++i) {
|
||||
if (!mExitProfiles[i].IsEmpty()) {
|
||||
pse->AddSubProfile(mExitProfiles[i].get());
|
||||
}
|
||||
}
|
||||
mExitProfiles.Clear();
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Profiler"];
|
||||
|
||||
this.Profiler = {
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
#include "ProfilerIOInterposeObserver.h"
|
||||
#include "ProfilerMarkers.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
void ProfilerIOInterposeObserver::Observe(Observation& aObservation)
|
||||
{
|
||||
if (!IsMainThread()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilerBacktrace* stack = profiler_get_backtrace();
|
||||
|
||||
nsCString filename;
|
||||
if (aObservation.Filename()) {
|
||||
filename = NS_ConvertUTF16toUTF8(aObservation.Filename());
|
||||
}
|
||||
|
||||
IOMarkerPayload* markerPayload = new IOMarkerPayload(aObservation.Reference(),
|
||||
filename.get(),
|
||||
aObservation.Start(),
|
||||
aObservation.End(),
|
||||
stack);
|
||||
PROFILER_MARKER_PAYLOAD(aObservation.ObservedOperationString(), markerPayload);
|
||||
}
|
||||
|
|
@ -5,24 +5,6 @@
|
|||
#ifndef PROFILERIOINTERPOSEOBSERVER_H
|
||||
#define PROFILERIOINTERPOSEOBSERVER_H
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
|
||||
#include "mozilla/IOInterposer.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* This class is the observer that calls into the profiler whenever
|
||||
* main thread I/O occurs.
|
||||
*/
|
||||
class ProfilerIOInterposeObserver final : public IOInterposeObserver
|
||||
{
|
||||
public:
|
||||
virtual void Observe(Observation& aObservation);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MOZ_ENABLE_PROFILER_SPS
|
||||
/*** STUB ***/
|
||||
|
||||
#endif // PROFILERIOINTERPOSEOBSERVER_H
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "SaveProfileTask.h"
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
nsresult
|
||||
SaveProfileTask::Run() {
|
||||
// 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
|
||||
|
||||
profiler_save_profile_to_file(tmpPath.get());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(ProfileSaveEvent, nsIProfileSaveEvent)
|
||||
|
||||
nsresult
|
||||
ProfileSaveEvent::AddSubProfile(const char* aProfile) {
|
||||
mFunc(aProfile, mClosure);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef PROFILER_SAVETASK_H_
|
||||
#define PROFILER_SAVETASK_H_
|
||||
|
||||
#include "platform.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsIProfileSaveEvent.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#define getpid GetCurrentProcessId
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 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 mozilla::Runnable {
|
||||
public:
|
||||
SaveProfileTask() {}
|
||||
|
||||
NS_IMETHOD Run();
|
||||
};
|
||||
|
||||
class ProfileSaveEvent final : public nsIProfileSaveEvent {
|
||||
public:
|
||||
typedef void (*AddSubProfileFunc)(const char* aProfile, void* aClosure);
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
ProfileSaveEvent(AddSubProfileFunc aFunc, void* aClosure)
|
||||
: mFunc(aFunc)
|
||||
, mClosure(aClosure)
|
||||
{}
|
||||
|
||||
NS_IMETHOD AddSubProfile(const char* aProfile) override;
|
||||
private:
|
||||
~ProfileSaveEvent() {}
|
||||
|
||||
AddSubProfileFunc mFunc;
|
||||
void* mClosure;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ThreadResponsiveness.h"
|
||||
#include "platform.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsITimer.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "ThreadProfile.h"
|
||||
|
||||
using mozilla::Monitor;
|
||||
using mozilla::MonitorAutoLock;
|
||||
using mozilla::TimeStamp;
|
||||
|
||||
class CheckResponsivenessTask : public mozilla::Runnable,
|
||||
public nsITimerCallback {
|
||||
public:
|
||||
CheckResponsivenessTask()
|
||||
: mLastTracerTime(TimeStamp::Now())
|
||||
, mMonitor("CheckResponsivenessTask")
|
||||
, mTimer(nullptr)
|
||||
, mStop(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(CheckResponsivenessTask);
|
||||
}
|
||||
|
||||
protected:
|
||||
~CheckResponsivenessTask()
|
||||
{
|
||||
MOZ_COUNT_DTOR(CheckResponsivenessTask);
|
||||
}
|
||||
|
||||
public:
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (mStop)
|
||||
return NS_OK;
|
||||
|
||||
// This is raced on because we might pause the thread here
|
||||
// for profiling so if we tried to use a monitor to protect
|
||||
// mLastTracerTime we could deadlock. We're risking seeing
|
||||
// a partial write which will show up as an outlier in our
|
||||
// performance data.
|
||||
mLastTracerTime = TimeStamp::Now();
|
||||
if (!mTimer) {
|
||||
mTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
}
|
||||
mTimer->InitWithCallback(this, 16, nsITimer::TYPE_ONE_SHOT);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD Notify(nsITimer* aTimer) final
|
||||
{
|
||||
NS_DispatchToMainThread(this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Terminate() {
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mStop = true;
|
||||
}
|
||||
|
||||
const TimeStamp& GetLastTracerTime() const {
|
||||
return mLastTracerTime;
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
private:
|
||||
TimeStamp mLastTracerTime;
|
||||
Monitor mMonitor;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
bool mStop;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(CheckResponsivenessTask, mozilla::Runnable,
|
||||
nsITimerCallback)
|
||||
|
||||
ThreadResponsiveness::ThreadResponsiveness(ThreadProfile *aThreadProfile)
|
||||
: mThreadProfile(aThreadProfile)
|
||||
, mActiveTracerEvent(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ThreadResponsiveness);
|
||||
}
|
||||
|
||||
ThreadResponsiveness::~ThreadResponsiveness()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ThreadResponsiveness);
|
||||
if (mActiveTracerEvent) {
|
||||
mActiveTracerEvent->Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ThreadResponsiveness::Update()
|
||||
{
|
||||
if (!mActiveTracerEvent) {
|
||||
if (mThreadProfile->GetThreadInfo()->IsMainThread()) {
|
||||
mActiveTracerEvent = new CheckResponsivenessTask();
|
||||
NS_DispatchToMainThread(mActiveTracerEvent);
|
||||
} else if (mThreadProfile->GetThreadInfo()->GetThread()) {
|
||||
mActiveTracerEvent = new CheckResponsivenessTask();
|
||||
mThreadProfile->GetThreadInfo()->
|
||||
GetThread()->Dispatch(mActiveTracerEvent, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (mActiveTracerEvent) {
|
||||
mLastTracerTime = mActiveTracerEvent->GetLastTracerTime();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef ThreadResponsiveness_h
|
||||
#define ThreadResponsiveness_h
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
class ThreadProfile;
|
||||
class CheckResponsivenessTask;
|
||||
|
||||
class ThreadResponsiveness {
|
||||
public:
|
||||
explicit ThreadResponsiveness(ThreadProfile *aThreadProfile);
|
||||
|
||||
~ThreadResponsiveness();
|
||||
|
||||
void Update();
|
||||
|
||||
mozilla::TimeDuration GetUnresponsiveDuration(const mozilla::TimeStamp& now) const {
|
||||
return now - mLastTracerTime;
|
||||
}
|
||||
|
||||
bool HasData() const {
|
||||
return !mLastTracerTime.IsNull();
|
||||
}
|
||||
private:
|
||||
ThreadProfile* mThreadProfile;
|
||||
RefPtr<CheckResponsivenessTask> mActiveTracerEvent;
|
||||
mozilla::TimeStamp mLastTracerTime;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/* -*- Mode: IDL; 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 "nsISupports.idl"
|
||||
|
||||
[uuid(f5ad0830-e178-41f9-b253-db9b4fae4cb3)]
|
||||
interface nsIProfileSaveEvent : nsISupports
|
||||
{
|
||||
/**
|
||||
* Call this method when observing this event to include
|
||||
* a sub profile origining from an external source such
|
||||
* as a non native thread or another process.
|
||||
*/
|
||||
void AddSubProfile(in string aMarker);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
/* -*- Mode: IDL; 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 "nsISupports.idl"
|
||||
|
||||
%{C++
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
class nsCString;
|
||||
%}
|
||||
|
||||
[ref] native StringArrayRef(const nsTArray<nsCString>);
|
||||
|
||||
/**
|
||||
* Start-up parameters for subprocesses are passed through nsIObserverService,
|
||||
* which, unfortunately, means we need to implement nsISupports in order to
|
||||
* go through it.
|
||||
*/
|
||||
[uuid(0a175ba7-8fcf-4ce9-9c4b-ccc6272f4425)]
|
||||
interface nsIProfilerStartParams : nsISupports
|
||||
{
|
||||
attribute uint32_t entries;
|
||||
attribute double interval;
|
||||
|
||||
[noscript, notxpcom, nostdcall] StringArrayRef getFeatures();
|
||||
[noscript, notxpcom, nostdcall] StringArrayRef getThreadFilterNames();
|
||||
};
|
||||
|
||||
[scriptable, uuid(ead3f75c-0e0e-4fbb-901c-1e5392ef5b2a)]
|
||||
interface nsIProfiler : nsISupports
|
||||
{
|
||||
boolean CanProfile();
|
||||
void StartProfiler(in uint32_t aEntries, in double aInterval,
|
||||
[array, size_is(aFeatureCount)] in string aFeatures,
|
||||
in uint32_t aFeatureCount,
|
||||
[array, size_is(aFilterCount), optional] in string aThreadNameFilters,
|
||||
[optional] in uint32_t aFilterCount);
|
||||
void StopProfiler();
|
||||
boolean IsPaused();
|
||||
void PauseSampling();
|
||||
void ResumeSampling();
|
||||
void AddMarker(in string aMarker);
|
||||
/*
|
||||
* Returns the JSON string of the profile. If aSinceTime is passed, only
|
||||
* report samples taken at >= aSinceTime.
|
||||
*/
|
||||
string GetProfile([optional] in double aSinceTime);
|
||||
|
||||
/*
|
||||
* Returns a JS object of the profile. If aSinceTime is passed, only report
|
||||
* samples taken at >= aSinceTime.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
jsval getProfileData([optional] in double aSinceTime);
|
||||
|
||||
[implicit_jscontext]
|
||||
nsISupports getProfileDataAsync([optional] in double aSinceTime);
|
||||
|
||||
boolean IsActive();
|
||||
void GetFeatures(out uint32_t aCount, [retval, array, size_is(aCount)] out string aFeatures);
|
||||
|
||||
/**
|
||||
* The starting parameters that were sent to the profiler for sampling.
|
||||
* If the profiler is not currently sampling, this will return null.
|
||||
*/
|
||||
readonly attribute nsIProfilerStartParams startParams;
|
||||
|
||||
/**
|
||||
* The profileGatherer will be null if the profiler is not currently
|
||||
* active.
|
||||
*/
|
||||
readonly attribute nsISupports profileGatherer;
|
||||
|
||||
void GetBufferInfo(out uint32_t aCurrentPosition, out uint32_t aTotalSize,
|
||||
out uint32_t aGeneration);
|
||||
|
||||
/**
|
||||
* Returns the elapsed time, in milliseconds, since the profiler's epoch.
|
||||
* The epoch is guaranteed to be constant for the duration of the
|
||||
* process, but is otherwise arbitrary.
|
||||
*/
|
||||
double getElapsedTime();
|
||||
|
||||
/**
|
||||
* Returns a JSON string of an array of shared library objects.
|
||||
* Every object has three properties: start, end, and name.
|
||||
* start and end are integers describing the address range that the library
|
||||
* occupies in memory. name is the path of the library as a string.
|
||||
*
|
||||
* On Windows profiling builds, the shared library objects will have
|
||||
* additional pdbSignature and pdbAge properties for uniquely identifying
|
||||
* shared library versions for stack symbolication.
|
||||
*/
|
||||
AString getSharedLibraryInformation();
|
||||
|
||||
/**
|
||||
* Dump the collected profile to a file.
|
||||
*/
|
||||
void dumpProfileToFile(in string aFilename);
|
||||
};
|
||||
|
|
@ -1,308 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 <string>
|
||||
#include <sstream>
|
||||
#include "GeckoProfiler.h"
|
||||
#include "nsProfiler.h"
|
||||
#include "nsProfilerStartParams.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsILoadContext.h"
|
||||
#include "nsIWebNavigation.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "shared-libraries.h"
|
||||
#include "js/Value.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
using mozilla::ErrorResult;
|
||||
using mozilla::dom::Promise;
|
||||
using std::string;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler)
|
||||
|
||||
nsProfiler::nsProfiler()
|
||||
: mLockedForPrivateBrowsing(false)
|
||||
{
|
||||
}
|
||||
|
||||
nsProfiler::~nsProfiler()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
observerService->RemoveObserver(this, "chrome-document-global-created");
|
||||
observerService->RemoveObserver(this, "last-pb-context-exited");
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsProfiler::Init() {
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
observerService->AddObserver(this, "chrome-document-global-created", false);
|
||||
observerService->AddObserver(this, "last-pb-context-exited", false);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::Observe(nsISupports *aSubject,
|
||||
const char *aTopic,
|
||||
const char16_t *aData)
|
||||
{
|
||||
if (strcmp(aTopic, "chrome-document-global-created") == 0) {
|
||||
nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface(aSubject);
|
||||
nsCOMPtr<nsIWebNavigation> parentWebNav = do_GetInterface(requestor);
|
||||
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(parentWebNav);
|
||||
if (loadContext && loadContext->UsePrivateBrowsing() && !mLockedForPrivateBrowsing) {
|
||||
mLockedForPrivateBrowsing = true;
|
||||
profiler_lock();
|
||||
}
|
||||
} else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
|
||||
mLockedForPrivateBrowsing = false;
|
||||
profiler_unlock();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::CanProfile(bool *aCanProfile)
|
||||
{
|
||||
*aCanProfile = !mLockedForPrivateBrowsing;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::StartProfiler(uint32_t aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount)
|
||||
{
|
||||
if (mLockedForPrivateBrowsing) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
profiler_start(aEntries, aInterval,
|
||||
aFeatures, aFeatureCount,
|
||||
aThreadNameFilters, aFilterCount);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::StopProfiler()
|
||||
{
|
||||
profiler_stop();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::IsPaused(bool *aIsPaused)
|
||||
{
|
||||
*aIsPaused = profiler_is_paused();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::PauseSampling()
|
||||
{
|
||||
profiler_pause();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::ResumeSampling()
|
||||
{
|
||||
profiler_resume();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::AddMarker(const char *aMarker)
|
||||
{
|
||||
PROFILER_MARKER(aMarker);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfile(double aSinceTime, char** aProfile)
|
||||
{
|
||||
mozilla::UniquePtr<char[]> profile = profiler_get_profile(aSinceTime);
|
||||
if (profile) {
|
||||
size_t len = strlen(profile.get());
|
||||
char *profileStr = static_cast<char *>
|
||||
(nsMemory::Clone(profile.get(), (len + 1) * sizeof(char)));
|
||||
profileStr[len] = '\0';
|
||||
*aProfile = profileStr;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
std::string GetSharedLibraryInfoStringInternal();
|
||||
|
||||
std::string
|
||||
GetSharedLibraryInfoString()
|
||||
{
|
||||
return GetSharedLibraryInfoStringInternal();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetSharedLibraryInformation(nsAString& aOutString)
|
||||
{
|
||||
aOutString.Assign(NS_ConvertUTF8toUTF16(GetSharedLibraryInfoString().c_str()));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::DumpProfileToFile(const char* aFilename)
|
||||
{
|
||||
profiler_save_profile_to_file(aFilename);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfileData(double aSinceTime, JSContext* aCx,
|
||||
JS::MutableHandle<JS::Value> aResult)
|
||||
{
|
||||
JS::RootedObject obj(aCx, profiler_get_profile_jsobject(aCx, aSinceTime));
|
||||
if (!obj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aResult.setObject(*obj);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfileDataAsync(double aSinceTime, JSContext* aCx,
|
||||
nsISupports** aPromise)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (NS_WARN_IF(!aCx)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsIGlobalObject* go = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
|
||||
|
||||
if (NS_WARN_IF(!go)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
ErrorResult result;
|
||||
RefPtr<Promise> promise = Promise::Create(go, result);
|
||||
if (NS_WARN_IF(result.Failed())) {
|
||||
return result.StealNSResult();
|
||||
}
|
||||
|
||||
profiler_get_profile_jsobject_async(aSinceTime, promise);
|
||||
|
||||
promise.forget(aPromise);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetElapsedTime(double* aElapsedTime)
|
||||
{
|
||||
*aElapsedTime = profiler_time();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::IsActive(bool *aIsActive)
|
||||
{
|
||||
*aIsActive = profiler_is_active();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetFeatures(uint32_t *aCount, char ***aFeatures)
|
||||
{
|
||||
uint32_t len = 0;
|
||||
|
||||
const char **features = profiler_get_features();
|
||||
if (!features) {
|
||||
*aCount = 0;
|
||||
*aFeatures = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
while (features[len]) {
|
||||
len++;
|
||||
}
|
||||
|
||||
char **featureList = static_cast<char **>
|
||||
(moz_xmalloc(len * sizeof(char*)));
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
size_t strLen = strlen(features[i]);
|
||||
featureList[i] = static_cast<char *>
|
||||
(nsMemory::Clone(features[i], (strLen + 1) * sizeof(char)));
|
||||
}
|
||||
|
||||
*aFeatures = featureList;
|
||||
*aCount = len;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetStartParams(nsIProfilerStartParams** aRetVal)
|
||||
{
|
||||
if (!profiler_is_active()) {
|
||||
*aRetVal = nullptr;
|
||||
} else {
|
||||
int entrySize = 0;
|
||||
double interval = 0;
|
||||
mozilla::Vector<const char*> filters;
|
||||
mozilla::Vector<const char*> features;
|
||||
profiler_get_start_params(&entrySize, &interval, &filters, &features);
|
||||
|
||||
nsTArray<nsCString> filtersArray;
|
||||
for (uint32_t i = 0; i < filters.length(); ++i) {
|
||||
filtersArray.AppendElement(filters[i]);
|
||||
}
|
||||
|
||||
nsTArray<nsCString> featuresArray;
|
||||
for (size_t i = 0; i < features.length(); ++i) {
|
||||
featuresArray.AppendElement(features[i]);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIProfilerStartParams> startParams =
|
||||
new nsProfilerStartParams(entrySize, interval, featuresArray,
|
||||
filtersArray);
|
||||
|
||||
startParams.forget(aRetVal);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration)
|
||||
{
|
||||
MOZ_ASSERT(aCurrentPosition);
|
||||
MOZ_ASSERT(aTotalSize);
|
||||
MOZ_ASSERT(aGeneration);
|
||||
profiler_get_buffer_info(aCurrentPosition, aTotalSize, aGeneration);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfileGatherer(nsISupports** aRetVal)
|
||||
{
|
||||
if (!aRetVal) {
|
||||
return NS_ERROR_INVALID_POINTER;
|
||||
}
|
||||
|
||||
// If we're not profiling, there will be no gatherer.
|
||||
if (!profiler_is_active()) {
|
||||
*aRetVal = nullptr;
|
||||
} else {
|
||||
nsCOMPtr<nsISupports> gatherer;
|
||||
profiler_get_gatherer(getter_AddRefs(gatherer));
|
||||
gatherer.forget(aRetVal);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 _NSPROFILER_H_
|
||||
#define _NSPROFILER_H_
|
||||
|
||||
#include "nsIProfiler.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
class nsProfiler final : public nsIProfiler, public nsIObserver
|
||||
{
|
||||
public:
|
||||
nsProfiler();
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
NS_DECL_NSIPROFILER
|
||||
|
||||
nsresult Init();
|
||||
private:
|
||||
~nsProfiler();
|
||||
bool mLockedForPrivateBrowsing;
|
||||
};
|
||||
|
||||
#endif /* _NSPROFILER_H_ */
|
||||
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/ModuleUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsProfiler.h"
|
||||
#include "nsProfilerCIID.h"
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsProfiler, Init)
|
||||
|
||||
NS_DEFINE_NAMED_CID(NS_PROFILER_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kProfilerCIDs[] = {
|
||||
{ &kNS_PROFILER_CID, false, nullptr, nsProfilerConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kProfilerContracts[] = {
|
||||
{ "@mozilla.org/tools/profiler;1", &kNS_PROFILER_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module kProfilerModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kProfilerCIDs,
|
||||
kProfilerContracts
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsProfilerModule) = &kProfilerModule;
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "nsProfilerStartParams.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsProfilerStartParams, nsIProfilerStartParams)
|
||||
|
||||
nsProfilerStartParams::nsProfilerStartParams(uint32_t aEntries,
|
||||
double aInterval,
|
||||
const nsTArray<nsCString>& aFeatures,
|
||||
const nsTArray<nsCString>& aThreadFilterNames) :
|
||||
mEntries(aEntries),
|
||||
mInterval(aInterval),
|
||||
mFeatures(aFeatures),
|
||||
mThreadFilterNames(aThreadFilterNames)
|
||||
{
|
||||
}
|
||||
|
||||
nsProfilerStartParams::~nsProfilerStartParams()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::GetEntries(uint32_t* aEntries)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aEntries);
|
||||
*aEntries = mEntries;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::SetEntries(uint32_t aEntries)
|
||||
{
|
||||
NS_ENSURE_ARG(aEntries);
|
||||
mEntries = aEntries;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::GetInterval(double* aInterval)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aInterval);
|
||||
*aInterval = mInterval;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::SetInterval(double aInterval)
|
||||
{
|
||||
NS_ENSURE_ARG(aInterval);
|
||||
mInterval = aInterval;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const nsTArray<nsCString>&
|
||||
nsProfilerStartParams::GetFeatures()
|
||||
{
|
||||
return mFeatures;
|
||||
}
|
||||
|
||||
const nsTArray<nsCString>&
|
||||
nsProfilerStartParams::GetThreadFilterNames()
|
||||
{
|
||||
return mThreadFilterNames;
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 _NSPROFILERSTARTPARAMS_H_
|
||||
#define _NSPROFILERSTARTPARAMS_H_
|
||||
|
||||
#include "nsIProfiler.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsProfilerStartParams : public nsIProfilerStartParams
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPROFILERSTARTPARAMS
|
||||
|
||||
nsProfilerStartParams(uint32_t aEntries,
|
||||
double aInterval,
|
||||
const nsTArray<nsCString>& aFeatures,
|
||||
const nsTArray<nsCString>& aThreadFilterNames);
|
||||
|
||||
private:
|
||||
virtual ~nsProfilerStartParams();
|
||||
uint32_t mEntries;
|
||||
double mInterval;
|
||||
nsTArray<nsCString> mFeatures;
|
||||
nsTArray<nsCString> mThreadFilterNames;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "PlatformMacros.h"
|
||||
#include "AutoObjectMapper.h"
|
||||
|
||||
#if defined(SPS_OS_android)
|
||||
# include <dlfcn.h>
|
||||
# include "mozilla/Types.h"
|
||||
// FIXME move these out of mozglue/linker/ElfLoader.h into their
|
||||
// own header, so as to avoid conflicts arising from two definitions
|
||||
// of Array
|
||||
extern "C" {
|
||||
MFBT_API size_t
|
||||
__dl_get_mappable_length(void *handle);
|
||||
MFBT_API void *
|
||||
__dl_mmap(void *handle, void *addr, size_t length, off_t offset);
|
||||
MFBT_API void
|
||||
__dl_munmap(void *handle, void *addr, size_t length);
|
||||
}
|
||||
// The following are for get_installation_lib_dir()
|
||||
# include "nsString.h"
|
||||
# include "nsDirectoryServiceUtils.h"
|
||||
# include "nsDirectoryServiceDefs.h"
|
||||
#endif
|
||||
|
||||
|
||||
// A helper function for creating failure error messages in
|
||||
// AutoObjectMapper*::Map.
|
||||
static void
|
||||
failedToMessage(void(*aLog)(const char*),
|
||||
const char* aHowFailed, std::string aFileName)
|
||||
{
|
||||
char buf[300];
|
||||
SprintfLiteral(buf, "AutoObjectMapper::Map: Failed to %s \'%s\'",
|
||||
aHowFailed, aFileName.c_str());
|
||||
buf[sizeof(buf)-1] = 0;
|
||||
aLog(buf);
|
||||
}
|
||||
|
||||
|
||||
AutoObjectMapperPOSIX::AutoObjectMapperPOSIX(void(*aLog)(const char*))
|
||||
: mImage(nullptr)
|
||||
, mSize(0)
|
||||
, mLog(aLog)
|
||||
, mIsMapped(false)
|
||||
{}
|
||||
|
||||
AutoObjectMapperPOSIX::~AutoObjectMapperPOSIX() {
|
||||
if (!mIsMapped) {
|
||||
// There's nothing to do.
|
||||
MOZ_ASSERT(!mImage);
|
||||
MOZ_ASSERT(mSize == 0);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mSize > 0);
|
||||
// The following assertion doesn't necessarily have to be true,
|
||||
// but we assume (reasonably enough) that no mmap facility would
|
||||
// be crazy enough to map anything at page zero.
|
||||
MOZ_ASSERT(mImage);
|
||||
munmap(mImage, mSize);
|
||||
}
|
||||
|
||||
bool AutoObjectMapperPOSIX::Map(/*OUT*/void** start, /*OUT*/size_t* length,
|
||||
std::string fileName)
|
||||
{
|
||||
MOZ_ASSERT(!mIsMapped);
|
||||
|
||||
int fd = open(fileName.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
failedToMessage(mLog, "open", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
int err = fstat(fd, &st);
|
||||
size_t sz = (err == 0) ? st.st_size : 0;
|
||||
if (err != 0 || sz == 0) {
|
||||
failedToMessage(mLog, "fstat", fileName);
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* image = mmap(nullptr, sz, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (image == MAP_FAILED) {
|
||||
failedToMessage(mLog, "mmap", fileName);
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
mIsMapped = true;
|
||||
mImage = *start = image;
|
||||
mSize = *length = sz;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#if defined(SPS_OS_android)
|
||||
// A helper function for AutoObjectMapperFaultyLib::Map. Finds out
|
||||
// where the installation's lib directory is, since we'll have to look
|
||||
// in there to get hold of libmozglue.so. Returned C string is heap
|
||||
// allocated and the caller must deallocate it.
|
||||
static char*
|
||||
get_installation_lib_dir()
|
||||
{
|
||||
nsCOMPtr<nsIProperties>
|
||||
directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
|
||||
if (!directoryService) {
|
||||
return nullptr;
|
||||
}
|
||||
nsCOMPtr<nsIFile> greDir;
|
||||
nsresult rv = directoryService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile),
|
||||
getter_AddRefs(greDir));
|
||||
if (NS_FAILED(rv)) return nullptr;
|
||||
nsCString path;
|
||||
rv = greDir->GetNativePath(path);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
return strdup(path.get());
|
||||
}
|
||||
|
||||
AutoObjectMapperFaultyLib::AutoObjectMapperFaultyLib(void(*aLog)(const char*))
|
||||
: AutoObjectMapperPOSIX(aLog)
|
||||
, mHdl(nullptr)
|
||||
{}
|
||||
|
||||
AutoObjectMapperFaultyLib::~AutoObjectMapperFaultyLib() {
|
||||
if (mHdl) {
|
||||
// We've got an object mapped by faulty.lib. Unmap it via faulty.lib.
|
||||
MOZ_ASSERT(mSize > 0);
|
||||
// Assert on the basis that no valid mapping would start at page zero.
|
||||
MOZ_ASSERT(mImage);
|
||||
__dl_munmap(mHdl, mImage, mSize);
|
||||
dlclose(mHdl);
|
||||
// Stop assertions in ~AutoObjectMapperPOSIX from failing.
|
||||
mImage = nullptr;
|
||||
mSize = 0;
|
||||
}
|
||||
// At this point the parent class destructor, ~AutoObjectMapperPOSIX,
|
||||
// gets called. If that has something mapped in the normal way, it
|
||||
// will unmap it in the normal way. Unfortunately there's no
|
||||
// obvious way to enforce the requirement that the object is mapped
|
||||
// either by faulty.lib or by the parent class, but not by both.
|
||||
}
|
||||
|
||||
bool AutoObjectMapperFaultyLib::Map(/*OUT*/void** start, /*OUT*/size_t* length,
|
||||
std::string fileName)
|
||||
{
|
||||
MOZ_ASSERT(!mHdl);
|
||||
|
||||
if (fileName == "libmozglue.so") {
|
||||
|
||||
// Do (2) in the comment above.
|
||||
char* libdir = get_installation_lib_dir();
|
||||
if (libdir) {
|
||||
fileName = std::string(libdir) + "/lib/" + fileName;
|
||||
free(libdir);
|
||||
}
|
||||
// Hand the problem off to the standard mapper.
|
||||
return AutoObjectMapperPOSIX::Map(start, length, fileName);
|
||||
|
||||
} else {
|
||||
|
||||
// Do cases (1) and (3) in the comment above. We have to
|
||||
// grapple with faulty.lib directly.
|
||||
void* hdl = dlopen(fileName.c_str(), RTLD_GLOBAL | RTLD_LAZY);
|
||||
if (!hdl) {
|
||||
failedToMessage(mLog, "get handle for ELF file", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t sz = __dl_get_mappable_length(hdl);
|
||||
if (sz == 0) {
|
||||
dlclose(hdl);
|
||||
failedToMessage(mLog, "get size for ELF file", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* image = __dl_mmap(hdl, nullptr, sz, 0);
|
||||
if (image == MAP_FAILED) {
|
||||
dlclose(hdl);
|
||||
failedToMessage(mLog, "mmap ELF file", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
mHdl = hdl;
|
||||
mImage = *start = image;
|
||||
mSize = *length = sz;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // defined(SPS_OS_android)
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef AutoObjectMapper_h
|
||||
#define AutoObjectMapper_h
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "PlatformMacros.h"
|
||||
|
||||
// A (nearly-) RAII class that maps an object in and then unmaps it on
|
||||
// destruction. This base class version uses the "normal" POSIX
|
||||
// functions: open, fstat, close, mmap, munmap.
|
||||
|
||||
class MOZ_STACK_CLASS AutoObjectMapperPOSIX {
|
||||
public:
|
||||
// The constructor does not attempt to map the file, because that
|
||||
// might fail. Instead, once the object has been constructed,
|
||||
// call Map() to attempt the mapping. There is no corresponding
|
||||
// Unmap() since the unmapping is done in the destructor. Failure
|
||||
// messages are sent to |aLog|.
|
||||
explicit AutoObjectMapperPOSIX(void(*aLog)(const char*));
|
||||
|
||||
// Unmap the file on destruction of this object.
|
||||
~AutoObjectMapperPOSIX();
|
||||
|
||||
// Map |fileName| into the address space and return the mapping
|
||||
// extents. If the file is zero sized this will fail. The file is
|
||||
// mapped read-only and private. Returns true iff the mapping
|
||||
// succeeded, in which case *start and *length hold its extent.
|
||||
// Once a call to Map succeeds, all subsequent calls to it will
|
||||
// fail.
|
||||
bool Map(/*OUT*/void** start, /*OUT*/size_t* length, std::string fileName);
|
||||
|
||||
protected:
|
||||
// If we are currently holding a mapped object, these record the
|
||||
// mapped address range.
|
||||
void* mImage;
|
||||
size_t mSize;
|
||||
|
||||
// A logging sink, for complaining about mapping failures.
|
||||
void (*mLog)(const char*);
|
||||
|
||||
private:
|
||||
// Are we currently holding a mapped object? This is private to
|
||||
// the base class. Derived classes need to have their own way to
|
||||
// track whether they are holding a mapped object.
|
||||
bool mIsMapped;
|
||||
|
||||
// Disable copying and assignment.
|
||||
AutoObjectMapperPOSIX(const AutoObjectMapperPOSIX&);
|
||||
AutoObjectMapperPOSIX& operator=(const AutoObjectMapperPOSIX&);
|
||||
// Disable heap allocation of this class.
|
||||
void* operator new(size_t);
|
||||
void* operator new[](size_t);
|
||||
void operator delete(void*);
|
||||
void operator delete[](void*);
|
||||
};
|
||||
|
||||
|
||||
#if defined(SPS_OS_android)
|
||||
// This is a variant of AutoObjectMapperPOSIX suitable for use in
|
||||
// conjunction with faulty.lib on Android. How it behaves depends on
|
||||
// the name of the file to be mapped. There are three possible cases:
|
||||
//
|
||||
// (1) /foo/bar/xyzzy/blah.apk!/libwurble.so
|
||||
// We hand it as-is to faulty.lib and let it fish the relevant
|
||||
// bits out of the APK.
|
||||
//
|
||||
// (2) libmozglue.so
|
||||
// This is part of the Fennec installation, but is not in the
|
||||
// APK. Instead we have to figure out the installation path
|
||||
// and look for it there. Because of faulty.lib limitations,
|
||||
// we have to use regular open/mmap instead of faulty.lib.
|
||||
//
|
||||
// (3) libanythingelse.so
|
||||
// faulty.lib assumes this is a system library, and prepends
|
||||
// "/system/lib/" to the path. So as in (1), we can give it
|
||||
// as-is to faulty.lib.
|
||||
//
|
||||
// Hence (1) and (3) require special-casing here. Case (2) simply
|
||||
// hands the problem to the parent class.
|
||||
|
||||
class MOZ_STACK_CLASS AutoObjectMapperFaultyLib : public AutoObjectMapperPOSIX {
|
||||
public:
|
||||
AutoObjectMapperFaultyLib(void(*aLog)(const char*));
|
||||
|
||||
~AutoObjectMapperFaultyLib();
|
||||
|
||||
bool Map(/*OUT*/void** start, /*OUT*/size_t* length, std::string fileName);
|
||||
|
||||
private:
|
||||
// faulty.lib requires us to maintain an abstract handle that can be
|
||||
// used later to unmap the area. If this is non-NULL, it is assumed
|
||||
// that unmapping is to be done by faulty.lib. Otherwise it goes
|
||||
// via the normal mechanism.
|
||||
void* mHdl;
|
||||
|
||||
// Disable copying and assignment.
|
||||
AutoObjectMapperFaultyLib(const AutoObjectMapperFaultyLib&);
|
||||
AutoObjectMapperFaultyLib& operator=(const AutoObjectMapperFaultyLib&);
|
||||
// Disable heap allocation of this class.
|
||||
void* operator new(size_t);
|
||||
void* operator new[](size_t);
|
||||
void operator delete(void*);
|
||||
void operator delete[](void*);
|
||||
};
|
||||
|
||||
#endif // defined(SPS_OS_android)
|
||||
|
||||
#endif // AutoObjectMapper_h
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2011, 2013 Google Inc.
|
||||
// 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.
|
||||
|
||||
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
|
||||
// This file is derived from the following files in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/module.cc
|
||||
// src/common/unique_string.cc
|
||||
|
||||
// There's no internal-only interface for LulCommon. Hence include
|
||||
// the external interface directly.
|
||||
#include "LulCommonExt.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
|
||||
namespace lul {
|
||||
|
||||
using std::string;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Module
|
||||
//
|
||||
Module::Module(const string &name, const string &os,
|
||||
const string &architecture, const string &id) :
|
||||
name_(name),
|
||||
os_(os),
|
||||
architecture_(architecture),
|
||||
id_(id) { }
|
||||
|
||||
Module::~Module() {
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// UniqueString
|
||||
//
|
||||
class UniqueString {
|
||||
public:
|
||||
explicit UniqueString(string str) { str_ = strdup(str.c_str()); }
|
||||
~UniqueString() { free(reinterpret_cast<void*>(const_cast<char*>(str_))); }
|
||||
const char* str_;
|
||||
};
|
||||
|
||||
const char* FromUniqueString(const UniqueString* ustr)
|
||||
{
|
||||
return ustr->str_;
|
||||
}
|
||||
|
||||
bool IsEmptyUniqueString(const UniqueString* ustr)
|
||||
{
|
||||
return (ustr->str_)[0] == '\0';
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// UniqueStringUniverse
|
||||
//
|
||||
UniqueStringUniverse::~UniqueStringUniverse()
|
||||
{
|
||||
for (std::map<string, UniqueString*>::iterator it = map_.begin();
|
||||
it != map_.end(); it++) {
|
||||
delete it->second;
|
||||
}
|
||||
}
|
||||
|
||||
const UniqueString* UniqueStringUniverse::ToUniqueString(string str)
|
||||
{
|
||||
std::map<string, UniqueString*>::iterator it = map_.find(str);
|
||||
if (it == map_.end()) {
|
||||
UniqueString* ustr = new UniqueString(str);
|
||||
map_[str] = ustr;
|
||||
return ustr;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lul
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,359 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "LulDwarfSummariser.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
// Set this to 1 for verbose logging
|
||||
#define DEBUG_SUMMARISER 0
|
||||
|
||||
namespace lul {
|
||||
|
||||
// Do |s64|'s lowest 32 bits sign extend back to |s64| itself?
|
||||
static inline bool fitsIn32Bits(int64 s64) {
|
||||
return s64 == ((s64 & 0xffffffff) ^ 0x80000000) - 0x80000000;
|
||||
}
|
||||
|
||||
// Check a LExpr prefix expression, starting at pfxInstrs[start] up to
|
||||
// the next PX_End instruction, to ensure that:
|
||||
// * It only mentions registers that are tracked on this target
|
||||
// * The start point is sane
|
||||
// If the expression is ok, return NULL. Else return a pointer
|
||||
// a const char* holding a bit of text describing the problem.
|
||||
static const char*
|
||||
checkPfxExpr(const vector<PfxInstr>* pfxInstrs, int64_t start)
|
||||
{
|
||||
size_t nInstrs = pfxInstrs->size();
|
||||
if (start < 0 || start >= (ssize_t)nInstrs) {
|
||||
return "bogus start point";
|
||||
}
|
||||
size_t i;
|
||||
for (i = start; i < nInstrs; i++) {
|
||||
PfxInstr pxi = (*pfxInstrs)[i];
|
||||
if (pxi.mOpcode == PX_End)
|
||||
break;
|
||||
if (pxi.mOpcode == PX_DwReg &&
|
||||
!registerIsTracked((DW_REG_NUMBER)pxi.mOperand)) {
|
||||
return "uses untracked reg";
|
||||
}
|
||||
}
|
||||
return nullptr; // success
|
||||
}
|
||||
|
||||
|
||||
Summariser::Summariser(SecMap* aSecMap, uintptr_t aTextBias,
|
||||
void(*aLog)(const char*))
|
||||
: mSecMap(aSecMap)
|
||||
, mTextBias(aTextBias)
|
||||
, mLog(aLog)
|
||||
{
|
||||
mCurrAddr = 0;
|
||||
mMax1Addr = 0; // Gives an empty range.
|
||||
|
||||
// Initialise the running RuleSet to "haven't got a clue" status.
|
||||
new (&mCurrRules) RuleSet();
|
||||
}
|
||||
|
||||
void
|
||||
Summariser::Entry(uintptr_t aAddress, uintptr_t aLength)
|
||||
{
|
||||
aAddress += mTextBias;
|
||||
if (DEBUG_SUMMARISER) {
|
||||
char buf[100];
|
||||
SprintfLiteral(buf,
|
||||
"LUL Entry(%llx, %llu)\n",
|
||||
(unsigned long long int)aAddress,
|
||||
(unsigned long long int)aLength);
|
||||
mLog(buf);
|
||||
}
|
||||
// This throws away any previous summary, that is, assumes
|
||||
// that the previous summary, if any, has been properly finished
|
||||
// by a call to End().
|
||||
mCurrAddr = aAddress;
|
||||
mMax1Addr = aAddress + aLength;
|
||||
new (&mCurrRules) RuleSet();
|
||||
}
|
||||
|
||||
void
|
||||
Summariser::Rule(uintptr_t aAddress, int aNewReg,
|
||||
LExprHow how, int16_t oldReg, int64_t offset)
|
||||
{
|
||||
aAddress += mTextBias;
|
||||
if (DEBUG_SUMMARISER) {
|
||||
char buf[100];
|
||||
if (how == NODEREF || how == DEREF) {
|
||||
bool deref = how == DEREF;
|
||||
SprintfLiteral(buf,
|
||||
"LUL 0x%llx old-r%d = %sr%d + %lld%s\n",
|
||||
(unsigned long long int)aAddress, aNewReg,
|
||||
deref ? "*(" : "", (int)oldReg, (long long int)offset,
|
||||
deref ? ")" : "");
|
||||
} else if (how == PFXEXPR) {
|
||||
SprintfLiteral(buf,
|
||||
"LUL 0x%llx old-r%d = pfx-expr-at %lld\n",
|
||||
(unsigned long long int)aAddress, aNewReg,
|
||||
(long long int)offset);
|
||||
} else {
|
||||
SprintfLiteral(buf,
|
||||
"LUL 0x%llx old-r%d = (invalid LExpr!)\n",
|
||||
(unsigned long long int)aAddress, aNewReg);
|
||||
}
|
||||
mLog(buf);
|
||||
}
|
||||
|
||||
if (mCurrAddr < aAddress) {
|
||||
// Flush the existing summary first.
|
||||
mCurrRules.mAddr = mCurrAddr;
|
||||
mCurrRules.mLen = aAddress - mCurrAddr;
|
||||
mSecMap->AddRuleSet(&mCurrRules);
|
||||
if (DEBUG_SUMMARISER) {
|
||||
mLog("LUL "); mCurrRules.Print(mLog);
|
||||
mLog("\n");
|
||||
}
|
||||
mCurrAddr = aAddress;
|
||||
}
|
||||
|
||||
// If for some reason summarisation fails, either or both of these
|
||||
// become non-null and point at constant text describing the
|
||||
// problem. Using two rather than just one avoids complications of
|
||||
// having to concatenate two strings to produce a complete error message.
|
||||
const char* reason1 = nullptr;
|
||||
const char* reason2 = nullptr;
|
||||
|
||||
// |offset| needs to be a 32 bit value that sign extends to 64 bits
|
||||
// on a 64 bit target. We will need to incorporate |offset| into
|
||||
// any LExpr made here. So we may as well check it right now.
|
||||
if (!fitsIn32Bits(offset)) {
|
||||
reason1 = "offset not in signed 32-bit range";
|
||||
goto cant_summarise;
|
||||
}
|
||||
|
||||
// FIXME: factor out common parts of the arch-dependent summarisers.
|
||||
|
||||
#if defined(LUL_ARCH_arm)
|
||||
|
||||
// ----------------- arm ----------------- //
|
||||
|
||||
// Now, can we add the rule to our summary? This depends on whether
|
||||
// the registers and the overall expression are representable. This
|
||||
// is the heart of the summarisation process.
|
||||
switch (aNewReg) {
|
||||
|
||||
case DW_REG_CFA:
|
||||
// This is a rule that defines the CFA. The only forms we
|
||||
// choose to represent are: r7/11/12/13 + offset. The offset
|
||||
// must fit into 32 bits since 'uintptr_t' is 32 bit on ARM,
|
||||
// hence there is no need to check it for overflow.
|
||||
if (how != NODEREF) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |how|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
switch (oldReg) {
|
||||
case DW_REG_ARM_R7: case DW_REG_ARM_R11:
|
||||
case DW_REG_ARM_R12: case DW_REG_ARM_R13:
|
||||
break;
|
||||
default:
|
||||
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
|
||||
break;
|
||||
|
||||
case DW_REG_ARM_R7: case DW_REG_ARM_R11: case DW_REG_ARM_R12:
|
||||
case DW_REG_ARM_R13: case DW_REG_ARM_R14: case DW_REG_ARM_R15: {
|
||||
// This is a new rule for R7, R11, R12, R13 (SP), R14 (LR) or
|
||||
// R15 (the return address).
|
||||
switch (how) {
|
||||
case NODEREF: case DEREF:
|
||||
// Check the old register is one we're tracking.
|
||||
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
|
||||
oldReg != DW_REG_CFA) {
|
||||
reason1 = "rule for R7/11/12/13/14/15: uses untracked reg";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
case PFXEXPR: {
|
||||
// Check that the prefix expression only mentions tracked registers.
|
||||
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
|
||||
reason2 = checkPfxExpr(pfxInstrs, offset);
|
||||
if (reason2) {
|
||||
reason1 = "rule for R7/11/12/13/14/15: ";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto cant_summarise;
|
||||
}
|
||||
LExpr expr = LExpr(how, oldReg, offset);
|
||||
switch (aNewReg) {
|
||||
case DW_REG_ARM_R7: mCurrRules.mR7expr = expr; break;
|
||||
case DW_REG_ARM_R11: mCurrRules.mR11expr = expr; break;
|
||||
case DW_REG_ARM_R12: mCurrRules.mR12expr = expr; break;
|
||||
case DW_REG_ARM_R13: mCurrRules.mR13expr = expr; break;
|
||||
case DW_REG_ARM_R14: mCurrRules.mR14expr = expr; break;
|
||||
case DW_REG_ARM_R15: mCurrRules.mR15expr = expr; break;
|
||||
default: MOZ_ASSERT(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Leave |reason1| and |reason2| unset here. This program point
|
||||
// is reached so often that it causes a flood of "Can't
|
||||
// summarise" messages. In any case, we don't really care about
|
||||
// the fact that this summary would produce a new value for a
|
||||
// register that we're not tracking. We do on the other hand
|
||||
// care if the summary's expression *uses* a register that we're
|
||||
// not tracking. But in that case one of the above failures
|
||||
// should tell us which.
|
||||
goto cant_summarise;
|
||||
}
|
||||
|
||||
// Mark callee-saved registers (r4 .. r11) as unchanged, if there is
|
||||
// no other information about them. FIXME: do this just once, at
|
||||
// the point where the ruleset is committed.
|
||||
if (mCurrRules.mR7expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR7expr = LExpr(NODEREF, DW_REG_ARM_R7, 0);
|
||||
}
|
||||
if (mCurrRules.mR11expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR11expr = LExpr(NODEREF, DW_REG_ARM_R11, 0);
|
||||
}
|
||||
if (mCurrRules.mR12expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR12expr = LExpr(NODEREF, DW_REG_ARM_R12, 0);
|
||||
}
|
||||
|
||||
// The old r13 (SP) value before the call is always the same as the
|
||||
// CFA.
|
||||
mCurrRules.mR13expr = LExpr(NODEREF, DW_REG_CFA, 0);
|
||||
|
||||
// If there's no information about R15 (the return address), say
|
||||
// it's a copy of R14 (the link register).
|
||||
if (mCurrRules.mR15expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR15expr = LExpr(NODEREF, DW_REG_ARM_R14, 0);
|
||||
}
|
||||
|
||||
#elif defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86)
|
||||
|
||||
// ---------------- x64/x86 ---------------- //
|
||||
|
||||
// Now, can we add the rule to our summary? This depends on whether
|
||||
// the registers and the overall expression are representable. This
|
||||
// is the heart of the summarisation process.
|
||||
switch (aNewReg) {
|
||||
|
||||
case DW_REG_CFA:
|
||||
// This is a rule that defines the CFA. The only forms we can
|
||||
// represent are: = SP+offset or = FP+offset.
|
||||
if (how != NODEREF) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |how|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
if (oldReg != DW_REG_INTEL_XSP && oldReg != DW_REG_INTEL_XBP) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
|
||||
break;
|
||||
|
||||
case DW_REG_INTEL_XSP: case DW_REG_INTEL_XBP: case DW_REG_INTEL_XIP: {
|
||||
// This is a new rule for XSP, XBP or XIP (the return address).
|
||||
switch (how) {
|
||||
case NODEREF: case DEREF:
|
||||
// Check the old register is one we're tracking.
|
||||
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
|
||||
oldReg != DW_REG_CFA) {
|
||||
reason1 = "rule for XSP/XBP/XIP: uses untracked reg";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
case PFXEXPR: {
|
||||
// Check that the prefix expression only mentions tracked registers.
|
||||
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
|
||||
reason2 = checkPfxExpr(pfxInstrs, offset);
|
||||
if (reason2) {
|
||||
reason1 = "rule for XSP/XBP/XIP: ";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto cant_summarise;
|
||||
}
|
||||
LExpr expr = LExpr(how, oldReg, offset);
|
||||
switch (aNewReg) {
|
||||
case DW_REG_INTEL_XBP: mCurrRules.mXbpExpr = expr; break;
|
||||
case DW_REG_INTEL_XSP: mCurrRules.mXspExpr = expr; break;
|
||||
case DW_REG_INTEL_XIP: mCurrRules.mXipExpr = expr; break;
|
||||
default: MOZ_CRASH("impossible value for aNewReg");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Leave |reason1| and |reason2| unset here, for the reasons
|
||||
// explained in the analogous point in the ARM case just above.
|
||||
goto cant_summarise;
|
||||
|
||||
}
|
||||
|
||||
// On Intel, it seems the old SP value before the call is always the
|
||||
// same as the CFA. Therefore, in the absence of any other way to
|
||||
// recover the SP, specify that the CFA should be copied.
|
||||
if (mCurrRules.mXspExpr.mHow == UNKNOWN) {
|
||||
mCurrRules.mXspExpr = LExpr(NODEREF, DW_REG_CFA, 0);
|
||||
}
|
||||
|
||||
// Also, gcc says "Undef" for BP when it is unchanged.
|
||||
if (mCurrRules.mXbpExpr.mHow == UNKNOWN) {
|
||||
mCurrRules.mXbpExpr = LExpr(NODEREF, DW_REG_INTEL_XBP, 0);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
# error "Unsupported arch"
|
||||
#endif
|
||||
|
||||
return;
|
||||
|
||||
cant_summarise:
|
||||
if (reason1 || reason2) {
|
||||
char buf[200];
|
||||
SprintfLiteral(buf, "LUL can't summarise: "
|
||||
"SVMA=0x%llx: %s%s, expr=LExpr(%s,%u,%lld)\n",
|
||||
(unsigned long long int)(aAddress - mTextBias),
|
||||
reason1 ? reason1 : "", reason2 ? reason2 : "",
|
||||
NameOf_LExprHow(how),
|
||||
(unsigned int)oldReg, (long long int)offset);
|
||||
mLog(buf);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Summariser::AddPfxInstr(PfxInstr pfxi)
|
||||
{
|
||||
return mSecMap->AddPfxInstr(pfxi);
|
||||
}
|
||||
|
||||
void
|
||||
Summariser::End()
|
||||
{
|
||||
if (DEBUG_SUMMARISER) {
|
||||
mLog("LUL End\n");
|
||||
}
|
||||
if (mCurrAddr < mMax1Addr) {
|
||||
mCurrRules.mAddr = mCurrAddr;
|
||||
mCurrRules.mLen = mMax1Addr - mCurrAddr;
|
||||
mSecMap->AddRuleSet(&mCurrRules);
|
||||
if (DEBUG_SUMMARISER) {
|
||||
mLog("LUL "); mCurrRules.Print(mLog);
|
||||
mLog("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lul
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LulDwarfSummariser_h
|
||||
#define LulDwarfSummariser_h
|
||||
|
||||
#include "LulMainInt.h"
|
||||
|
||||
namespace lul {
|
||||
|
||||
class Summariser
|
||||
{
|
||||
public:
|
||||
Summariser(SecMap* aSecMap, uintptr_t aTextBias, void(*aLog)(const char*));
|
||||
|
||||
virtual void Entry(uintptr_t aAddress, uintptr_t aLength);
|
||||
virtual void End();
|
||||
|
||||
// Tell the summariser that the value for |aNewReg| at |aAddress| is
|
||||
// recovered using the LExpr that can be constructed using the
|
||||
// components |how|, |oldReg| and |offset|. The summariser will
|
||||
// inspect the components and may reject them for various reasons,
|
||||
// but the hope is that it will find them acceptable and record this
|
||||
// rule permanently.
|
||||
virtual void Rule(uintptr_t aAddress, int aNewReg,
|
||||
LExprHow how, int16_t oldReg, int64_t offset);
|
||||
|
||||
virtual uint32_t AddPfxInstr(PfxInstr pfxi);
|
||||
|
||||
// Send output to the logging sink, for debugging.
|
||||
virtual void Log(const char* str) { mLog(str); }
|
||||
|
||||
private:
|
||||
// The SecMap in which we park the finished summaries (RuleSets) and
|
||||
// also any PfxInstrs derived from Dwarf expressions.
|
||||
SecMap* mSecMap;
|
||||
|
||||
// Running state for the current summary (RuleSet) under construction.
|
||||
RuleSet mCurrRules;
|
||||
|
||||
// The start of the address range to which the RuleSet under
|
||||
// construction applies.
|
||||
uintptr_t mCurrAddr;
|
||||
|
||||
// The highest address, plus one, for which the RuleSet under
|
||||
// construction could possibly apply. If there are no further
|
||||
// incoming events then mCurrRules will eventually be emitted
|
||||
// as-is, for the range mCurrAddr.. mMax1Addr - 1, if that is
|
||||
// nonempty.
|
||||
uintptr_t mMax1Addr;
|
||||
|
||||
// The bias value (to add to the SVMAs, to get AVMAs) to be used
|
||||
// when adding entries into mSecMap.
|
||||
uintptr_t mTextBias;
|
||||
|
||||
// A logging sink, for debugging.
|
||||
void (*mLog)(const char* aFmt);
|
||||
};
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulDwarfSummariser_h
|
||||
|
|
@ -1,915 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2006, 2011, 2012 Google Inc.
|
||||
// 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.
|
||||
|
||||
// Restructured in 2009 by: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
// (derived from)
|
||||
// dump_symbols.cc: implement google_breakpad::WriteSymbolFile:
|
||||
// Find all the debugging info in a file and dump it as a Breakpad symbol file.
|
||||
//
|
||||
// dump_symbols.h: Read debugging information from an ELF file, and write
|
||||
// it out as a Breakpad symbol file.
|
||||
|
||||
// This file is derived from the following files in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/linux/dump_symbols.cc
|
||||
// src/common/linux/elfutils.cc
|
||||
// src/common/linux/file_id.cc
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "LulPlatformMacros.h"
|
||||
#include "LulCommonExt.h"
|
||||
#include "LulDwarfExt.h"
|
||||
#include "LulElfInt.h"
|
||||
#include "LulMainInt.h"
|
||||
|
||||
|
||||
#if defined(LUL_PLAT_arm_android) && !defined(SHT_ARM_EXIDX)
|
||||
// bionic and older glibsc don't define it
|
||||
# define SHT_ARM_EXIDX (SHT_LOPROC + 1)
|
||||
#endif
|
||||
|
||||
|
||||
// This namespace contains helper functions.
|
||||
namespace {
|
||||
|
||||
using lul::DwarfCFIToModule;
|
||||
using lul::FindElfSectionByName;
|
||||
using lul::GetOffset;
|
||||
using lul::IsValidElf;
|
||||
using lul::Module;
|
||||
using lul::UniqueStringUniverse;
|
||||
using lul::scoped_ptr;
|
||||
using lul::Summariser;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::set;
|
||||
|
||||
//
|
||||
// FDWrapper
|
||||
//
|
||||
// Wrapper class to make sure opened file is closed.
|
||||
//
|
||||
class FDWrapper {
|
||||
public:
|
||||
explicit FDWrapper(int fd) :
|
||||
fd_(fd) {}
|
||||
~FDWrapper() {
|
||||
if (fd_ != -1)
|
||||
close(fd_);
|
||||
}
|
||||
int get() {
|
||||
return fd_;
|
||||
}
|
||||
int release() {
|
||||
int fd = fd_;
|
||||
fd_ = -1;
|
||||
return fd;
|
||||
}
|
||||
private:
|
||||
int fd_;
|
||||
};
|
||||
|
||||
//
|
||||
// MmapWrapper
|
||||
//
|
||||
// Wrapper class to make sure mapped regions are unmapped.
|
||||
//
|
||||
class MmapWrapper {
|
||||
public:
|
||||
MmapWrapper() : is_set_(false), base_(NULL), size_(0){}
|
||||
~MmapWrapper() {
|
||||
if (is_set_ && base_ != NULL) {
|
||||
MOZ_ASSERT(size_ > 0);
|
||||
munmap(base_, size_);
|
||||
}
|
||||
}
|
||||
void set(void *mapped_address, size_t mapped_size) {
|
||||
is_set_ = true;
|
||||
base_ = mapped_address;
|
||||
size_ = mapped_size;
|
||||
}
|
||||
void release() {
|
||||
MOZ_ASSERT(is_set_);
|
||||
is_set_ = false;
|
||||
base_ = NULL;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_set_;
|
||||
void *base_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
|
||||
// Set NUM_DW_REGNAMES to be the number of Dwarf register names
|
||||
// appropriate to the machine architecture given in HEADER. Return
|
||||
// true on success, or false if HEADER's machine architecture is not
|
||||
// supported.
|
||||
template<typename ElfClass>
|
||||
bool DwarfCFIRegisterNames(const typename ElfClass::Ehdr* elf_header,
|
||||
unsigned int* num_dw_regnames) {
|
||||
switch (elf_header->e_machine) {
|
||||
case EM_386:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::I386();
|
||||
return true;
|
||||
case EM_ARM:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::ARM();
|
||||
return true;
|
||||
case EM_X86_64:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::X86_64();
|
||||
return true;
|
||||
default:
|
||||
MOZ_ASSERT(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ElfClass>
|
||||
bool LoadDwarfCFI(const string& dwarf_filename,
|
||||
const typename ElfClass::Ehdr* elf_header,
|
||||
const char* section_name,
|
||||
const typename ElfClass::Shdr* section,
|
||||
const bool eh_frame,
|
||||
const typename ElfClass::Shdr* got_section,
|
||||
const typename ElfClass::Shdr* text_section,
|
||||
const bool big_endian,
|
||||
SecMap* smap,
|
||||
uintptr_t text_bias,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
// Find the appropriate set of register names for this file's
|
||||
// architecture.
|
||||
unsigned int num_dw_regs = 0;
|
||||
if (!DwarfCFIRegisterNames<ElfClass>(elf_header, &num_dw_regs)) {
|
||||
fprintf(stderr, "%s: unrecognized ELF machine architecture '%d';"
|
||||
" cannot convert DWARF call frame information\n",
|
||||
dwarf_filename.c_str(), elf_header->e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
const lul::Endianness endianness
|
||||
= big_endian ? lul::ENDIANNESS_BIG : lul::ENDIANNESS_LITTLE;
|
||||
|
||||
// Find the call frame information and its size.
|
||||
const char* cfi =
|
||||
GetOffset<ElfClass, char>(elf_header, section->sh_offset);
|
||||
size_t cfi_size = section->sh_size;
|
||||
|
||||
// Plug together the parser, handler, and their entourages.
|
||||
|
||||
// Here's a summariser, which will receive the output of the
|
||||
// parser, create summaries, and add them to |smap|.
|
||||
Summariser summ(smap, text_bias, log);
|
||||
|
||||
lul::ByteReader reader(endianness);
|
||||
reader.SetAddressSize(ElfClass::kAddrSize);
|
||||
|
||||
DwarfCFIToModule::Reporter module_reporter(log, dwarf_filename, section_name);
|
||||
DwarfCFIToModule handler(num_dw_regs, &module_reporter, &reader, usu, &summ);
|
||||
|
||||
// Provide the base addresses for .eh_frame encoded pointers, if
|
||||
// possible.
|
||||
reader.SetCFIDataBase(section->sh_addr, cfi);
|
||||
if (got_section)
|
||||
reader.SetDataBase(got_section->sh_addr);
|
||||
if (text_section)
|
||||
reader.SetTextBase(text_section->sh_addr);
|
||||
|
||||
lul::CallFrameInfo::Reporter dwarf_reporter(log, dwarf_filename,
|
||||
section_name);
|
||||
lul::CallFrameInfo parser(cfi, cfi_size,
|
||||
&reader, &handler, &dwarf_reporter,
|
||||
eh_frame);
|
||||
parser.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadELF(const string& obj_file, MmapWrapper* map_wrapper,
|
||||
void** elf_header) {
|
||||
int obj_fd = open(obj_file.c_str(), O_RDONLY);
|
||||
if (obj_fd < 0) {
|
||||
fprintf(stderr, "Failed to open ELF file '%s': %s\n",
|
||||
obj_file.c_str(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
FDWrapper obj_fd_wrapper(obj_fd);
|
||||
struct stat st;
|
||||
if (fstat(obj_fd, &st) != 0 && st.st_size <= 0) {
|
||||
fprintf(stderr, "Unable to fstat ELF file '%s': %s\n",
|
||||
obj_file.c_str(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
// Mapping it read-only is good enough. In any case, mapping it
|
||||
// read-write confuses Valgrind's debuginfo acquire/discard
|
||||
// heuristics, making it hard to profile the profiler.
|
||||
void *obj_base = mmap(nullptr, st.st_size,
|
||||
PROT_READ, MAP_PRIVATE, obj_fd, 0);
|
||||
if (obj_base == MAP_FAILED) {
|
||||
fprintf(stderr, "Failed to mmap ELF file '%s': %s\n",
|
||||
obj_file.c_str(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
map_wrapper->set(obj_base, st.st_size);
|
||||
*elf_header = obj_base;
|
||||
if (!IsValidElf(*elf_header)) {
|
||||
fprintf(stderr, "Not a valid ELF file: %s\n", obj_file.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the endianness of ELF_HEADER. If it's invalid, return false.
|
||||
template<typename ElfClass>
|
||||
bool ElfEndianness(const typename ElfClass::Ehdr* elf_header,
|
||||
bool* big_endian) {
|
||||
if (elf_header->e_ident[EI_DATA] == ELFDATA2LSB) {
|
||||
*big_endian = false;
|
||||
return true;
|
||||
}
|
||||
if (elf_header->e_ident[EI_DATA] == ELFDATA2MSB) {
|
||||
*big_endian = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
fprintf(stderr, "bad data encoding in ELF header: %d\n",
|
||||
elf_header->e_ident[EI_DATA]);
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// LoadSymbolsInfo
|
||||
//
|
||||
// Holds the state between the two calls to LoadSymbols() in case it's necessary
|
||||
// to follow the .gnu_debuglink section and load debug information from a
|
||||
// different file.
|
||||
//
|
||||
template<typename ElfClass>
|
||||
class LoadSymbolsInfo {
|
||||
public:
|
||||
typedef typename ElfClass::Addr Addr;
|
||||
|
||||
explicit LoadSymbolsInfo(const vector<string>& dbg_dirs) :
|
||||
debug_dirs_(dbg_dirs),
|
||||
has_loading_addr_(false) {}
|
||||
|
||||
// Keeps track of which sections have been loaded so sections don't
|
||||
// accidentally get loaded twice from two different files.
|
||||
void LoadedSection(const string §ion) {
|
||||
if (loaded_sections_.count(section) == 0) {
|
||||
loaded_sections_.insert(section);
|
||||
} else {
|
||||
fprintf(stderr, "Section %s has already been loaded.\n",
|
||||
section.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
string debuglink_file() const {
|
||||
return debuglink_file_;
|
||||
}
|
||||
|
||||
private:
|
||||
const vector<string>& debug_dirs_; // Directories in which to
|
||||
// search for the debug ELF file.
|
||||
|
||||
string debuglink_file_; // Full path to the debug ELF file.
|
||||
|
||||
bool has_loading_addr_; // Indicate if LOADING_ADDR_ is valid.
|
||||
|
||||
set<string> loaded_sections_; // Tracks the Loaded ELF sections
|
||||
// between calls to LoadSymbols().
|
||||
};
|
||||
|
||||
// Find the preferred loading address of the binary.
|
||||
template<typename ElfClass>
|
||||
typename ElfClass::Addr GetLoadingAddress(
|
||||
const typename ElfClass::Phdr* program_headers,
|
||||
int nheader) {
|
||||
typedef typename ElfClass::Phdr Phdr;
|
||||
|
||||
// For non-PIC executables (e_type == ET_EXEC), the load address is
|
||||
// the start address of the first PT_LOAD segment. (ELF requires
|
||||
// the segments to be sorted by load address.) For PIC executables
|
||||
// and dynamic libraries (e_type == ET_DYN), this address will
|
||||
// normally be zero.
|
||||
for (int i = 0; i < nheader; ++i) {
|
||||
const Phdr& header = program_headers[i];
|
||||
if (header.p_type == PT_LOAD)
|
||||
return header.p_vaddr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename ElfClass>
|
||||
bool LoadSymbols(const string& obj_file,
|
||||
const bool big_endian,
|
||||
const typename ElfClass::Ehdr* elf_header,
|
||||
const bool read_gnu_debug_link,
|
||||
LoadSymbolsInfo<ElfClass>* info,
|
||||
SecMap* smap,
|
||||
void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
typedef typename ElfClass::Phdr Phdr;
|
||||
typedef typename ElfClass::Shdr Shdr;
|
||||
|
||||
char buf[500];
|
||||
SprintfLiteral(buf, "LoadSymbols: BEGIN %s\n", obj_file.c_str());
|
||||
buf[sizeof(buf)-1] = 0;
|
||||
log(buf);
|
||||
|
||||
// This is how the text bias is calculated.
|
||||
// BEGIN CALCULATE BIAS
|
||||
uintptr_t loading_addr = GetLoadingAddress<ElfClass>(
|
||||
GetOffset<ElfClass, Phdr>(elf_header, elf_header->e_phoff),
|
||||
elf_header->e_phnum);
|
||||
uintptr_t text_bias = ((uintptr_t)rx_avma) - loading_addr;
|
||||
SprintfLiteral(buf,
|
||||
"LoadSymbols: rx_avma=%llx, text_bias=%llx",
|
||||
(unsigned long long int)(uintptr_t)rx_avma,
|
||||
(unsigned long long int)text_bias);
|
||||
buf[sizeof(buf)-1] = 0;
|
||||
log(buf);
|
||||
// END CALCULATE BIAS
|
||||
|
||||
const Shdr* sections =
|
||||
GetOffset<ElfClass, Shdr>(elf_header, elf_header->e_shoff);
|
||||
const Shdr* section_names = sections + elf_header->e_shstrndx;
|
||||
const char* names =
|
||||
GetOffset<ElfClass, char>(elf_header, section_names->sh_offset);
|
||||
const char *names_end = names + section_names->sh_size;
|
||||
bool found_usable_info = false;
|
||||
|
||||
// Dwarf Call Frame Information (CFI) is actually independent from
|
||||
// the other DWARF debugging information, and can be used alone.
|
||||
const Shdr* dwarf_cfi_section =
|
||||
FindElfSectionByName<ElfClass>(".debug_frame", SHT_PROGBITS,
|
||||
sections, names, names_end,
|
||||
elf_header->e_shnum);
|
||||
if (dwarf_cfi_section) {
|
||||
// Ignore the return value of this function; even without call frame
|
||||
// information, the other debugging information could be perfectly
|
||||
// useful.
|
||||
info->LoadedSection(".debug_frame");
|
||||
bool result =
|
||||
LoadDwarfCFI<ElfClass>(obj_file, elf_header, ".debug_frame",
|
||||
dwarf_cfi_section, false, 0, 0, big_endian,
|
||||
smap, text_bias, usu, log);
|
||||
found_usable_info = found_usable_info || result;
|
||||
if (result)
|
||||
log("LoadSymbols: read CFI from .debug_frame");
|
||||
}
|
||||
|
||||
// Linux C++ exception handling information can also provide
|
||||
// unwinding data.
|
||||
const Shdr* eh_frame_section =
|
||||
FindElfSectionByName<ElfClass>(".eh_frame", SHT_PROGBITS,
|
||||
sections, names, names_end,
|
||||
elf_header->e_shnum);
|
||||
if (eh_frame_section) {
|
||||
// Pointers in .eh_frame data may be relative to the base addresses of
|
||||
// certain sections. Provide those sections if present.
|
||||
const Shdr* got_section =
|
||||
FindElfSectionByName<ElfClass>(".got", SHT_PROGBITS,
|
||||
sections, names, names_end,
|
||||
elf_header->e_shnum);
|
||||
const Shdr* text_section =
|
||||
FindElfSectionByName<ElfClass>(".text", SHT_PROGBITS,
|
||||
sections, names, names_end,
|
||||
elf_header->e_shnum);
|
||||
info->LoadedSection(".eh_frame");
|
||||
// As above, ignore the return value of this function.
|
||||
bool result =
|
||||
LoadDwarfCFI<ElfClass>(obj_file, elf_header, ".eh_frame",
|
||||
eh_frame_section, true,
|
||||
got_section, text_section, big_endian,
|
||||
smap, text_bias, usu, log);
|
||||
found_usable_info = found_usable_info || result;
|
||||
if (result)
|
||||
log("LoadSymbols: read CFI from .eh_frame");
|
||||
}
|
||||
|
||||
SprintfLiteral(buf, "LoadSymbols: END %s\n", obj_file.c_str());
|
||||
buf[sizeof(buf)-1] = 0;
|
||||
log(buf);
|
||||
|
||||
return found_usable_info;
|
||||
}
|
||||
|
||||
// Return the breakpad symbol file identifier for the architecture of
|
||||
// ELF_HEADER.
|
||||
template<typename ElfClass>
|
||||
const char* ElfArchitecture(const typename ElfClass::Ehdr* elf_header) {
|
||||
typedef typename ElfClass::Half Half;
|
||||
Half arch = elf_header->e_machine;
|
||||
switch (arch) {
|
||||
case EM_386: return "x86";
|
||||
case EM_ARM: return "arm";
|
||||
case EM_MIPS: return "mips";
|
||||
case EM_PPC64: return "ppc64";
|
||||
case EM_PPC: return "ppc";
|
||||
case EM_S390: return "s390";
|
||||
case EM_SPARC: return "sparc";
|
||||
case EM_SPARCV9: return "sparcv9";
|
||||
case EM_X86_64: return "x86_64";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Format the Elf file identifier in IDENTIFIER as a UUID with the
|
||||
// dashes removed.
|
||||
string FormatIdentifier(unsigned char identifier[16]) {
|
||||
char identifier_str[40];
|
||||
lul::FileID::ConvertIdentifierToString(
|
||||
identifier,
|
||||
identifier_str,
|
||||
sizeof(identifier_str));
|
||||
string id_no_dash;
|
||||
for (int i = 0; identifier_str[i] != '\0'; ++i)
|
||||
if (identifier_str[i] != '-')
|
||||
id_no_dash += identifier_str[i];
|
||||
// Add an extra "0" by the end. PDB files on Windows have an 'age'
|
||||
// number appended to the end of the file identifier; this isn't
|
||||
// really used or necessary on other platforms, but be consistent.
|
||||
id_no_dash += '0';
|
||||
return id_no_dash;
|
||||
}
|
||||
|
||||
// Return the non-directory portion of FILENAME: the portion after the
|
||||
// last slash, or the whole filename if there are no slashes.
|
||||
string BaseFileName(const string &filename) {
|
||||
// Lots of copies! basename's behavior is less than ideal.
|
||||
char *c_filename = strdup(filename.c_str());
|
||||
string base = basename(c_filename);
|
||||
free(c_filename);
|
||||
return base;
|
||||
}
|
||||
|
||||
template<typename ElfClass>
|
||||
bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header,
|
||||
const string& obj_filename,
|
||||
const vector<string>& debug_dirs,
|
||||
SecMap* smap, void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
typedef typename ElfClass::Ehdr Ehdr;
|
||||
|
||||
unsigned char identifier[16];
|
||||
if (!lul
|
||||
::FileID::ElfFileIdentifierFromMappedFile(elf_header, identifier)) {
|
||||
fprintf(stderr, "%s: unable to generate file identifier\n",
|
||||
obj_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *architecture = ElfArchitecture<ElfClass>(elf_header);
|
||||
if (!architecture) {
|
||||
fprintf(stderr, "%s: unrecognized ELF machine architecture: %d\n",
|
||||
obj_filename.c_str(), elf_header->e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Figure out what endianness this file is.
|
||||
bool big_endian;
|
||||
if (!ElfEndianness<ElfClass>(elf_header, &big_endian))
|
||||
return false;
|
||||
|
||||
string name = BaseFileName(obj_filename);
|
||||
string os = "Linux";
|
||||
string id = FormatIdentifier(identifier);
|
||||
|
||||
LoadSymbolsInfo<ElfClass> info(debug_dirs);
|
||||
if (!LoadSymbols<ElfClass>(obj_filename, big_endian, elf_header,
|
||||
!debug_dirs.empty(), &info,
|
||||
smap, rx_avma, rx_size, usu, log)) {
|
||||
const string debuglink_file = info.debuglink_file();
|
||||
if (debuglink_file.empty())
|
||||
return false;
|
||||
|
||||
// Load debuglink ELF file.
|
||||
fprintf(stderr, "Found debugging info in %s\n", debuglink_file.c_str());
|
||||
MmapWrapper debug_map_wrapper;
|
||||
Ehdr* debug_elf_header = NULL;
|
||||
if (!LoadELF(debuglink_file, &debug_map_wrapper,
|
||||
reinterpret_cast<void**>(&debug_elf_header)))
|
||||
return false;
|
||||
// Sanity checks to make sure everything matches up.
|
||||
const char *debug_architecture =
|
||||
ElfArchitecture<ElfClass>(debug_elf_header);
|
||||
if (!debug_architecture) {
|
||||
fprintf(stderr, "%s: unrecognized ELF machine architecture: %d\n",
|
||||
debuglink_file.c_str(), debug_elf_header->e_machine);
|
||||
return false;
|
||||
}
|
||||
if (strcmp(architecture, debug_architecture)) {
|
||||
fprintf(stderr, "%s with ELF machine architecture %s does not match "
|
||||
"%s with ELF architecture %s\n",
|
||||
debuglink_file.c_str(), debug_architecture,
|
||||
obj_filename.c_str(), architecture);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool debug_big_endian;
|
||||
if (!ElfEndianness<ElfClass>(debug_elf_header, &debug_big_endian))
|
||||
return false;
|
||||
if (debug_big_endian != big_endian) {
|
||||
fprintf(stderr, "%s and %s does not match in endianness\n",
|
||||
obj_filename.c_str(), debuglink_file.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadSymbols<ElfClass>(debuglink_file, debug_big_endian,
|
||||
debug_elf_header, false, &info,
|
||||
smap, rx_avma, rx_size, usu, log)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace (anon)
|
||||
|
||||
|
||||
namespace lul {
|
||||
|
||||
bool ReadSymbolDataInternal(const uint8_t* obj_file,
|
||||
const string& obj_filename,
|
||||
const vector<string>& debug_dirs,
|
||||
SecMap* smap, void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
|
||||
if (!IsValidElf(obj_file)) {
|
||||
fprintf(stderr, "Not a valid ELF file: %s\n", obj_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
int elfclass = ElfClass(obj_file);
|
||||
if (elfclass == ELFCLASS32) {
|
||||
return ReadSymbolDataElfClass<ElfClass32>(
|
||||
reinterpret_cast<const Elf32_Ehdr*>(obj_file),
|
||||
obj_filename, debug_dirs, smap, rx_avma, rx_size, usu, log);
|
||||
}
|
||||
if (elfclass == ELFCLASS64) {
|
||||
return ReadSymbolDataElfClass<ElfClass64>(
|
||||
reinterpret_cast<const Elf64_Ehdr*>(obj_file),
|
||||
obj_filename, debug_dirs, smap, rx_avma, rx_size, usu, log);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadSymbolData(const string& obj_file,
|
||||
const vector<string>& debug_dirs,
|
||||
SecMap* smap, void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
MmapWrapper map_wrapper;
|
||||
void* elf_header = NULL;
|
||||
if (!LoadELF(obj_file, &map_wrapper, &elf_header))
|
||||
return false;
|
||||
|
||||
return ReadSymbolDataInternal(reinterpret_cast<uint8_t*>(elf_header),
|
||||
obj_file, debug_dirs,
|
||||
smap, rx_avma, rx_size, usu, log);
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename ElfClass>
|
||||
void FindElfClassSection(const char *elf_base,
|
||||
const char *section_name,
|
||||
typename ElfClass::Word section_type,
|
||||
const void **section_start,
|
||||
int *section_size) {
|
||||
typedef typename ElfClass::Ehdr Ehdr;
|
||||
typedef typename ElfClass::Shdr Shdr;
|
||||
|
||||
MOZ_ASSERT(elf_base);
|
||||
MOZ_ASSERT(section_start);
|
||||
MOZ_ASSERT(section_size);
|
||||
|
||||
MOZ_ASSERT(strncmp(elf_base, ELFMAG, SELFMAG) == 0);
|
||||
|
||||
const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
|
||||
MOZ_ASSERT(elf_header->e_ident[EI_CLASS] == ElfClass::kClass);
|
||||
|
||||
const Shdr* sections =
|
||||
GetOffset<ElfClass,Shdr>(elf_header, elf_header->e_shoff);
|
||||
const Shdr* section_names = sections + elf_header->e_shstrndx;
|
||||
const char* names =
|
||||
GetOffset<ElfClass,char>(elf_header, section_names->sh_offset);
|
||||
const char *names_end = names + section_names->sh_size;
|
||||
|
||||
const Shdr* section =
|
||||
FindElfSectionByName<ElfClass>(section_name, section_type,
|
||||
sections, names, names_end,
|
||||
elf_header->e_shnum);
|
||||
|
||||
if (section != NULL && section->sh_size > 0) {
|
||||
*section_start = elf_base + section->sh_offset;
|
||||
*section_size = section->sh_size;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ElfClass>
|
||||
void FindElfClassSegment(const char *elf_base,
|
||||
typename ElfClass::Word segment_type,
|
||||
const void **segment_start,
|
||||
int *segment_size) {
|
||||
typedef typename ElfClass::Ehdr Ehdr;
|
||||
typedef typename ElfClass::Phdr Phdr;
|
||||
|
||||
MOZ_ASSERT(elf_base);
|
||||
MOZ_ASSERT(segment_start);
|
||||
MOZ_ASSERT(segment_size);
|
||||
|
||||
MOZ_ASSERT(strncmp(elf_base, ELFMAG, SELFMAG) == 0);
|
||||
|
||||
const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
|
||||
MOZ_ASSERT(elf_header->e_ident[EI_CLASS] == ElfClass::kClass);
|
||||
|
||||
const Phdr* phdrs =
|
||||
GetOffset<ElfClass,Phdr>(elf_header, elf_header->e_phoff);
|
||||
|
||||
for (int i = 0; i < elf_header->e_phnum; ++i) {
|
||||
if (phdrs[i].p_type == segment_type) {
|
||||
*segment_start = elf_base + phdrs[i].p_offset;
|
||||
*segment_size = phdrs[i].p_filesz;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace (anon)
|
||||
|
||||
bool IsValidElf(const void* elf_base) {
|
||||
return strncmp(reinterpret_cast<const char*>(elf_base),
|
||||
ELFMAG, SELFMAG) == 0;
|
||||
}
|
||||
|
||||
int ElfClass(const void* elf_base) {
|
||||
const ElfW(Ehdr)* elf_header =
|
||||
reinterpret_cast<const ElfW(Ehdr)*>(elf_base);
|
||||
|
||||
return elf_header->e_ident[EI_CLASS];
|
||||
}
|
||||
|
||||
bool FindElfSection(const void *elf_mapped_base,
|
||||
const char *section_name,
|
||||
uint32_t section_type,
|
||||
const void **section_start,
|
||||
int *section_size,
|
||||
int *elfclass) {
|
||||
MOZ_ASSERT(elf_mapped_base);
|
||||
MOZ_ASSERT(section_start);
|
||||
MOZ_ASSERT(section_size);
|
||||
|
||||
*section_start = NULL;
|
||||
*section_size = 0;
|
||||
|
||||
if (!IsValidElf(elf_mapped_base))
|
||||
return false;
|
||||
|
||||
int cls = ElfClass(elf_mapped_base);
|
||||
if (elfclass) {
|
||||
*elfclass = cls;
|
||||
}
|
||||
|
||||
const char* elf_base =
|
||||
static_cast<const char*>(elf_mapped_base);
|
||||
|
||||
if (cls == ELFCLASS32) {
|
||||
FindElfClassSection<ElfClass32>(elf_base, section_name, section_type,
|
||||
section_start, section_size);
|
||||
return *section_start != NULL;
|
||||
} else if (cls == ELFCLASS64) {
|
||||
FindElfClassSection<ElfClass64>(elf_base, section_name, section_type,
|
||||
section_start, section_size);
|
||||
return *section_start != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindElfSegment(const void *elf_mapped_base,
|
||||
uint32_t segment_type,
|
||||
const void **segment_start,
|
||||
int *segment_size,
|
||||
int *elfclass) {
|
||||
MOZ_ASSERT(elf_mapped_base);
|
||||
MOZ_ASSERT(segment_start);
|
||||
MOZ_ASSERT(segment_size);
|
||||
|
||||
*segment_start = NULL;
|
||||
*segment_size = 0;
|
||||
|
||||
if (!IsValidElf(elf_mapped_base))
|
||||
return false;
|
||||
|
||||
int cls = ElfClass(elf_mapped_base);
|
||||
if (elfclass) {
|
||||
*elfclass = cls;
|
||||
}
|
||||
|
||||
const char* elf_base =
|
||||
static_cast<const char*>(elf_mapped_base);
|
||||
|
||||
if (cls == ELFCLASS32) {
|
||||
FindElfClassSegment<ElfClass32>(elf_base, segment_type,
|
||||
segment_start, segment_size);
|
||||
return *segment_start != NULL;
|
||||
} else if (cls == ELFCLASS64) {
|
||||
FindElfClassSegment<ElfClass64>(elf_base, segment_type,
|
||||
segment_start, segment_size);
|
||||
return *segment_start != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// (derived from)
|
||||
// file_id.cc: Return a unique identifier for a file
|
||||
//
|
||||
// See file_id.h for documentation
|
||||
//
|
||||
|
||||
// ELF note name and desc are 32-bits word padded.
|
||||
#define NOTE_PADDING(a) ((a + 3) & ~3)
|
||||
|
||||
// These functions are also used inside the crashed process, so be safe
|
||||
// and use the syscall/libc wrappers instead of direct syscalls or libc.
|
||||
|
||||
template<typename ElfClass>
|
||||
static bool ElfClassBuildIDNoteIdentifier(const void *section, int length,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
typedef typename ElfClass::Nhdr Nhdr;
|
||||
|
||||
const void* section_end = reinterpret_cast<const char*>(section) + length;
|
||||
const Nhdr* note_header = reinterpret_cast<const Nhdr*>(section);
|
||||
while (reinterpret_cast<const void *>(note_header) < section_end) {
|
||||
if (note_header->n_type == NT_GNU_BUILD_ID)
|
||||
break;
|
||||
note_header = reinterpret_cast<const Nhdr*>(
|
||||
reinterpret_cast<const char*>(note_header) + sizeof(Nhdr) +
|
||||
NOTE_PADDING(note_header->n_namesz) +
|
||||
NOTE_PADDING(note_header->n_descsz));
|
||||
}
|
||||
if (reinterpret_cast<const void *>(note_header) >= section_end ||
|
||||
note_header->n_descsz == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* build_id = reinterpret_cast<const char*>(note_header) +
|
||||
sizeof(Nhdr) + NOTE_PADDING(note_header->n_namesz);
|
||||
// Copy as many bits of the build ID as will fit
|
||||
// into the GUID space.
|
||||
memset(identifier, 0, kMDGUIDSize);
|
||||
memcpy(identifier, build_id,
|
||||
std::min(kMDGUIDSize, (size_t)note_header->n_descsz));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt to locate a .note.gnu.build-id section in an ELF binary
|
||||
// and copy as many bytes of it as will fit into |identifier|.
|
||||
static bool FindElfBuildIDNote(const void *elf_mapped_base,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
void* note_section;
|
||||
int note_size, elfclass;
|
||||
if ((!FindElfSegment(elf_mapped_base, PT_NOTE,
|
||||
(const void**)¬e_section, ¬e_size, &elfclass) ||
|
||||
note_size == 0) &&
|
||||
(!FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE,
|
||||
(const void**)¬e_section, ¬e_size, &elfclass) ||
|
||||
note_size == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elfclass == ELFCLASS32) {
|
||||
return ElfClassBuildIDNoteIdentifier<ElfClass32>(note_section, note_size,
|
||||
identifier);
|
||||
} else if (elfclass == ELFCLASS64) {
|
||||
return ElfClassBuildIDNoteIdentifier<ElfClass64>(note_section, note_size,
|
||||
identifier);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to locate the .text section of an ELF binary and generate
|
||||
// a simple hash by XORing the first page worth of bytes into |identifier|.
|
||||
static bool HashElfTextSection(const void *elf_mapped_base,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
void* text_section;
|
||||
int text_size;
|
||||
if (!FindElfSection(elf_mapped_base, ".text", SHT_PROGBITS,
|
||||
(const void**)&text_section, &text_size, NULL) ||
|
||||
text_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(identifier, 0, kMDGUIDSize);
|
||||
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(text_section);
|
||||
const uint8_t* ptr_end = ptr + std::min(text_size, 4096);
|
||||
while (ptr < ptr_end) {
|
||||
for (unsigned i = 0; i < kMDGUIDSize; i++)
|
||||
identifier[i] ^= ptr[i];
|
||||
ptr += kMDGUIDSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool FileID::ElfFileIdentifierFromMappedFile(const void* base,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
// Look for a build id note first.
|
||||
if (FindElfBuildIDNote(base, identifier))
|
||||
return true;
|
||||
|
||||
// Fall back on hashing the first page of the text section.
|
||||
return HashElfTextSection(base, identifier);
|
||||
}
|
||||
|
||||
// static
|
||||
void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||
char* buffer, int buffer_length) {
|
||||
uint8_t identifier_swapped[kMDGUIDSize];
|
||||
|
||||
// Endian-ness swap to match dump processor expectation.
|
||||
memcpy(identifier_swapped, identifier, kMDGUIDSize);
|
||||
uint32_t* data1 = reinterpret_cast<uint32_t*>(identifier_swapped);
|
||||
*data1 = htonl(*data1);
|
||||
uint16_t* data2 = reinterpret_cast<uint16_t*>(identifier_swapped + 4);
|
||||
*data2 = htons(*data2);
|
||||
uint16_t* data3 = reinterpret_cast<uint16_t*>(identifier_swapped + 6);
|
||||
*data3 = htons(*data3);
|
||||
|
||||
int buffer_idx = 0;
|
||||
for (unsigned int idx = 0;
|
||||
(buffer_idx < buffer_length) && (idx < kMDGUIDSize);
|
||||
++idx) {
|
||||
int hi = (identifier_swapped[idx] >> 4) & 0x0F;
|
||||
int lo = (identifier_swapped[idx]) & 0x0F;
|
||||
|
||||
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
|
||||
buffer[buffer_idx++] = '-';
|
||||
|
||||
buffer[buffer_idx++] = (hi >= 10) ? 'A' + hi - 10 : '0' + hi;
|
||||
buffer[buffer_idx++] = (lo >= 10) ? 'A' + lo - 10 : '0' + lo;
|
||||
}
|
||||
|
||||
// NULL terminate
|
||||
buffer[(buffer_idx < buffer_length) ? buffer_idx : buffer_idx - 1] = 0;
|
||||
}
|
||||
|
||||
} // namespace lul
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,397 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LulMain_h
|
||||
#define LulMain_h
|
||||
|
||||
#include "LulPlatformMacros.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
|
||||
// LUL: A Lightweight Unwind Library.
|
||||
// This file provides the end-user (external) interface for LUL.
|
||||
|
||||
// Some comments about naming in the implementation. These are safe
|
||||
// to ignore if you are merely using LUL, but are important if you
|
||||
// hack on its internals.
|
||||
//
|
||||
// Debuginfo readers in general have tended to use the word "address"
|
||||
// to mean several different things. This sometimes makes them
|
||||
// difficult to understand and maintain. LUL tries hard to avoid
|
||||
// using the word "address" and instead uses the following more
|
||||
// precise terms:
|
||||
//
|
||||
// * SVMA ("Stated Virtual Memory Address"): this is an address of a
|
||||
// symbol (etc) as it is stated in the symbol table, or other
|
||||
// metadata, of an object. Such values are typically small and
|
||||
// start from zero or thereabouts, unless the object has been
|
||||
// prelinked.
|
||||
//
|
||||
// * AVMA ("Actual Virtual Memory Address"): this is the address of a
|
||||
// symbol (etc) in a running process, that is, once the associated
|
||||
// object has been mapped into a process. Such values are typically
|
||||
// much larger than SVMAs, since objects can get mapped arbitrarily
|
||||
// far along the address space.
|
||||
//
|
||||
// * "Bias": the difference between AVMA and SVMA for a given symbol
|
||||
// (specifically, AVMA - SVMA). The bias is always an integral
|
||||
// number of pages. Once we know the bias for a given object's
|
||||
// text section (for example), we can compute the AVMAs of all of
|
||||
// its text symbols by adding the bias to their SVMAs.
|
||||
//
|
||||
// * "Image address": typically, to read debuginfo from an object we
|
||||
// will temporarily mmap in the file so as to read symbol tables
|
||||
// etc. Addresses in this temporary mapping are called "Image
|
||||
// addresses". Note that the temporary mapping is entirely
|
||||
// unrelated to the mappings of the file that the dynamic linker
|
||||
// must perform merely in order to get the program to run. Hence
|
||||
// image addresses are unrelated to either SVMAs or AVMAs.
|
||||
|
||||
|
||||
namespace lul {
|
||||
|
||||
// A machine word plus validity tag.
|
||||
class TaggedUWord {
|
||||
public:
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Construct a valid one.
|
||||
explicit TaggedUWord(uintptr_t w)
|
||||
: mValue(w)
|
||||
, mValid(true)
|
||||
{}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Construct an invalid one.
|
||||
TaggedUWord()
|
||||
: mValue(0)
|
||||
, mValid(false)
|
||||
{}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator+(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() + rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator-(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() - rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator&(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() & rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator|(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() | rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord CmpGEs(TaggedUWord rhs) const {
|
||||
if (Valid() && rhs.Valid()) {
|
||||
intptr_t s1 = (intptr_t)Value();
|
||||
intptr_t s2 = (intptr_t)rhs.Value();
|
||||
return TaggedUWord(s1 >= s2 ? 1 : 0);
|
||||
}
|
||||
return TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator<<(TaggedUWord rhs) const {
|
||||
if (Valid() && rhs.Valid()) {
|
||||
uintptr_t shift = rhs.Value();
|
||||
if (shift < 8 * sizeof(uintptr_t))
|
||||
return TaggedUWord(Value() << shift);
|
||||
}
|
||||
return TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Is equal? Note: non-validity on either side gives non-equality.
|
||||
bool operator==(TaggedUWord other) const {
|
||||
return (mValid && other.Valid()) ? (mValue == other.Value()) : false;
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Is it word-aligned?
|
||||
bool IsAligned() const {
|
||||
return mValid && (mValue & (sizeof(uintptr_t)-1)) == 0;
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
uintptr_t Value() const { return mValue; }
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
bool Valid() const { return mValid; }
|
||||
|
||||
private:
|
||||
uintptr_t mValue;
|
||||
bool mValid;
|
||||
};
|
||||
|
||||
|
||||
// The registers, with validity tags, that will be unwound.
|
||||
|
||||
struct UnwindRegs {
|
||||
#if defined(LUL_ARCH_arm)
|
||||
TaggedUWord r7;
|
||||
TaggedUWord r11;
|
||||
TaggedUWord r12;
|
||||
TaggedUWord r13;
|
||||
TaggedUWord r14;
|
||||
TaggedUWord r15;
|
||||
#elif defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86)
|
||||
TaggedUWord xbp;
|
||||
TaggedUWord xsp;
|
||||
TaggedUWord xip;
|
||||
#else
|
||||
# error "Unknown plat"
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
// The maximum number of bytes in a stack snapshot. This can be
|
||||
// increased if necessary, but larger values cost performance, since a
|
||||
// stack snapshot needs to be copied between sampling and worker
|
||||
// threads for each snapshot. In practice 32k seems to be enough
|
||||
// to get good backtraces.
|
||||
static const size_t N_STACK_BYTES = 32768;
|
||||
|
||||
// The stack chunk image that will be unwound.
|
||||
struct StackImage {
|
||||
// [start_avma, +len) specify the address range in the buffer.
|
||||
// Obviously we require 0 <= len <= N_STACK_BYTES.
|
||||
uintptr_t mStartAvma;
|
||||
size_t mLen;
|
||||
uint8_t mContents[N_STACK_BYTES];
|
||||
};
|
||||
|
||||
|
||||
// Statistics collection for the unwinder.
|
||||
template<typename T>
|
||||
class LULStats {
|
||||
public:
|
||||
LULStats()
|
||||
: mContext(0)
|
||||
, mCFI(0)
|
||||
, mScanned(0)
|
||||
{}
|
||||
|
||||
template <typename S>
|
||||
explicit LULStats(const LULStats<S>& aOther)
|
||||
: mContext(aOther.mContext)
|
||||
, mCFI(aOther.mCFI)
|
||||
, mScanned(aOther.mScanned)
|
||||
{}
|
||||
|
||||
template <typename S>
|
||||
LULStats<T>& operator=(const LULStats<S>& aOther)
|
||||
{
|
||||
mContext = aOther.mContext;
|
||||
mCFI = aOther.mCFI;
|
||||
mScanned = aOther.mScanned;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
uint32_t operator-(const LULStats<S>& aOther) {
|
||||
return (mContext - aOther.mContext) +
|
||||
(mCFI - aOther.mCFI) + (mScanned - aOther.mScanned);
|
||||
}
|
||||
|
||||
T mContext; // Number of context frames
|
||||
T mCFI; // Number of CFI/EXIDX frames
|
||||
T mScanned; // Number of scanned frames
|
||||
};
|
||||
|
||||
|
||||
// The core unwinder library class. Just one of these is needed, and
|
||||
// it can be shared by multiple unwinder threads.
|
||||
//
|
||||
// The library operates in one of two modes.
|
||||
//
|
||||
// * Admin mode. The library is this state after creation. In Admin
|
||||
// mode, no unwinding may be performed. It is however allowable to
|
||||
// perform administrative tasks -- primarily, loading of unwind info
|
||||
// -- in this mode. In particular, it is safe for the library to
|
||||
// perform dynamic memory allocation in this mode. Safe in the
|
||||
// sense that there is no risk of deadlock against unwinding threads
|
||||
// that might -- because of where they have been sampled -- hold the
|
||||
// system's malloc lock.
|
||||
//
|
||||
// * Unwind mode. In this mode, calls to ::Unwind may be made, but
|
||||
// nothing else. ::Unwind guarantees not to make any dynamic memory
|
||||
// requests, so as to guarantee that the calling thread won't
|
||||
// deadlock in the case where it already holds the system's malloc lock.
|
||||
//
|
||||
// The library is created in Admin mode. After debuginfo is loaded,
|
||||
// the caller must switch it into Unwind mode by calling
|
||||
// ::EnableUnwinding. There is no way to switch it back to Admin mode
|
||||
// after that. To safely switch back to Admin mode would require the
|
||||
// caller (or other external agent) to guarantee that there are no
|
||||
// pending ::Unwind calls.
|
||||
|
||||
class PriMap;
|
||||
class SegArray;
|
||||
class UniqueStringUniverse;
|
||||
|
||||
class LUL {
|
||||
public:
|
||||
// Create; supply a logging sink. Sets the object in Admin mode.
|
||||
explicit LUL(void (*aLog)(const char*));
|
||||
|
||||
// Destroy. Caller is responsible for ensuring that no other
|
||||
// threads are in Unwind calls. All resources are freed and all
|
||||
// registered unwinder threads are deregistered. Can be called
|
||||
// either in Admin or Unwind mode.
|
||||
~LUL();
|
||||
|
||||
// Notify the library that unwinding is now allowed and so
|
||||
// admin-mode calls are no longer allowed. The object is initially
|
||||
// created in admin mode. The only possible transition is
|
||||
// admin->unwinding, therefore.
|
||||
void EnableUnwinding();
|
||||
|
||||
// Notify of a new r-x mapping, and load the associated unwind info.
|
||||
// The filename is strdup'd and used for debug printing. If
|
||||
// aMappedImage is NULL, this function will mmap/munmap the file
|
||||
// itself, so as to be able to read the unwind info. If
|
||||
// aMappedImage is non-NULL then it is assumed to point to a
|
||||
// called-supplied and caller-managed mapped image of the file.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyAfterMap(uintptr_t aRXavma, size_t aSize,
|
||||
const char* aFileName, const void* aMappedImage);
|
||||
|
||||
// In rare cases we know an executable area exists but don't know
|
||||
// what the associated file is. This call notifies LUL of such
|
||||
// areas. This is important for correct functioning of stack
|
||||
// scanning and of the x86-{linux,android} special-case
|
||||
// __kernel_syscall function handling.
|
||||
// This must be called only after the code area in
|
||||
// question really has been mapped.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyExecutableArea(uintptr_t aRXavma, size_t aSize);
|
||||
|
||||
// Notify that a mapped area has been unmapped; discard any
|
||||
// associated unwind info. Acquires mRWlock for writing. Note that
|
||||
// to avoid segfaulting the stack-scan unwinder, which inspects code
|
||||
// areas, this must be called before the code area in question is
|
||||
// really unmapped. Note that, unlike NotifyAfterMap(), this
|
||||
// function takes the start and end addresses of the range to be
|
||||
// unmapped, rather than a start and a length parameter. This is so
|
||||
// as to make it possible to notify an unmap for the entire address
|
||||
// space using a single call.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyBeforeUnmap(uintptr_t aAvmaMin, uintptr_t aAvmaMax);
|
||||
|
||||
// Apply NotifyBeforeUnmap to the entire address space. This causes
|
||||
// LUL to discard all unwind and executable-area information for the
|
||||
// entire address space.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyBeforeUnmapAll() {
|
||||
NotifyBeforeUnmap(0, UINTPTR_MAX);
|
||||
}
|
||||
|
||||
// Returns the number of mappings currently registered.
|
||||
// May only be called in Admin mode.
|
||||
size_t CountMappings();
|
||||
|
||||
// Unwind |aStackImg| starting with the context in |aStartRegs|.
|
||||
// Write the number of frames recovered in *aFramesUsed. Put
|
||||
// the PC values in aFramePCs[0 .. *aFramesUsed-1] and
|
||||
// the SP values in aFrameSPs[0 .. *aFramesUsed-1].
|
||||
// |aFramesAvail| is the size of the two output arrays and hence the
|
||||
// largest possible value of *aFramesUsed. PC values are always
|
||||
// valid, and the unwind will stop when the PC becomes invalid, but
|
||||
// the SP values might be invalid, in which case the value zero will
|
||||
// be written in the relevant frameSPs[] slot.
|
||||
//
|
||||
// Unwinding may optionally use stack scanning. The maximum number
|
||||
// of frames that may be recovered by stack scanning is
|
||||
// |aScannedFramesAllowed| and the actual number recovered is
|
||||
// written into *aScannedFramesAcquired. |aScannedFramesAllowed|
|
||||
// must be less than or equal to |aFramesAvail|.
|
||||
//
|
||||
// This function assumes that the SP values increase as it unwinds
|
||||
// away from the innermost frame -- that is, that the stack grows
|
||||
// down. It monitors SP values as it unwinds to check they
|
||||
// decrease, so as to avoid looping on corrupted stacks.
|
||||
//
|
||||
// May only be called in Unwind mode. Multiple threads may unwind
|
||||
// at once. LUL user is responsible for ensuring that no thread makes
|
||||
// any Admin calls whilst in Unwind mode.
|
||||
// MOZ_CRASHes if the calling thread is not registered for unwinding.
|
||||
//
|
||||
// Up to aScannedFramesAllowed stack-scanned frames may be recovered.
|
||||
//
|
||||
// The calling thread must previously have been registered via a call to
|
||||
// RegisterSampledThread.
|
||||
void Unwind(/*OUT*/uintptr_t* aFramePCs,
|
||||
/*OUT*/uintptr_t* aFrameSPs,
|
||||
/*OUT*/size_t* aFramesUsed,
|
||||
/*OUT*/size_t* aScannedFramesAcquired,
|
||||
size_t aFramesAvail,
|
||||
size_t aScannedFramesAllowed,
|
||||
UnwindRegs* aStartRegs, StackImage* aStackImg);
|
||||
|
||||
// The logging sink. Call to send debug strings to the caller-
|
||||
// specified destination. Can only be called by the Admin thread.
|
||||
void (*mLog)(const char*);
|
||||
|
||||
// Statistics relating to unwinding. These have to be atomic since
|
||||
// unwinding can occur on different threads simultaneously.
|
||||
LULStats<mozilla::Atomic<uint32_t>> mStats;
|
||||
|
||||
// Possibly show the statistics. This may not be called from any
|
||||
// registered sampling thread, since it involves I/O.
|
||||
void MaybeShowStats();
|
||||
|
||||
private:
|
||||
// The statistics counters at the point where they were last printed.
|
||||
LULStats<uint32_t> mStatsPrevious;
|
||||
|
||||
// Are we in admin mode? Initially |true| but changes to |false|
|
||||
// once unwinding begins.
|
||||
bool mAdminMode;
|
||||
|
||||
// The thread ID associated with admin mode. This is the only thread
|
||||
// that is allowed do perform non-Unwind calls on this object. Conversely,
|
||||
// no registered Unwinding thread may be the admin thread. This is so
|
||||
// as to clearly partition the one thread that may do dynamic memory
|
||||
// allocation from the threads that are being sampled, since the latter
|
||||
// absolutely may not do dynamic memory allocation.
|
||||
int mAdminThreadId;
|
||||
|
||||
// The top level mapping from code address ranges to postprocessed
|
||||
// unwind info. Basically a sorted array of (addr, len, info)
|
||||
// records. This field is updated by NotifyAfterMap and NotifyBeforeUnmap.
|
||||
PriMap* mPriMap;
|
||||
|
||||
// An auxiliary structure that records which address ranges are
|
||||
// mapped r-x, for the benefit of the stack scanner.
|
||||
SegArray* mSegArray;
|
||||
|
||||
// A UniqueStringUniverse that holds all the strdup'd strings created
|
||||
// whilst reading unwind information. This is included so as to make
|
||||
// it possible to free them in ~LUL.
|
||||
UniqueStringUniverse* mUSU;
|
||||
};
|
||||
|
||||
|
||||
// Run unit tests on an initialised, loaded-up LUL instance, and print
|
||||
// summary results on |aLUL|'s logging sink. Also return the number
|
||||
// of tests run in *aNTests and the number that passed in
|
||||
// *aNTestsPassed.
|
||||
void
|
||||
RunLulUnitTests(/*OUT*/int* aNTests, /*OUT*/int*aNTestsPassed, LUL* aLUL);
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulMain_h
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "LulMain.h"
|
||||
#include "shared-libraries.h"
|
||||
#include "AutoObjectMapper.h"
|
||||
|
||||
// Contains miscellaneous helpers that are used to connect SPS and LUL.
|
||||
|
||||
|
||||
// Find out, in a platform-dependent way, where the code modules got
|
||||
// mapped in the process' virtual address space, and get |aLUL| to
|
||||
// load unwind info for them.
|
||||
void
|
||||
read_procmaps(lul::LUL* aLUL)
|
||||
{
|
||||
MOZ_ASSERT(aLUL->CountMappings() == 0);
|
||||
|
||||
# if defined(SPS_OS_linux) || defined(SPS_OS_android) || defined(SPS_OS_darwin)
|
||||
SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
|
||||
|
||||
for (size_t i = 0; i < info.GetSize(); i++) {
|
||||
const SharedLibrary& lib = info.GetEntry(i);
|
||||
|
||||
# if defined(SPS_OS_android)
|
||||
// We're using faulty.lib. Use a special-case object mapper.
|
||||
AutoObjectMapperFaultyLib mapper(aLUL->mLog);
|
||||
# else
|
||||
// We can use the standard POSIX-based mapper.
|
||||
AutoObjectMapperPOSIX mapper(aLUL->mLog);
|
||||
# endif
|
||||
|
||||
// Ask |mapper| to map the object. Then hand its mapped address
|
||||
// to NotifyAfterMap().
|
||||
void* image = nullptr;
|
||||
size_t size = 0;
|
||||
bool ok = mapper.Map(&image, &size, lib.GetName());
|
||||
if (ok && image && size > 0) {
|
||||
aLUL->NotifyAfterMap(lib.GetStart(), lib.GetEnd()-lib.GetStart(),
|
||||
lib.GetName().c_str(), image);
|
||||
} else if (!ok && lib.GetName() == "") {
|
||||
// The object has no name and (as a consequence) the mapper
|
||||
// failed to map it. This happens on Linux, where
|
||||
// GetInfoForSelf() produces two such mappings: one for the
|
||||
// executable and one for the VDSO. The executable one isn't a
|
||||
// big deal since there's not much interesting code in there,
|
||||
// but the VDSO one is a problem on x86-{linux,android} because
|
||||
// lack of knowledge about the mapped area inhibits LUL's
|
||||
// special __kernel_syscall handling. Hence notify |aLUL| at
|
||||
// least of the mapping, even though it can't read any unwind
|
||||
// information for the area.
|
||||
aLUL->NotifyExecutableArea(lib.GetStart(), lib.GetEnd()-lib.GetStart());
|
||||
}
|
||||
|
||||
// |mapper| goes out of scope at this point and so its destructor
|
||||
// unmaps the object.
|
||||
}
|
||||
|
||||
# else
|
||||
# error "Unknown platform"
|
||||
# endif
|
||||
}
|
||||
|
||||
|
||||
// LUL needs a callback for its logging sink.
|
||||
void
|
||||
logging_sink_for_LUL(const char* str) {
|
||||
// Ignore any trailing \n, since LOG will add one anyway.
|
||||
size_t n = strlen(str);
|
||||
if (n > 0 && str[n-1] == '\n') {
|
||||
char* tmp = strdup(str);
|
||||
tmp[n-1] = 0;
|
||||
LOG(tmp);
|
||||
free(tmp);
|
||||
} else {
|
||||
LOG(str);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_PLATFORM_LINUX_LUL_H
|
||||
#define MOZ_PLATFORM_LINUX_LUL_H
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
// Find out, in a platform-dependent way, where the code modules got
|
||||
// mapped in the process' virtual address space, and get |aLUL| to
|
||||
// load unwind info for them.
|
||||
void
|
||||
read_procmaps(lul::LUL* aLUL);
|
||||
|
||||
// LUL needs a callback for its logging sink.
|
||||
void
|
||||
logging_sink_for_LUL(const char* str);
|
||||
|
||||
// A singleton instance of the library.
|
||||
extern lul::LUL* sLUL;
|
||||
|
||||
#endif /* ndef MOZ_PLATFORM_LINUX_LUL_H */
|
||||
|
|
@ -4,112 +4,6 @@
|
|||
# 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/.
|
||||
|
||||
if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
|
||||
XPIDL_MODULE = 'profiler'
|
||||
XPIDL_SOURCES += [
|
||||
'gecko/nsIProfiler.idl',
|
||||
'gecko/nsIProfileSaveEvent.idl',
|
||||
]
|
||||
EXPORTS += [
|
||||
'public/GeckoProfilerFunc.h',
|
||||
'public/GeckoProfilerImpl.h',
|
||||
'public/ProfilerBacktrace.h',
|
||||
'public/ProfilerMarkers.h',
|
||||
'public/PseudoStack.h',
|
||||
'public/shared-libraries.h',
|
||||
]
|
||||
EXPORTS.mozilla += [
|
||||
'public/ProfileGatherer.h',
|
||||
]
|
||||
EXTRA_JS_MODULES += [
|
||||
'gecko/Profiler.jsm',
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
'core/GeckoSampler.cpp',
|
||||
'core/platform.cpp',
|
||||
'core/ProfileBuffer.cpp',
|
||||
'core/ProfileEntry.cpp',
|
||||
'core/ProfileJSONWriter.cpp',
|
||||
'core/ProfilerBacktrace.cpp',
|
||||
'core/ProfilerMarkers.cpp',
|
||||
'core/StackTop.cpp',
|
||||
'core/SyncProfile.cpp',
|
||||
'core/ThreadInfo.cpp',
|
||||
'core/ThreadProfile.cpp',
|
||||
'gecko/nsProfiler.cpp',
|
||||
'gecko/nsProfilerFactory.cpp',
|
||||
'gecko/nsProfilerStartParams.cpp',
|
||||
'gecko/ProfileGatherer.cpp',
|
||||
'gecko/ProfilerIOInterposeObserver.cpp',
|
||||
'gecko/SaveProfileTask.cpp',
|
||||
'gecko/ThreadResponsiveness.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
|
||||
UNIFIED_SOURCES += [
|
||||
'lul/AutoObjectMapper.cpp',
|
||||
'lul/LulCommon.cpp',
|
||||
'lul/LulDwarf.cpp',
|
||||
'lul/LulDwarfSummariser.cpp',
|
||||
'lul/LulElf.cpp',
|
||||
'lul/LulMain.cpp',
|
||||
'lul/platform-linux-lul.cpp',
|
||||
]
|
||||
# These files cannot be built in unified mode because of name clashes with mozglue headers on Android.
|
||||
SOURCES += [
|
||||
'core/platform-linux.cc',
|
||||
'core/shared-libraries-linux.cc',
|
||||
]
|
||||
SOURCES += [
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/elfutils.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/guid_creator.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/linux_libc_support.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/memory_mapped_file.cc',
|
||||
]
|
||||
if CONFIG['CPU_ARCH'] == 'arm':
|
||||
SOURCES += [
|
||||
'core/EHABIStackWalk.cpp',
|
||||
]
|
||||
elif CONFIG['OS_TARGET'] == 'Darwin':
|
||||
UNIFIED_SOURCES += [
|
||||
'core/platform-macos.cc',
|
||||
'core/shared-libraries-macos.cc',
|
||||
]
|
||||
elif CONFIG['OS_TARGET'] == 'WINNT':
|
||||
SOURCES += [
|
||||
'core/IntelPowerGadget.cpp',
|
||||
'core/platform-win32.cc',
|
||||
'core/shared-libraries-win32.cc',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/docshell/base',
|
||||
'/ipc/chromium/src',
|
||||
'/mozglue/linker',
|
||||
'/toolkit/crashreporter/google-breakpad/src',
|
||||
'/tools/profiler/core/',
|
||||
'/tools/profiler/gecko/',
|
||||
'/xpcom/base',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Android':
|
||||
LOCAL_INCLUDES += [
|
||||
# We need access to Breakpad's getcontext(3) which is suitable for Android
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/android/include',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Android':
|
||||
SOURCES += ['/toolkit/crashreporter/google-breakpad/src/common/android/breakpad_getcontext.S']
|
||||
|
||||
if CONFIG['ANDROID_CPU_ARCH'] == 'armeabi':
|
||||
DEFINES['ARCH_ARMV6'] = True
|
||||
|
||||
if CONFIG['ENABLE_TESTS']:
|
||||
DIRS += ['tests/gtest']
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
IPDL_SOURCES += [
|
||||
'gecko/ProfilerTypes.ipdlh',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -79,8 +79,6 @@ enum TracingMetadata {
|
|||
TRACING_TIMESTAMP
|
||||
};
|
||||
|
||||
#if !defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
|
|
@ -248,12 +246,6 @@ static inline bool profiler_in_privacy_mode() { return false; }
|
|||
static inline void profiler_log(const char *str) {}
|
||||
static inline void profiler_log(const char *fmt, va_list args) {}
|
||||
|
||||
#else
|
||||
|
||||
#include "GeckoProfilerImpl.h"
|
||||
|
||||
#endif
|
||||
|
||||
class MOZ_RAII GeckoProfilerInitRAII {
|
||||
public:
|
||||
explicit GeckoProfilerInitRAII(void* stackTop) {
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef PROFILER_FUNCS_H
|
||||
#define PROFILER_FUNCS_H
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "js/TypeDecls.h"
|
||||
#endif
|
||||
#include "js/ProfilingStack.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include <stdint.h>
|
||||
|
||||
class nsISupports;
|
||||
|
||||
namespace mozilla {
|
||||
class TimeStamp;
|
||||
|
||||
namespace dom {
|
||||
class Promise;
|
||||
} // namespace dom
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
class ProfilerBacktrace;
|
||||
class ProfilerMarkerPayload;
|
||||
|
||||
// Returns a handle to pass on exit. This can check that we are popping the
|
||||
// correct callstack.
|
||||
inline void* mozilla_sampler_call_enter(const char *aInfo, js::ProfileEntry::Category aCategory,
|
||||
void *aFrameAddress = nullptr, bool aCopy = false,
|
||||
uint32_t line = 0);
|
||||
|
||||
inline void mozilla_sampler_call_exit(void* handle);
|
||||
|
||||
void mozilla_sampler_add_marker(const char *aInfo,
|
||||
ProfilerMarkerPayload *aPayload = nullptr);
|
||||
|
||||
void mozilla_sampler_start(int aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount);
|
||||
|
||||
void mozilla_sampler_stop();
|
||||
|
||||
bool mozilla_sampler_is_paused();
|
||||
void mozilla_sampler_pause();
|
||||
void mozilla_sampler_resume();
|
||||
|
||||
ProfilerBacktrace* mozilla_sampler_get_backtrace();
|
||||
void mozilla_sampler_free_backtrace(ProfilerBacktrace* aBacktrace);
|
||||
void mozilla_sampler_get_backtrace_noalloc(char *output, size_t outputSize);
|
||||
|
||||
bool mozilla_sampler_is_active();
|
||||
|
||||
bool mozilla_sampler_feature_active(const char* aName);
|
||||
|
||||
void mozilla_sampler_responsiveness(const mozilla::TimeStamp& time);
|
||||
|
||||
void mozilla_sampler_frame_number(int frameNumber);
|
||||
|
||||
const double* mozilla_sampler_get_responsiveness();
|
||||
|
||||
void mozilla_sampler_save();
|
||||
|
||||
mozilla::UniquePtr<char[]> mozilla_sampler_get_profile(double aSinceTime);
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
JSObject *mozilla_sampler_get_profile_data(JSContext* aCx, double aSinceTime);
|
||||
void mozilla_sampler_get_profile_data_async(double aSinceTime,
|
||||
mozilla::dom::Promise* aPromise);
|
||||
void mozilla_sampler_get_profiler_start_params(int* aEntrySize,
|
||||
double* aInterval,
|
||||
mozilla::Vector<const char*>* aFilters,
|
||||
mozilla::Vector<const char*>* aFeatures);
|
||||
void mozilla_sampler_get_gatherer(nsISupports** aRetVal);
|
||||
#endif
|
||||
|
||||
// Make this function easily callable from a debugger in a build without
|
||||
// debugging information (work around http://llvm.org/bugs/show_bug.cgi?id=22211)
|
||||
extern "C" {
|
||||
void mozilla_sampler_save_profile_to_file(const char* aFilename);
|
||||
}
|
||||
|
||||
const char** mozilla_sampler_get_features();
|
||||
|
||||
void mozilla_sampler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize,
|
||||
uint32_t *aGeneration);
|
||||
|
||||
void mozilla_sampler_init(void* stackTop);
|
||||
|
||||
void mozilla_sampler_shutdown();
|
||||
|
||||
// Lock the profiler. When locked the profiler is (1) stopped,
|
||||
// (2) profile data is cleared, (3) profiler-locked is fired.
|
||||
// This is used to lock down the profiler during private browsing
|
||||
void mozilla_sampler_lock();
|
||||
|
||||
// Unlock the profiler, leaving it stopped and fires profiler-unlocked.
|
||||
void mozilla_sampler_unlock();
|
||||
|
||||
// Register/unregister threads with the profiler
|
||||
bool mozilla_sampler_register_thread(const char* name, void* stackTop);
|
||||
void mozilla_sampler_unregister_thread();
|
||||
|
||||
void mozilla_sampler_sleep_start();
|
||||
void mozilla_sampler_sleep_end();
|
||||
bool mozilla_sampler_is_sleeping();
|
||||
|
||||
double mozilla_sampler_time();
|
||||
double mozilla_sampler_time(const mozilla::TimeStamp& aTime);
|
||||
|
||||
void mozilla_sampler_tracing(const char* aCategory, const char* aInfo,
|
||||
TracingMetadata aMetaData);
|
||||
|
||||
void mozilla_sampler_tracing(const char* aCategory, const char* aInfo,
|
||||
ProfilerBacktrace* aCause,
|
||||
TracingMetadata aMetaData);
|
||||
|
||||
void mozilla_sampler_log(const char *fmt, va_list args);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,515 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
// IWYU pragma: private, include "GeckoProfiler.h"
|
||||
|
||||
#ifndef TOOLS_SPS_SAMPLER_H_
|
||||
#define TOOLS_SPS_SAMPLER_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/GuardObjects.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "nscore.h"
|
||||
#include "nsISupports.h"
|
||||
#endif
|
||||
#include "GeckoProfilerFunc.h"
|
||||
#include "PseudoStack.h"
|
||||
#include "ProfilerBacktrace.h"
|
||||
|
||||
// Make sure that we can use std::min here without the Windows headers messing with us.
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
class GeckoSampler;
|
||||
|
||||
namespace mozilla {
|
||||
class TimeStamp;
|
||||
} // namespace mozilla
|
||||
|
||||
extern MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack;
|
||||
extern MOZ_THREAD_LOCAL(GeckoSampler *) tlsTicker;
|
||||
extern MOZ_THREAD_LOCAL(void *) tlsStackTop;
|
||||
extern bool stack_key_initialized;
|
||||
|
||||
#ifndef SAMPLE_FUNCTION_NAME
|
||||
# ifdef __GNUC__
|
||||
# define SAMPLE_FUNCTION_NAME __FUNCTION__
|
||||
# elif defined(_MSC_VER)
|
||||
# define SAMPLE_FUNCTION_NAME __FUNCTION__
|
||||
# else
|
||||
# define SAMPLE_FUNCTION_NAME __func__ // defined in C99, supported in various C++ compilers. Just raw function name.
|
||||
# endif
|
||||
#endif
|
||||
|
||||
static inline
|
||||
void profiler_init(void* stackTop)
|
||||
{
|
||||
mozilla_sampler_init(stackTop);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_shutdown()
|
||||
{
|
||||
mozilla_sampler_shutdown();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_start(int aProfileEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount)
|
||||
{
|
||||
mozilla_sampler_start(aProfileEntries, aInterval, aFeatures, aFeatureCount, aThreadNameFilters, aFilterCount);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_stop()
|
||||
{
|
||||
mozilla_sampler_stop();
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_is_paused()
|
||||
{
|
||||
return mozilla_sampler_is_paused();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_pause()
|
||||
{
|
||||
mozilla_sampler_pause();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_resume()
|
||||
{
|
||||
mozilla_sampler_resume();
|
||||
}
|
||||
|
||||
static inline
|
||||
ProfilerBacktrace* profiler_get_backtrace()
|
||||
{
|
||||
return mozilla_sampler_get_backtrace();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_free_backtrace(ProfilerBacktrace* aBacktrace)
|
||||
{
|
||||
mozilla_sampler_free_backtrace(aBacktrace);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_backtrace_noalloc(char *output, size_t outputSize)
|
||||
{
|
||||
return mozilla_sampler_get_backtrace_noalloc(output, outputSize);
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_is_active()
|
||||
{
|
||||
return mozilla_sampler_is_active();
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_feature_active(const char* aName)
|
||||
{
|
||||
return mozilla_sampler_feature_active(aName);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_responsiveness(const mozilla::TimeStamp& aTime)
|
||||
{
|
||||
mozilla_sampler_responsiveness(aTime);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_set_frame_number(int frameNumber)
|
||||
{
|
||||
return mozilla_sampler_frame_number(frameNumber);
|
||||
}
|
||||
|
||||
static inline
|
||||
mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0)
|
||||
{
|
||||
return mozilla_sampler_get_profile(aSinceTime);
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
static inline
|
||||
JSObject* profiler_get_profile_jsobject(JSContext* aCx, double aSinceTime = 0)
|
||||
{
|
||||
return mozilla_sampler_get_profile_data(aCx, aSinceTime);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_profile_jsobject_async(double aSinceTime = 0,
|
||||
mozilla::dom::Promise* aPromise = 0)
|
||||
{
|
||||
mozilla_sampler_get_profile_data_async(aSinceTime, aPromise);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_start_params(int* aEntrySize,
|
||||
double* aInterval,
|
||||
mozilla::Vector<const char*>* aFilters,
|
||||
mozilla::Vector<const char*>* aFeatures)
|
||||
{
|
||||
mozilla_sampler_get_profiler_start_params(aEntrySize, aInterval, aFilters, aFeatures);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_gatherer(nsISupports** aRetVal)
|
||||
{
|
||||
mozilla_sampler_get_gatherer(aRetVal);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static inline
|
||||
void profiler_save_profile_to_file(const char* aFilename)
|
||||
{
|
||||
return mozilla_sampler_save_profile_to_file(aFilename);
|
||||
}
|
||||
|
||||
static inline
|
||||
const char** profiler_get_features()
|
||||
{
|
||||
return mozilla_sampler_get_features();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize,
|
||||
uint32_t *aGeneration)
|
||||
{
|
||||
return mozilla_sampler_get_buffer_info(aCurrentPosition, aTotalSize, aGeneration);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_lock()
|
||||
{
|
||||
return mozilla_sampler_lock();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_unlock()
|
||||
{
|
||||
return mozilla_sampler_unlock();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_register_thread(const char* name, void* guessStackTop)
|
||||
{
|
||||
mozilla_sampler_register_thread(name, guessStackTop);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_unregister_thread()
|
||||
{
|
||||
mozilla_sampler_unregister_thread();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_sleep_start()
|
||||
{
|
||||
mozilla_sampler_sleep_start();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_sleep_end()
|
||||
{
|
||||
mozilla_sampler_sleep_end();
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_is_sleeping()
|
||||
{
|
||||
return mozilla_sampler_is_sleeping();
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
static inline
|
||||
void profiler_js_operation_callback()
|
||||
{
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack->jsOperationCallback();
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline
|
||||
double profiler_time()
|
||||
{
|
||||
return mozilla_sampler_time();
|
||||
}
|
||||
|
||||
static inline
|
||||
double profiler_time(const mozilla::TimeStamp& aTime)
|
||||
{
|
||||
return mozilla_sampler_time(aTime);
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_in_privacy_mode()
|
||||
{
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
return false;
|
||||
}
|
||||
return stack->mPrivacyMode;
|
||||
}
|
||||
|
||||
static inline void profiler_tracing(const char* aCategory, const char* aInfo,
|
||||
ProfilerBacktrace* aCause,
|
||||
TracingMetadata aMetaData = TRACING_DEFAULT)
|
||||
{
|
||||
// Don't insert a marker if we're not profiling to avoid
|
||||
// the heap copy (malloc).
|
||||
if (!stack_key_initialized || !profiler_is_active()) {
|
||||
delete aCause;
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla_sampler_tracing(aCategory, aInfo, aCause, aMetaData);
|
||||
}
|
||||
|
||||
static inline void profiler_tracing(const char* aCategory, const char* aInfo,
|
||||
TracingMetadata aMetaData = TRACING_DEFAULT)
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
return;
|
||||
|
||||
// Don't insert a marker if we're not profiling to avoid
|
||||
// the heap copy (malloc).
|
||||
if (!profiler_is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla_sampler_tracing(aCategory, aInfo, aMetaData);
|
||||
}
|
||||
|
||||
#define SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line) id ## line
|
||||
#define SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, line) SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line)
|
||||
#define SAMPLER_APPEND_LINE_NUMBER(id) SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, __LINE__)
|
||||
|
||||
// Uncomment this to turn on systrace or build with
|
||||
// ac_add_options --enable-systace
|
||||
//#define MOZ_USE_SYSTRACE
|
||||
#ifdef MOZ_USE_SYSTRACE
|
||||
#ifndef ATRACE_TAG
|
||||
# define ATRACE_TAG ATRACE_TAG_ALWAYS
|
||||
#endif
|
||||
// We need HAVE_ANDROID_OS to be defined for Trace.h.
|
||||
// If its not set we will set it temporary and remove it.
|
||||
# ifndef HAVE_ANDROID_OS
|
||||
# define HAVE_ANDROID_OS
|
||||
# define REMOVE_HAVE_ANDROID_OS
|
||||
# endif
|
||||
// Android source code will include <cutils/trace.h> before this. There is no
|
||||
// HAVE_ANDROID_OS defined in Firefox OS build at that time. Enabled it globally
|
||||
// will cause other build break. So atrace_begin and atrace_end are not defined.
|
||||
// It will cause a build-break when we include <utils/Trace.h>. Use undef
|
||||
// _LIBS_CUTILS_TRACE_H will force <cutils/trace.h> to define atrace_begin and
|
||||
// atrace_end with defined HAVE_ANDROID_OS again. Then there is no build-break.
|
||||
# undef _LIBS_CUTILS_TRACE_H
|
||||
# include <utils/Trace.h>
|
||||
# define MOZ_PLATFORM_TRACING(name) android::ScopedTrace SAMPLER_APPEND_LINE_NUMBER(scopedTrace)(ATRACE_TAG, name);
|
||||
# ifdef REMOVE_HAVE_ANDROID_OS
|
||||
# undef HAVE_ANDROID_OS
|
||||
# undef REMOVE_HAVE_ANDROID_OS
|
||||
# endif
|
||||
#else
|
||||
# define MOZ_PLATFORM_TRACING(name)
|
||||
#endif
|
||||
|
||||
// we want the class and function name but can't easily get that using preprocessor macros
|
||||
// __func__ doesn't have the class name and __PRETTY_FUNCTION__ has the parameters
|
||||
|
||||
#define PROFILER_LABEL(name_space, info, category) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__)
|
||||
#define PROFILER_LABEL_FUNC(category) MOZ_PLATFORM_TRACING(SAMPLE_FUNCTION_NAME) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(SAMPLE_FUNCTION_NAME, category, __LINE__)
|
||||
#define PROFILER_LABEL_PRINTF(name_space, info, category, ...) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, __VA_ARGS__)
|
||||
|
||||
#define PROFILER_MARKER(info) mozilla_sampler_add_marker(info)
|
||||
#define PROFILER_MARKER_PAYLOAD(info, payload) mozilla_sampler_add_marker(info, payload)
|
||||
#define PROFILER_MAIN_THREAD_MARKER(info) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla_sampler_add_marker(info)
|
||||
|
||||
#define PROFILER_MAIN_THREAD_LABEL(name_space, info, category) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__)
|
||||
#define PROFILER_MAIN_THREAD_LABEL_PRINTF(name_space, info, category, ...) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, __VA_ARGS__)
|
||||
|
||||
|
||||
#if !defined(PLATFORM_LIKELY_MEMORY_CONSTRAINED) && !defined(ARCH_ARMV6)
|
||||
# define PROFILE_DEFAULT_ENTRY 1000000
|
||||
#else
|
||||
# define PROFILE_DEFAULT_ENTRY 100000
|
||||
#endif
|
||||
|
||||
// In the case of profiler_get_backtrace we know that we only need enough space
|
||||
// for a single backtrace.
|
||||
#define GET_BACKTRACE_DEFAULT_ENTRY 1000
|
||||
|
||||
#if defined(PLATFORM_LIKELY_MEMORY_CONSTRAINED)
|
||||
/* A 1ms sampling interval has been shown to be a large perf hit
|
||||
* (10fps) on memory-contrained (low-end) platforms, and additionally
|
||||
* to yield different results from the profiler. Where this is the
|
||||
* important case, b2g, there are also many gecko processes which
|
||||
* magnify these effects. */
|
||||
# define PROFILE_DEFAULT_INTERVAL 10
|
||||
#elif defined(ANDROID)
|
||||
// We use a lower frequency on Android, in order to make things work
|
||||
// more smoothly on phones. This value can be adjusted later with
|
||||
// some libunwind optimizations.
|
||||
// In one sample measurement on Galaxy Nexus, out of about 700 backtraces,
|
||||
// 60 of them took more than 25ms, and the average and standard deviation
|
||||
// were 6.17ms and 9.71ms respectively.
|
||||
|
||||
// For now since we don't support stackwalking let's use 1ms since it's fast
|
||||
// enough.
|
||||
#define PROFILE_DEFAULT_INTERVAL 1
|
||||
#else
|
||||
#define PROFILE_DEFAULT_INTERVAL 1
|
||||
#endif
|
||||
#define PROFILE_DEFAULT_FEATURES NULL
|
||||
#define PROFILE_DEFAULT_FEATURE_COUNT 0
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MOZ_RAII GeckoProfilerTracingRAII {
|
||||
public:
|
||||
GeckoProfilerTracingRAII(const char* aCategory, const char* aInfo,
|
||||
mozilla::UniquePtr<ProfilerBacktrace> aBacktrace
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: mCategory(aCategory)
|
||||
, mInfo(aInfo)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
profiler_tracing(mCategory, mInfo, aBacktrace.release(), TRACING_INTERVAL_START);
|
||||
}
|
||||
|
||||
~GeckoProfilerTracingRAII() {
|
||||
profiler_tracing(mCategory, mInfo, TRACING_INTERVAL_END);
|
||||
}
|
||||
|
||||
protected:
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
const char* mCategory;
|
||||
const char* mInfo;
|
||||
};
|
||||
|
||||
class MOZ_RAII SamplerStackFrameRAII {
|
||||
public:
|
||||
// we only copy the strings at save time, so to take multiple parameters we'd need to copy them then.
|
||||
SamplerStackFrameRAII(const char *aInfo,
|
||||
js::ProfileEntry::Category aCategory, uint32_t line
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
mHandle = mozilla_sampler_call_enter(aInfo, aCategory, this, false, line);
|
||||
}
|
||||
~SamplerStackFrameRAII() {
|
||||
mozilla_sampler_call_exit(mHandle);
|
||||
}
|
||||
private:
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
void* mHandle;
|
||||
};
|
||||
|
||||
static const int SAMPLER_MAX_STRING = 128;
|
||||
class MOZ_RAII SamplerStackFramePrintfRAII {
|
||||
public:
|
||||
// we only copy the strings at save time, so to take multiple parameters we'd need to copy them then.
|
||||
SamplerStackFramePrintfRAII(const char *aInfo,
|
||||
js::ProfileEntry::Category aCategory, uint32_t line, const char *aFormat, ...)
|
||||
: mHandle(nullptr)
|
||||
{
|
||||
if (profiler_is_active() && !profiler_in_privacy_mode()) {
|
||||
va_list args;
|
||||
va_start(args, aFormat);
|
||||
char buff[SAMPLER_MAX_STRING];
|
||||
|
||||
// We have to use seperate printf's because we're using
|
||||
// the vargs.
|
||||
VsprintfLiteral(buff, aFormat, args);
|
||||
SprintfLiteral(mDest, "%s %s", aInfo, buff);
|
||||
|
||||
mHandle = mozilla_sampler_call_enter(mDest, aCategory, this, true, line);
|
||||
va_end(args);
|
||||
} else {
|
||||
mHandle = mozilla_sampler_call_enter(aInfo, aCategory, this, false, line);
|
||||
}
|
||||
}
|
||||
~SamplerStackFramePrintfRAII() {
|
||||
mozilla_sampler_call_exit(mHandle);
|
||||
}
|
||||
private:
|
||||
char mDest[SAMPLER_MAX_STRING];
|
||||
void* mHandle;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
inline PseudoStack* mozilla_get_pseudo_stack(void)
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
return nullptr;
|
||||
return tlsPseudoStack.get();
|
||||
}
|
||||
|
||||
inline void* mozilla_sampler_call_enter(const char *aInfo,
|
||||
js::ProfileEntry::Category aCategory, void *aFrameAddress, bool aCopy, uint32_t line)
|
||||
{
|
||||
// check if we've been initialized to avoid calling pthread_getspecific
|
||||
// with a null tlsStack which will return undefined results.
|
||||
if (!stack_key_initialized)
|
||||
return nullptr;
|
||||
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
// we can't infer whether 'stack' has been initialized
|
||||
// based on the value of stack_key_intiailized because
|
||||
// 'stack' is only intialized when a thread is being
|
||||
// profiled.
|
||||
if (!stack) {
|
||||
return stack;
|
||||
}
|
||||
stack->push(aInfo, aCategory, aFrameAddress, aCopy, line);
|
||||
|
||||
// The handle is meant to support future changes
|
||||
// but for now it is simply use to save a call to
|
||||
// pthread_getspecific on exit. It also supports the
|
||||
// case where the sampler is initialized between
|
||||
// enter and exit.
|
||||
return stack;
|
||||
}
|
||||
|
||||
inline void mozilla_sampler_call_exit(void *aHandle)
|
||||
{
|
||||
if (!aHandle)
|
||||
return;
|
||||
|
||||
PseudoStack *stack = (PseudoStack*)aHandle;
|
||||
stack->popAndMaybeDelete();
|
||||
}
|
||||
|
||||
void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload);
|
||||
|
||||
static inline
|
||||
void profiler_log(const char *str)
|
||||
{
|
||||
profiler_tracing("log", str, TRACING_EVENT);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_log(const char *fmt, va_list args)
|
||||
{
|
||||
mozilla_sampler_log(fmt, args);
|
||||
}
|
||||
|
||||
#endif /* ndef TOOLS_SPS_SAMPLER_H_ */
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_PROFILE_GATHERER_H
|
||||
#define MOZ_PROFILE_GATHERER_H
|
||||
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
class GeckoSampler;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ProfileGatherer final : public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
explicit ProfileGatherer(GeckoSampler* aTicker);
|
||||
void WillGatherOOPProfile();
|
||||
void GatheredOOPProfile();
|
||||
void Start(double aSinceTime, mozilla::dom::Promise* aPromise);
|
||||
void Cancel();
|
||||
void OOPExitProfile(const nsCString& aProfile);
|
||||
|
||||
private:
|
||||
~ProfileGatherer() {};
|
||||
void Finish();
|
||||
void Reset();
|
||||
|
||||
nsTArray<nsCString> mExitProfiles;
|
||||
RefPtr<mozilla::dom::Promise> mPromise;
|
||||
GeckoSampler* mTicker;
|
||||
double mSinceTime;
|
||||
uint32_t mPendingProfiles;
|
||||
bool mGathering;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef __PROFILER_BACKTRACE_H
|
||||
#define __PROFILER_BACKTRACE_H
|
||||
|
||||
class SyncProfile;
|
||||
class SpliceableJSONWriter;
|
||||
class UniqueStacks;
|
||||
|
||||
class ProfilerBacktrace
|
||||
{
|
||||
public:
|
||||
explicit ProfilerBacktrace(SyncProfile* aProfile);
|
||||
~ProfilerBacktrace();
|
||||
|
||||
// ProfilerBacktraces' stacks are deduplicated in the context of the
|
||||
// profile that contains the backtrace as a marker payload.
|
||||
//
|
||||
// That is, markers that contain backtraces should not need their own stack,
|
||||
// frame, and string tables. They should instead reuse their parent
|
||||
// profile's tables.
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks);
|
||||
|
||||
private:
|
||||
ProfilerBacktrace(const ProfilerBacktrace&);
|
||||
ProfilerBacktrace& operator=(const ProfilerBacktrace&);
|
||||
|
||||
SyncProfile* mProfile;
|
||||
};
|
||||
|
||||
#endif // __PROFILER_BACKTRACE_H
|
||||
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef PROFILER_MARKERS_H
|
||||
#define PROFILER_MARKERS_H
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
class Layer;
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
class SpliceableJSONWriter;
|
||||
class UniqueStacks;
|
||||
|
||||
/**
|
||||
* This is an abstract object that can be implied to supply
|
||||
* data to be attached with a profiler marker. Most data inserted
|
||||
* into a profile is stored in a circular buffer. This buffer
|
||||
* typically wraps around and overwrites most entries. Because
|
||||
* of this, this structure is designed to defer the work of
|
||||
* prepare the payload only when 'preparePayload' is called.
|
||||
*
|
||||
* Note when implementing that this object is typically constructed
|
||||
* on a particular thread but 'preparePayload' and the destructor
|
||||
* is called from the main thread.
|
||||
*/
|
||||
class ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* ProfilerMarkerPayload takes ownership of aStack
|
||||
*/
|
||||
explicit ProfilerMarkerPayload(ProfilerBacktrace* aStack = nullptr);
|
||||
ProfilerMarkerPayload(const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack = nullptr);
|
||||
|
||||
/**
|
||||
* Called from the main thread
|
||||
*/
|
||||
virtual ~ProfilerMarkerPayload();
|
||||
|
||||
/**
|
||||
* Called from the main thread
|
||||
*/
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) = 0;
|
||||
|
||||
mozilla::TimeStamp GetStartTime() const { return mStartTime; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called from the main thread
|
||||
*/
|
||||
void streamCommonProps(const char* aMarkerType, SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
|
||||
void SetStack(ProfilerBacktrace* aStack) { mStack = aStack; }
|
||||
|
||||
private:
|
||||
mozilla::TimeStamp mStartTime;
|
||||
mozilla::TimeStamp mEndTime;
|
||||
ProfilerBacktrace* mStack;
|
||||
};
|
||||
|
||||
class ProfilerMarkerTracing : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData);
|
||||
ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData, ProfilerBacktrace* aCause);
|
||||
|
||||
const char *GetCategory() const { return mCategory; }
|
||||
TracingMetadata GetMetaData() const { return mMetaData; }
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
const char *mCategory;
|
||||
TracingMetadata mMetaData;
|
||||
};
|
||||
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "gfxASurface.h"
|
||||
class ProfilerMarkerImagePayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
explicit ProfilerMarkerImagePayload(gfxASurface *aImg);
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
RefPtr<gfxASurface> mImg;
|
||||
};
|
||||
|
||||
class IOMarkerPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
IOMarkerPayload(const char* aSource, const char* aFilename, const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack);
|
||||
~IOMarkerPayload();
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
const char* mSource;
|
||||
char* mFilename;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the translation applied to a 2d layer so we can
|
||||
* track the layer position at each frame.
|
||||
*/
|
||||
class LayerTranslationPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
LayerTranslationPayload(mozilla::layers::Layer* aLayer,
|
||||
mozilla::gfx::Point aPoint);
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::layers::Layer* mLayer;
|
||||
mozilla::gfx::Point mPoint;
|
||||
};
|
||||
|
||||
#include "Units.h" // For ScreenIntPoint
|
||||
|
||||
/**
|
||||
* Tracks when touch events are processed by gecko, not when
|
||||
* the touch actually occured in gonk/android.
|
||||
*/
|
||||
class TouchDataPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
explicit TouchDataPayload(const mozilla::ScreenIntPoint& aPoint);
|
||||
virtual ~TouchDataPayload() {}
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::ScreenIntPoint mPoint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tracks when a vsync occurs according to the HardwareComposer.
|
||||
*/
|
||||
class VsyncPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
explicit VsyncPayload(mozilla::TimeStamp aVsyncTimestamp);
|
||||
virtual ~VsyncPayload() {}
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::TimeStamp mVsyncTimestamp;
|
||||
};
|
||||
|
||||
class GPUMarkerPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
GPUMarkerPayload(const mozilla::TimeStamp& aCpuTimeStart,
|
||||
const mozilla::TimeStamp& aCpuTimeEnd,
|
||||
uint64_t aGpuTimeStart,
|
||||
uint64_t aGpuTimeEnd);
|
||||
~GPUMarkerPayload() {}
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::TimeStamp mCpuTimeStart;
|
||||
mozilla::TimeStamp mCpuTimeEnd;
|
||||
uint64_t mGpuTimeStart;
|
||||
uint64_t mGpuTimeEnd;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // PROFILER_MARKERS_H
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue