summaryrefslogtreecommitdiff
path: root/tools/profiler/core/GeckoSampler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/core/GeckoSampler.cpp')
-rw-r--r--tools/profiler/core/GeckoSampler.cpp1306
1 files changed, 0 insertions, 1306 deletions
diff --git a/tools/profiler/core/GeckoSampler.cpp b/tools/profiler/core/GeckoSampler.cpp
deleted file mode 100644
index 4283542f6c..0000000000
--- a/tools/profiler/core/GeckoSampler.cpp
+++ /dev/null
@@ -1,1306 +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 <algorithm>
-#include <string>
-#include <stdio.h>
-#include <fstream>
-#include <sstream>
-#include "GeckoProfiler.h"
-#ifndef SPS_STANDALONE
-#include "SaveProfileTask.h"
-#include "nsThreadUtils.h"
-#include "prenv.h"
-#include "prtime.h"
-#include "nsXULAppAPI.h"
-#endif
-#include "ProfileEntry.h"
-#include "SyncProfile.h"
-#include "platform.h"
-#include "shared-libraries.h"
-#include "mozilla/StackWalk.h"
-#include "GeckoSampler.h"
-
-// JSON
-#include "ProfileJSONWriter.h"
-
-#ifndef SPS_STANDALONE
-// Meta
-#include "nsXPCOM.h"
-#include "nsXPCOMCID.h"
-#include "nsIHttpProtocolHandler.h"
-#include "nsServiceManagerUtils.h"
-#include "nsIXULRuntime.h"
-#include "nsIXULAppInfo.h"
-#include "nsDirectoryServiceUtils.h"
-#include "nsDirectoryServiceDefs.h"
-#include "nsIObserverService.h"
-#include "mozilla/Services.h"
-#include "PlatformMacros.h"
-#include "nsTArray.h"
-
-#include "mozilla/ProfileGatherer.h"
-#endif
-
-#if defined(SPS_OS_android)
- #include "FennecJNIWrappers.h"
-#endif
-
-#ifndef SPS_STANDALONE
-// JS
-#include "jsfriendapi.h"
-#include "js/ProfilingFrameIterator.h"
-#endif
-
-#if defined(MOZ_PROFILING) && (defined(XP_MACOSX) || defined(XP_WIN))
- #define USE_NS_STACKWALK
-#endif
-
-#if defined(XP_WIN)
-typedef CONTEXT tickcontext_t;
-#elif defined(LINUX)
-#include <ucontext.h>
-typedef ucontext_t tickcontext_t;
-#endif
-
-#if defined(LINUX) || defined(XP_MACOSX)
-#include <sys/types.h>
-pid_t gettid();
-#endif
-
-#if defined(__arm__) && defined(ANDROID)
- // Should also work on ARM Linux, but not tested there yet.
- #define USE_EHABI_STACKWALK
-#endif
-#ifdef USE_EHABI_STACKWALK
- #include "EHABIStackWalk.h"
-#endif
-
-#ifndef SPS_STANDALONE
-#if 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
-#endif
-
-using std::string;
-using namespace mozilla;
-
-#ifndef MAXPATHLEN
- #ifdef PATH_MAX
- #define MAXPATHLEN PATH_MAX
- #elif defined(MAX_PATH)
- #define MAXPATHLEN MAX_PATH
- #elif defined(_MAX_PATH)
- #define MAXPATHLEN _MAX_PATH
- #elif defined(CCHMAXPATH)
- #define MAXPATHLEN CCHMAXPATH
- #else
- #define MAXPATHLEN 1024
- #endif
-#endif
-
-#ifdef MOZ_VALGRIND
-# include <valgrind/memcheck.h>
-#else
-# define VALGRIND_MAKE_MEM_DEFINED(_addr,_len) ((void)0)
-#endif
-
-
-///////////////////////////////////////////////////////////////////////
-// BEGIN SaveProfileTask et al
-
-static void
-AddSharedLibraryInfoToStream(std::ostream& aStream, const SharedLibrary& aLib)
-{
- aStream << "{";
- aStream << "\"start\":" << aLib.GetStart();
- aStream << ",\"end\":" << aLib.GetEnd();
- aStream << ",\"offset\":" << aLib.GetOffset();
- aStream << ",\"name\":\"" << aLib.GetName() << "\"";
- const std::string &breakpadId = aLib.GetBreakpadId();
- aStream << ",\"breakpadId\":\"" << breakpadId << "\"";
-#ifdef XP_WIN
- // FIXME: remove this XP_WIN code when the profiler plugin has switched to
- // using breakpadId.
- std::string pdbSignature = breakpadId.substr(0, 32);
- std::string pdbAgeStr = breakpadId.substr(32, breakpadId.size() - 1);
-
- std::stringstream stream;
- stream << pdbAgeStr;
-
- unsigned pdbAge;
- stream << std::hex;
- stream >> pdbAge;
-
-#ifdef DEBUG
- std::ostringstream oStream;
- oStream << pdbSignature << std::hex << std::uppercase << pdbAge;
- MOZ_ASSERT(breakpadId == oStream.str());
-#endif
-
- aStream << ",\"pdbSignature\":\"" << pdbSignature << "\"";
- aStream << ",\"pdbAge\":" << pdbAge;
- aStream << ",\"pdbName\":\"" << aLib.GetName() << "\"";
-#endif
- aStream << "}";
-}
-
-std::string
-GetSharedLibraryInfoStringInternal()
-{
- SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
- if (info.GetSize() == 0)
- return "[]";
-
- std::ostringstream os;
- os << "[";
- AddSharedLibraryInfoToStream(os, info.GetEntry(0));
-
- for (size_t i = 1; i < info.GetSize(); i++) {
- os << ",";
- AddSharedLibraryInfoToStream(os, info.GetEntry(i));
- }
-
- os << "]";
- return os.str();
-}
-
-static bool
-hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) {
- for(size_t i = 0; i < aFeatureCount; i++) {
- if (strcmp(aFeatures[i], aFeature) == 0)
- return true;
- }
- return false;
-}
-
-GeckoSampler::GeckoSampler(double aInterval, int aEntrySize,
- const char** aFeatures, uint32_t aFeatureCount,
- const char** aThreadNameFilters, uint32_t aFilterCount)
- : Sampler(aInterval, true, aEntrySize)
- , mPrimaryThreadProfile(nullptr)
- , mBuffer(new ProfileBuffer(aEntrySize))
- , mSaveRequested(false)
-#if defined(XP_WIN)
- , mIntelPowerGadget(nullptr)
-#endif
-{
- mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
-
- mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
- mProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu");
- mProfilePower = hasFeature(aFeatures, aFeatureCount, "power");
- // Users sometimes ask to filter by a list of threads but forget to request
- // profiling non main threads. Let's make it implificit if we have a filter
- mProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads") || aFilterCount > 0;
- mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf");
- mPrivacyMode = hasFeature(aFeatures, aFeatureCount, "privacy");
- mAddMainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio");
- mProfileMemory = hasFeature(aFeatures, aFeatureCount, "memory");
- mTaskTracer = hasFeature(aFeatures, aFeatureCount, "tasktracer");
- mLayersDump = hasFeature(aFeatures, aFeatureCount, "layersdump");
- mDisplayListDump = hasFeature(aFeatures, aFeatureCount, "displaylistdump");
- mProfileRestyle = hasFeature(aFeatures, aFeatureCount, "restyle");
-
-#if defined(XP_WIN)
- if (mProfilePower) {
- mIntelPowerGadget = new IntelPowerGadget();
- mProfilePower = mIntelPowerGadget->Init();
- }
-#endif
-
-#if defined(SPS_OS_android)
- mProfileJava = mozilla::jni::IsFennec() &&
- hasFeature(aFeatures, aFeatureCount, "java");
-#else
- mProfileJava = false;
-#endif
-
- // Deep copy aThreadNameFilters
- MOZ_ALWAYS_TRUE(mThreadNameFilters.resize(aFilterCount));
- for (uint32_t i = 0; i < aFilterCount; ++i) {
- mThreadNameFilters[i] = aThreadNameFilters[i];
- }
-
- // Deep copy aFeatures
- MOZ_ALWAYS_TRUE(mFeatures.resize(aFeatureCount));
- for (uint32_t i = 0; i < aFeatureCount; ++i) {
- mFeatures[i] = aFeatures[i];
- }
-
- bool ignore;
- sStartTime = mozilla::TimeStamp::ProcessCreation(ignore);
-
- {
- ::MutexAutoLock lock(*sRegisteredThreadsMutex);
-
- // Create ThreadProfile for each registered thread
- for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
- ThreadInfo* info = sRegisteredThreads->at(i);
-
- RegisterThread(info);
- }
-
- SetActiveSampler(this);
- }
-
-#ifdef MOZ_TASK_TRACER
- if (mTaskTracer) {
- mozilla::tasktracer::StartLogging();
- }
-#endif
-
- mGatherer = new mozilla::ProfileGatherer(this);
-}
-
-GeckoSampler::~GeckoSampler()
-{
- if (IsActive())
- Stop();
-
- SetActiveSampler(nullptr);
-
- // Destroy ThreadProfile for all threads
- {
- ::MutexAutoLock lock(*sRegisteredThreadsMutex);
-
- for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
- ThreadInfo* info = sRegisteredThreads->at(i);
- ThreadProfile* profile = info->Profile();
- if (profile) {
- delete profile;
- info->SetProfile(nullptr);
- }
- // We've stopped profiling. We no longer need to retain
- // information for an old thread.
- if (info->IsPendingDelete()) {
- delete info;
- sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
- i--;
- }
- }
- }
-#if defined(XP_WIN)
- delete mIntelPowerGadget;
-#endif
-
- // Cancel any in-flight async profile gatherering
- // requests
- mGatherer->Cancel();
-}
-
-void GeckoSampler::HandleSaveRequest()
-{
- if (!mSaveRequested)
- return;
- mSaveRequested = false;
-
-#ifndef SPS_STANDALONE
- // TODO: Use use the ipc/chromium Tasks here to support processes
- // without XPCOM.
- nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask();
- NS_DispatchToMainThread(runnable);
-#endif
-}
-
-void GeckoSampler::DeleteExpiredMarkers()
-{
- mBuffer->deleteExpiredStoredMarkers();
-}
-
-void GeckoSampler::StreamTaskTracer(SpliceableJSONWriter& aWriter)
-{
-#ifdef MOZ_TASK_TRACER
- aWriter.StartArrayProperty("data");
- UniquePtr<nsTArray<nsCString>> data = mozilla::tasktracer::GetLoggedData(sStartTime);
- for (uint32_t i = 0; i < data->Length(); ++i) {
- aWriter.StringElement((data->ElementAt(i)).get());
- }
- aWriter.EndArray();
-
- aWriter.StartArrayProperty("threads");
- ::MutexAutoLock lock(*sRegisteredThreadsMutex);
- for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
- // Thread meta data
- ThreadInfo* info = sRegisteredThreads->at(i);
- aWriter.StartObjectElement();
- if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
- // TODO Add the proper plugin name
- aWriter.StringProperty("name", "Plugin");
- } else {
- aWriter.StringProperty("name", info->Name());
- }
- aWriter.IntProperty("tid", static_cast<int>(info->ThreadId()));
- aWriter.EndObject();
- }
- aWriter.EndArray();
-
- aWriter.DoubleProperty("start", static_cast<double>(mozilla::tasktracer::GetStartTime()));
-#endif
-}
-
-
-void GeckoSampler::StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter)
-{
- aWriter.IntProperty("version", 3);
- aWriter.DoubleProperty("interval", interval());
- aWriter.IntProperty("stackwalk", mUseStackWalk);
-
-#ifndef SPS_STANDALONE
- mozilla::TimeDuration delta = mozilla::TimeStamp::Now() - sStartTime;
- aWriter.DoubleProperty("startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
-
- aWriter.IntProperty("processType", XRE_GetProcessType());
-
- nsresult res;
- nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
- if (!NS_FAILED(res)) {
- nsAutoCString string;
-
- res = http->GetPlatform(string);
- if (!NS_FAILED(res))
- aWriter.StringProperty("platform", string.Data());
-
- res = http->GetOscpu(string);
- if (!NS_FAILED(res))
- aWriter.StringProperty("oscpu", string.Data());
-
- res = http->GetMisc(string);
- if (!NS_FAILED(res))
- aWriter.StringProperty("misc", string.Data());
- }
-
- nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
- if (runtime) {
- nsAutoCString string;
-
- res = runtime->GetXPCOMABI(string);
- if (!NS_FAILED(res))
- aWriter.StringProperty("abi", string.Data());
-
- res = runtime->GetWidgetToolkit(string);
- if (!NS_FAILED(res))
- aWriter.StringProperty("toolkit", string.Data());
- }
-
- nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
- if (appInfo) {
- nsAutoCString string;
-
- res = appInfo->GetName(string);
- if (!NS_FAILED(res))
- aWriter.StringProperty("product", string.Data());
- }
-#endif
-}
-
-void GeckoSampler::ToStreamAsJSON(std::ostream& stream, double aSinceTime)
-{
- SpliceableJSONWriter b(mozilla::MakeUnique<OStreamJSONWriteFunc>(stream));
- StreamJSON(b, aSinceTime);
-}
-
-#ifndef SPS_STANDALONE
-JSObject* GeckoSampler::ToJSObject(JSContext *aCx, double aSinceTime)
-{
- JS::RootedValue val(aCx);
- {
- UniquePtr<char[]> buf = ToJSON(aSinceTime);
- NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
- MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(js_string.get()),
- js_string.Length(), &val));
- }
- return &val.toObject();
-}
-
-void GeckoSampler::GetGatherer(nsISupports** aRetVal)
-{
- if (!aRetVal || NS_WARN_IF(!mGatherer)) {
- return;
- }
- NS_ADDREF(*aRetVal = mGatherer);
-}
-#endif
-
-UniquePtr<char[]> GeckoSampler::ToJSON(double aSinceTime)
-{
- SpliceableChunkedJSONWriter b;
- StreamJSON(b, aSinceTime);
- return b.WriteFunc()->CopyData();
-}
-
-void GeckoSampler::ToJSObjectAsync(double aSinceTime,
- mozilla::dom::Promise* aPromise)
-{
- if (NS_WARN_IF(!mGatherer)) {
- return;
- }
-
- mGatherer->Start(aSinceTime, aPromise);
-}
-
-struct SubprocessClosure {
- explicit SubprocessClosure(SpliceableJSONWriter* aWriter)
- : mWriter(aWriter)
- {}
-
- SpliceableJSONWriter* mWriter;
-};
-
-void SubProcessCallback(const char* aProfile, void* aClosure)
-{
- // Called by the observer to get their profile data included
- // as a sub profile
- SubprocessClosure* closure = (SubprocessClosure*)aClosure;
-
- // Add the string profile into the profile
- closure->mWriter->StringElement(aProfile);
-}
-
-
-#if defined(SPS_OS_android)
-static
-void BuildJavaThreadJSObject(SpliceableJSONWriter& aWriter)
-{
- aWriter.StringProperty("name", "Java Main Thread");
-
- aWriter.StartArrayProperty("samples");
-
- // for each sample
- for (int sampleId = 0; true; sampleId++) {
- bool firstRun = true;
- // for each frame
- for (int frameId = 0; true; frameId++) {
- jni::String::LocalRef frameName =
- java::GeckoJavaSampler::GetFrameName(0, sampleId, frameId);
- // when we run out of frames, we stop looping
- if (!frameName) {
- // if we found at least one frame, we have objects to close
- if (!firstRun) {
- aWriter.EndArray();
- aWriter.EndObject();
- }
- break;
- }
- // the first time around, open the sample object and frames array
- if (firstRun) {
- firstRun = false;
-
- double sampleTime =
- java::GeckoJavaSampler::GetSampleTime(0, sampleId);
-
- aWriter.StartObjectElement();
- aWriter.DoubleProperty("time", sampleTime);
-
- aWriter.StartArrayProperty("frames");
- }
- // add a frame to the sample
- aWriter.StartObjectElement();
- aWriter.StringProperty("location",
- frameName->ToCString().BeginReading());
- aWriter.EndObject();
- }
- // if we found no frames for this sample, we are done
- if (firstRun) {
- break;
- }
- }
-
- aWriter.EndArray();
-}
-#endif
-
-void GeckoSampler::StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime)
-{
- aWriter.Start(SpliceableJSONWriter::SingleLineStyle);
- {
- // Put shared library info
- aWriter.StringProperty("libs", GetSharedLibraryInfoStringInternal().c_str());
-
- // Put meta data
- aWriter.StartObjectProperty("meta");
- StreamMetaJSCustomObject(aWriter);
- aWriter.EndObject();
-
- // Data of TaskTracer doesn't belong in the circular buffer.
- if (TaskTracer()) {
- aWriter.StartObjectProperty("tasktracer");
- StreamTaskTracer(aWriter);
- aWriter.EndObject();
- }
-
- // Lists the samples for each ThreadProfile
- aWriter.StartArrayProperty("threads");
- {
- SetPaused(true);
-
- {
- ::MutexAutoLock lock(*sRegisteredThreadsMutex);
-
- for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
- // Thread not being profiled, skip it
- if (!sRegisteredThreads->at(i)->Profile())
- continue;
-
- // Note that we intentionally include ThreadProfile which
- // have been marked for pending delete.
-
- ::MutexAutoLock lock(sRegisteredThreads->at(i)->Profile()->GetMutex());
-
- sRegisteredThreads->at(i)->Profile()->StreamJSON(aWriter, aSinceTime);
- }
- }
-
-#ifndef SPS_STANDALONE
- if (Sampler::CanNotifyObservers()) {
- // Send a event asking any subprocesses (plugins) to
- // give us their information
- SubprocessClosure closure(&aWriter);
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- if (os) {
- RefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback, &closure);
- os->NotifyObservers(pse, "profiler-subprocess", nullptr);
- }
- }
-
- #if defined(SPS_OS_android)
- if (ProfileJava()) {
- java::GeckoJavaSampler::Pause();
-
- aWriter.Start();
- {
- BuildJavaThreadJSObject(aWriter);
- }
- aWriter.End();
-
- java::GeckoJavaSampler::Unpause();
- }
- #endif
-#endif
-
- SetPaused(false);
- }
- aWriter.EndArray();
- }
- aWriter.End();
-}
-
-void GeckoSampler::FlushOnJSShutdown(JSContext* aContext)
-{
-#ifndef SPS_STANDALONE
- SetPaused(true);
-
- {
- ::MutexAutoLock lock(*sRegisteredThreadsMutex);
-
- for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
- // Thread not being profiled, skip it.
- if (!sRegisteredThreads->at(i)->Profile() ||
- sRegisteredThreads->at(i)->IsPendingDelete()) {
- continue;
- }
-
- // Thread not profiling the context that's going away, skip it.
- if (sRegisteredThreads->at(i)->Profile()->GetPseudoStack()->mContext != aContext) {
- continue;
- }
-
- ::MutexAutoLock lock(sRegisteredThreads->at(i)->Profile()->GetMutex());
- sRegisteredThreads->at(i)->Profile()->FlushSamplesAndMarkers();
- }
- }
-
- SetPaused(false);
-#endif
-}
-
-void PseudoStack::flushSamplerOnJSShutdown()
-{
-#ifndef SPS_STANDALONE
- MOZ_ASSERT(mContext);
- GeckoSampler* t = tlsTicker.get();
- if (t) {
- t->FlushOnJSShutdown(mContext);
- }
-#endif
-}
-
-// END SaveProfileTask et al
-////////////////////////////////////////////////////////////////////////
-
-static
-void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr)
-{
- aProfile.addTag(ProfileEntry(aTagName, ""));
- // Add one to store the null termination
- size_t strLen = strlen(aStr) + 1;
- for (size_t j = 0; j < strLen;) {
- // Store as many characters in the void* as the platform allows
- char text[sizeof(void*)];
- size_t len = sizeof(void*)/sizeof(char);
- if (j+len >= strLen) {
- len = strLen - j;
- }
- memcpy(text, &aStr[j], len);
- j += sizeof(void*)/sizeof(char);
- // Cast to *((void**) to pass the text data to a void*
- aProfile.addTag(ProfileEntry('d', *((void**)(&text[0]))));
- }
-}
-
-static
-void addPseudoEntry(volatile StackEntry &entry, ThreadProfile &aProfile,
- PseudoStack *stack, void *lastpc)
-{
- // Pseudo-frames with the BEGIN_PSEUDO_JS flag are just annotations
- // and should not be recorded in the profile.
- if (entry.hasFlag(StackEntry::BEGIN_PSEUDO_JS))
- return;
-
- int lineno = -1;
-
- // First entry has tagName 's' (start)
- // Check for magic pointer bit 1 to indicate copy
- const char* sampleLabel = entry.label();
- if (entry.isCopyLabel()) {
- // Store the string using 1 or more 'd' (dynamic) tags
- // that will happen to the preceding tag
-
- addDynamicTag(aProfile, 'c', sampleLabel);
-#ifndef SPS_STANDALONE
- if (entry.isJs()) {
- JSScript* script = entry.script();
- if (script) {
- if (!entry.pc()) {
- // The JIT only allows the top-most entry to have a nullptr pc
- MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]);
- // If stack-walking was disabled, then that's just unfortunate
- if (lastpc) {
- jsbytecode *jspc = js::ProfilingGetPC(stack->mContext, script,
- lastpc);
- if (jspc) {
- lineno = JS_PCToLineNumber(script, jspc);
- }
- }
- } else {
- lineno = JS_PCToLineNumber(script, entry.pc());
- }
- }
- } else {
- lineno = entry.line();
- }
-#endif
- } else {
- aProfile.addTag(ProfileEntry('c', sampleLabel));
-
- // XXX: Bug 1010578. Don't assume a CPP entry and try to get the
- // line for js entries as well.
- if (entry.isCpp()) {
- lineno = entry.line();
- }
- }
-
- if (lineno != -1) {
- aProfile.addTag(ProfileEntry('n', lineno));
- }
-
- uint32_t category = entry.category();
- MOZ_ASSERT(!(category & StackEntry::IS_CPP_ENTRY));
- MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY));
-
- if (category) {
- aProfile.addTag(ProfileEntry('y', (int)category));
- }
-}
-
-struct NativeStack
-{
- void** pc_array;
- void** sp_array;
- size_t size;
- size_t count;
-};
-
-mozilla::Atomic<bool> WALKING_JS_STACK(false);
-
-struct AutoWalkJSStack {
- bool walkAllowed;
-
- AutoWalkJSStack() : walkAllowed(false) {
- walkAllowed = WALKING_JS_STACK.compareExchange(false, true);
- }
-
- ~AutoWalkJSStack() {
- if (walkAllowed)
- WALKING_JS_STACK = false;
- }
-};
-
-static
-void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack)
-{
- PseudoStack* pseudoStack = aProfile.GetPseudoStack();
- volatile StackEntry *pseudoFrames = pseudoStack->mStack;
- uint32_t pseudoCount = pseudoStack->stackSize();
-
- // Make a copy of the JS stack into a JSFrame array. This is necessary since,
- // like the native stack, the JS stack is iterated youngest-to-oldest and we
- // need to iterate oldest-to-youngest when adding entries to aProfile.
-
- // Synchronous sampling reports an invalid buffer generation to
- // ProfilingFrameIterator to avoid incorrectly resetting the generation of
- // sampled JIT entries inside the JS engine. See note below concerning 'J'
- // entries.
- uint32_t startBufferGen;
- if (aSample->isSamplingCurrentThread) {
- startBufferGen = UINT32_MAX;
- } else {
- startBufferGen = aProfile.bufferGeneration();
- }
- uint32_t jsCount = 0;
-#ifndef SPS_STANDALONE
- JS::ProfilingFrameIterator::Frame jsFrames[1000];
- // Only walk jit stack if profiling frame iterator is turned on.
- if (pseudoStack->mContext && JS::IsProfilingEnabledForContext(pseudoStack->mContext)) {
- AutoWalkJSStack autoWalkJSStack;
- const uint32_t maxFrames = mozilla::ArrayLength(jsFrames);
-
- if (aSample && autoWalkJSStack.walkAllowed) {
- JS::ProfilingFrameIterator::RegisterState registerState;
- registerState.pc = aSample->pc;
- registerState.sp = aSample->sp;
-#ifdef ENABLE_ARM_LR_SAVING
- registerState.lr = aSample->lr;
-#endif
-
- JS::ProfilingFrameIterator jsIter(pseudoStack->mContext,
- registerState,
- startBufferGen);
- for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) {
- // See note below regarding 'J' entries.
- if (aSample->isSamplingCurrentThread || jsIter.isWasm()) {
- uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames);
- jsCount += extracted;
- if (jsCount == maxFrames)
- break;
- } else {
- mozilla::Maybe<JS::ProfilingFrameIterator::Frame> frame =
- jsIter.getPhysicalFrameWithoutLabel();
- if (frame.isSome())
- jsFrames[jsCount++] = mozilla::Move(frame.ref());
- }
- }
- }
- }
-#endif
-
- // Start the sample with a root entry.
- aProfile.addTag(ProfileEntry('s', "(root)"));
-
- // While the pseudo-stack array is ordered oldest-to-youngest, the JS and
- // native arrays are ordered youngest-to-oldest. We must add frames to
- // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards
- // and JS and native arrays backwards. Note: this means the terminating
- // condition jsIndex and nativeIndex is being < 0.
- uint32_t pseudoIndex = 0;
- int32_t jsIndex = jsCount - 1;
- int32_t nativeIndex = aNativeStack.count - 1;
-
- uint8_t *lastPseudoCppStackAddr = nullptr;
-
- // Iterate as long as there is at least one frame remaining.
- while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) {
- // There are 1 to 3 frames available. Find and add the oldest.
-
- uint8_t *pseudoStackAddr = nullptr;
- uint8_t *jsStackAddr = nullptr;
- uint8_t *nativeStackAddr = nullptr;
-
- if (pseudoIndex != pseudoCount) {
- volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex];
-
- if (pseudoFrame.isCpp())
- lastPseudoCppStackAddr = (uint8_t *) pseudoFrame.stackAddress();
-
-#ifndef SPS_STANDALONE
- // Skip any pseudo-stack JS frames which are marked isOSR
- // Pseudostack frames are marked isOSR when the JS interpreter
- // enters a jit frame on a loop edge (via on-stack-replacement,
- // or OSR). To avoid both the pseudoframe and jit frame being
- // recorded (and showing up twice), the interpreter marks the
- // interpreter pseudostack entry with the OSR flag to ensure that
- // it doesn't get counted.
- if (pseudoFrame.isJs() && pseudoFrame.isOSR()) {
- pseudoIndex++;
- continue;
- }
-#endif
-
- MOZ_ASSERT(lastPseudoCppStackAddr);
- pseudoStackAddr = lastPseudoCppStackAddr;
- }
-
-#ifndef SPS_STANDALONE
- if (jsIndex >= 0)
- jsStackAddr = (uint8_t *) jsFrames[jsIndex].stackAddress;
-#endif
-
- if (nativeIndex >= 0)
- nativeStackAddr = (uint8_t *) aNativeStack.sp_array[nativeIndex];
-
- // If there's a native stack entry which has the same SP as a
- // pseudo stack entry, pretend we didn't see the native stack
- // entry. Ditto for a native stack entry which has the same SP as
- // a JS stack entry. In effect this means pseudo or JS entries
- // trump conflicting native entries.
- if (nativeStackAddr && (pseudoStackAddr == nativeStackAddr || jsStackAddr == nativeStackAddr)) {
- nativeStackAddr = nullptr;
- nativeIndex--;
- MOZ_ASSERT(pseudoStackAddr || jsStackAddr);
- }
-
- // Sanity checks.
- MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr &&
- pseudoStackAddr != nativeStackAddr);
- MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr &&
- jsStackAddr != nativeStackAddr);
- MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr &&
- nativeStackAddr != jsStackAddr);
-
- // Check to see if pseudoStack frame is top-most.
- if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) {
- MOZ_ASSERT(pseudoIndex < pseudoCount);
- volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex];
- addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr);
- pseudoIndex++;
- continue;
- }
-
-#ifndef SPS_STANDALONE
- // Check to see if JS jit stack frame is top-most
- if (jsStackAddr > nativeStackAddr) {
- MOZ_ASSERT(jsIndex >= 0);
- const JS::ProfilingFrameIterator::Frame& jsFrame = jsFrames[jsIndex];
-
- // Stringifying non-wasm JIT frames is delayed until streaming
- // time. To re-lookup the entry in the JitcodeGlobalTable, we need to
- // store the JIT code address ('J') in the circular buffer.
- //
- // Note that we cannot do this when we are sychronously sampling the
- // current thread; that is, when called from profiler_get_backtrace. The
- // captured backtrace is usually externally stored for an indeterminate
- // amount of time, such as in nsRefreshDriver. Problematically, the
- // stored backtrace may be alive across a GC during which the profiler
- // itself is disabled. In that case, the JS engine is free to discard
- // its JIT code. This means that if we inserted such 'J' entries into
- // the buffer, nsRefreshDriver would now be holding on to a backtrace
- // with stale JIT code return addresses.
- if (aSample->isSamplingCurrentThread ||
- jsFrame.kind == JS::ProfilingFrameIterator::Frame_Wasm) {
- addDynamicTag(aProfile, 'c', jsFrame.label.get());
- } else {
- MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion ||
- jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline);
- aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress));
- }
-
- jsIndex--;
- continue;
- }
-#endif
-
- // If we reach here, there must be a native stack entry and it must be the
- // greatest entry.
- if (nativeStackAddr) {
- MOZ_ASSERT(nativeIndex >= 0);
- aProfile
- .addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex]));
- }
- if (nativeIndex >= 0) {
- nativeIndex--;
- }
- }
-
-#ifndef SPS_STANDALONE
- // Update the JS context with the current profile sample buffer generation.
- //
- // Do not do this for synchronous sampling, which create their own
- // ProfileBuffers.
- if (!aSample->isSamplingCurrentThread && pseudoStack->mContext) {
- MOZ_ASSERT(aProfile.bufferGeneration() >= startBufferGen);
- uint32_t lapCount = aProfile.bufferGeneration() - startBufferGen;
- JS::UpdateJSContextProfilerSampleBufferGen(pseudoStack->mContext,
- aProfile.bufferGeneration(),
- lapCount);
- }
-#endif
-}
-
-#ifdef USE_NS_STACKWALK
-static
-void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP,
- void* aClosure)
-{
- NativeStack* nativeStack = static_cast<NativeStack*>(aClosure);
- MOZ_ASSERT(nativeStack->count < nativeStack->size);
- nativeStack->sp_array[nativeStack->count] = aSP;
- nativeStack->pc_array[nativeStack->count] = aPC;
- nativeStack->count++;
-}
-
-void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
-{
- void* pc_array[1000];
- void* sp_array[1000];
- NativeStack nativeStack = {
- pc_array,
- sp_array,
- mozilla::ArrayLength(pc_array),
- 0
- };
-
- // Start with the current function. We use 0 as the frame number here because
- // the FramePointerStackWalk() and MozStackWalk() calls below will use 1..N.
- // This is a bit weird but it doesn't matter because StackWalkCallback()
- // doesn't use the frame number argument.
- StackWalkCallback(/* frameNumber */ 0, aSample->pc, aSample->sp, &nativeStack);
-
- uint32_t maxFrames = uint32_t(nativeStack.size - nativeStack.count);
- // win X64 doesn't support disabling frame pointers emission so we need
- // to fallback to using StackWalk64 which is slower.
-#if defined(XP_MACOSX) || (defined(XP_WIN) && !defined(V8_HOST_ARCH_X64))
- void *stackEnd = aSample->threadProfile->GetStackTop();
- bool rv = true;
- if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd)
- rv = FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0,
- maxFrames, &nativeStack,
- reinterpret_cast<void**>(aSample->fp), stackEnd);
-#else
- void *platformData = nullptr;
-
- uintptr_t thread = GetThreadHandle(aSample->threadProfile->GetPlatformData());
- MOZ_ASSERT(thread);
- bool rv = MozStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
- &nativeStack, thread, platformData);
-#endif
- if (rv)
- mergeStacksIntoProfile(aProfile, aSample, nativeStack);
-}
-#endif
-
-
-#ifdef USE_EHABI_STACKWALK
-void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
-{
- void *pc_array[1000];
- void *sp_array[1000];
- NativeStack nativeStack = {
- pc_array,
- sp_array,
- mozilla::ArrayLength(pc_array),
- 0
- };
-
- const mcontext_t *mcontext = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext;
- mcontext_t savedContext;
- PseudoStack *pseudoStack = aProfile.GetPseudoStack();
-
- nativeStack.count = 0;
- // The pseudostack contains an "EnterJIT" frame whenever we enter
- // JIT code with profiling enabled; the stack pointer value points
- // the saved registers. We use this to unwind resume unwinding
- // after encounting JIT code.
- for (uint32_t i = pseudoStack->stackSize(); i > 0; --i) {
- // The pseudostack grows towards higher indices, so we iterate
- // backwards (from callee to caller).
- volatile StackEntry &entry = pseudoStack->mStack[i - 1];
- if (!entry.isJs() && strcmp(entry.label(), "EnterJIT") == 0) {
- // Found JIT entry frame. Unwind up to that point (i.e., force
- // the stack walk to stop before the block of saved registers;
- // note that it yields nondecreasing stack pointers), then restore
- // the saved state.
- uint32_t *vSP = reinterpret_cast<uint32_t*>(entry.stackAddress());
-
- nativeStack.count += EHABIStackWalk(*mcontext,
- /* stackBase = */ vSP,
- sp_array + nativeStack.count,
- pc_array + nativeStack.count,
- nativeStack.size - nativeStack.count);
-
- memset(&savedContext, 0, sizeof(savedContext));
- // See also: struct EnterJITStack in js/src/jit/arm/Trampoline-arm.cpp
- savedContext.arm_r4 = *vSP++;
- savedContext.arm_r5 = *vSP++;
- savedContext.arm_r6 = *vSP++;
- savedContext.arm_r7 = *vSP++;
- savedContext.arm_r8 = *vSP++;
- savedContext.arm_r9 = *vSP++;
- savedContext.arm_r10 = *vSP++;
- savedContext.arm_fp = *vSP++;
- savedContext.arm_lr = *vSP++;
- savedContext.arm_sp = reinterpret_cast<uint32_t>(vSP);
- savedContext.arm_pc = savedContext.arm_lr;
- mcontext = &savedContext;
- }
- }
-
- // Now unwind whatever's left (starting from either the last EnterJIT
- // frame or, if no EnterJIT was found, the original registers).
- nativeStack.count += EHABIStackWalk(*mcontext,
- aProfile.GetStackTop(),
- sp_array + nativeStack.count,
- pc_array + nativeStack.count,
- nativeStack.size - nativeStack.count);
-
- mergeStacksIntoProfile(aProfile, aSample, nativeStack);
-}
-#endif
-
-
-#ifdef USE_LUL_STACKWALK
-void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
-{
- const mcontext_t* mc
- = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext;
-
- lul::UnwindRegs startRegs;
- memset(&startRegs, 0, sizeof(startRegs));
-
-# if defined(SPS_PLAT_amd64_linux)
- startRegs.xip = lul::TaggedUWord(mc->gregs[REG_RIP]);
- startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_RSP]);
- startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_RBP]);
-# elif defined(SPS_PLAT_arm_android)
- startRegs.r15 = lul::TaggedUWord(mc->arm_pc);
- startRegs.r14 = lul::TaggedUWord(mc->arm_lr);
- startRegs.r13 = lul::TaggedUWord(mc->arm_sp);
- startRegs.r12 = lul::TaggedUWord(mc->arm_ip);
- startRegs.r11 = lul::TaggedUWord(mc->arm_fp);
- startRegs.r7 = lul::TaggedUWord(mc->arm_r7);
-# elif defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
- startRegs.xip = lul::TaggedUWord(mc->gregs[REG_EIP]);
- startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_ESP]);
- startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_EBP]);
-# else
-# error "Unknown plat"
-# endif
-
- /* Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not
- going past the stack's registered top point. Do some basic
- sanity checks too. This assumes that the TaggedUWord holding
- the stack pointer value is valid, but it should be, since it
- was constructed that way in the code just above. */
-
- lul::StackImage stackImg;
-
- {
-# if defined(SPS_PLAT_amd64_linux)
- uintptr_t rEDZONE_SIZE = 128;
- uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
-# elif defined(SPS_PLAT_arm_android)
- uintptr_t rEDZONE_SIZE = 0;
- uintptr_t start = startRegs.r13.Value() - rEDZONE_SIZE;
-# elif defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
- uintptr_t rEDZONE_SIZE = 0;
- uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
-# else
-# error "Unknown plat"
-# endif
- uintptr_t end = reinterpret_cast<uintptr_t>(aProfile.GetStackTop());
- uintptr_t ws = sizeof(void*);
- start &= ~(ws-1);
- end &= ~(ws-1);
- uintptr_t nToCopy = 0;
- if (start < end) {
- nToCopy = end - start;
- if (nToCopy > lul::N_STACK_BYTES)
- nToCopy = lul::N_STACK_BYTES;
- }
- MOZ_ASSERT(nToCopy <= lul::N_STACK_BYTES);
- stackImg.mLen = nToCopy;
- stackImg.mStartAvma = start;
- if (nToCopy > 0) {
- memcpy(&stackImg.mContents[0], (void*)start, nToCopy);
- (void)VALGRIND_MAKE_MEM_DEFINED(&stackImg.mContents[0], nToCopy);
- }
- }
-
- // The maximum number of frames that LUL will produce. Setting it
- // too high gives a risk of it wasting a lot of time looping on
- // corrupted stacks.
- const int MAX_NATIVE_FRAMES = 256;
-
- size_t scannedFramesAllowed = 0;
-
- uintptr_t framePCs[MAX_NATIVE_FRAMES];
- uintptr_t frameSPs[MAX_NATIVE_FRAMES];
- size_t framesAvail = mozilla::ArrayLength(framePCs);
- size_t framesUsed = 0;
- size_t scannedFramesAcquired = 0;
- sLUL->Unwind( &framePCs[0], &frameSPs[0],
- &framesUsed, &scannedFramesAcquired,
- framesAvail, scannedFramesAllowed,
- &startRegs, &stackImg );
-
- NativeStack nativeStack = {
- reinterpret_cast<void**>(framePCs),
- reinterpret_cast<void**>(frameSPs),
- mozilla::ArrayLength(framePCs),
- 0
- };
-
- nativeStack.count = framesUsed;
-
- mergeStacksIntoProfile(aProfile, aSample, nativeStack);
-
- // Update stats in the LUL stats object. Unfortunately this requires
- // three global memory operations.
- sLUL->mStats.mContext += 1;
- sLUL->mStats.mCFI += framesUsed - 1 - scannedFramesAcquired;
- sLUL->mStats.mScanned += scannedFramesAcquired;
-}
-#endif
-
-
-static
-void doSampleStackTrace(ThreadProfile &aProfile, TickSample *aSample, bool aAddLeafAddresses)
-{
- NativeStack nativeStack = { nullptr, nullptr, 0, 0 };
- mergeStacksIntoProfile(aProfile, aSample, nativeStack);
-
-#ifdef ENABLE_SPS_LEAF_DATA
- if (aSample && aAddLeafAddresses) {
- aProfile.addTag(ProfileEntry('l', (void*)aSample->pc));
-#ifdef ENABLE_ARM_LR_SAVING
- aProfile.addTag(ProfileEntry('L', (void*)aSample->lr));
-#endif
- }
-#endif
-}
-
-void GeckoSampler::Tick(TickSample* sample)
-{
- // Don't allow for ticks to happen within other ticks.
- InplaceTick(sample);
-}
-
-void GeckoSampler::InplaceTick(TickSample* sample)
-{
- ThreadProfile& currThreadProfile = *sample->threadProfile;
-
- currThreadProfile.addTag(ProfileEntry('T', currThreadProfile.ThreadId()));
-
- if (sample) {
- mozilla::TimeDuration delta = sample->timestamp - sStartTime;
- currThreadProfile.addTag(ProfileEntry('t', delta.ToMilliseconds()));
- }
-
- PseudoStack* stack = currThreadProfile.GetPseudoStack();
-
-#if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) || \
- defined(USE_LUL_STACKWALK)
- if (mUseStackWalk) {
- doNativeBacktrace(currThreadProfile, sample);
- } else {
- doSampleStackTrace(currThreadProfile, sample, mAddLeafAddresses);
- }
-#else
- doSampleStackTrace(currThreadProfile, sample, mAddLeafAddresses);
-#endif
-
- // Don't process the PeudoStack's markers if we're
- // synchronously sampling the current thread.
- if (!sample->isSamplingCurrentThread) {
- ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers();
- while (pendingMarkersList && pendingMarkersList->peek()) {
- ProfilerMarker* marker = pendingMarkersList->popHead();
- currThreadProfile.addStoredMarker(marker);
- currThreadProfile.addTag(ProfileEntry('m', marker));
- }
- }
-
-#ifndef SPS_STANDALONE
- if (sample && currThreadProfile.GetThreadResponsiveness()->HasData()) {
- mozilla::TimeDuration delta = currThreadProfile.GetThreadResponsiveness()->GetUnresponsiveDuration(sample->timestamp);
- currThreadProfile.addTag(ProfileEntry('r', delta.ToMilliseconds()));
- }
-#endif
-
- // rssMemory is equal to 0 when we are not recording.
- if (sample && sample->rssMemory != 0) {
- currThreadProfile.addTag(ProfileEntry('R', static_cast<double>(sample->rssMemory)));
- }
-
- // ussMemory is equal to 0 when we are not recording.
- if (sample && sample->ussMemory != 0) {
- currThreadProfile.addTag(ProfileEntry('U', static_cast<double>(sample->ussMemory)));
- }
-
-#if defined(XP_WIN)
- if (mProfilePower) {
- mIntelPowerGadget->TakeSample();
- currThreadProfile.addTag(ProfileEntry('p', static_cast<double>(mIntelPowerGadget->GetTotalPackagePowerInWatts())));
- }
-#endif
-
- if (sLastFrameNumber != sFrameNumber) {
- currThreadProfile.addTag(ProfileEntry('f', sFrameNumber));
- sLastFrameNumber = sFrameNumber;
- }
-}
-
-namespace {
-
-SyncProfile* NewSyncProfile()
-{
- PseudoStack* stack = tlsPseudoStack.get();
- if (!stack) {
- MOZ_ASSERT(stack);
- return nullptr;
- }
- Thread::tid_t tid = Thread::GetCurrentId();
-
- ThreadInfo* info = new ThreadInfo("SyncProfile", tid, false, stack, nullptr);
- SyncProfile* profile = new SyncProfile(info, GET_BACKTRACE_DEFAULT_ENTRY);
- return profile;
-}
-
-} // namespace
-
-SyncProfile* GeckoSampler::GetBacktrace()
-{
- SyncProfile* profile = NewSyncProfile();
-
- TickSample sample;
- sample.threadProfile = profile;
-
-#if defined(HAVE_NATIVE_UNWIND) || defined(USE_LUL_STACKWALK)
-#if defined(XP_WIN) || defined(LINUX)
- tickcontext_t context;
- sample.PopulateContext(&context);
-#elif defined(XP_MACOSX)
- sample.PopulateContext(nullptr);
-#endif
-#endif
-
- sample.isSamplingCurrentThread = true;
- sample.timestamp = mozilla::TimeStamp::Now();
-
- profile->BeginUnwind();
- Tick(&sample);
- profile->EndUnwind();
-
- return profile;
-}
-
-void
-GeckoSampler::GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration)
-{
- *aCurrentPosition = mBuffer->mWritePos;
- *aTotalSize = mBuffer->mEntrySize;
- *aGeneration = mBuffer->mGeneration;
-}