diff options
Diffstat (limited to 'tools/profiler/gecko')
-rw-r--r-- | tools/profiler/gecko/ProfileGatherer.cpp | 207 | ||||
-rw-r--r-- | tools/profiler/gecko/Profiler.jsm | 16 | ||||
-rw-r--r-- | tools/profiler/gecko/ProfilerIOInterposeObserver.cpp | 30 | ||||
-rw-r--r-- | tools/profiler/gecko/ProfilerIOInterposeObserver.h | 28 | ||||
-rw-r--r-- | tools/profiler/gecko/ProfilerTypes.ipdlh | 16 | ||||
-rw-r--r-- | tools/profiler/gecko/SaveProfileTask.cpp | 45 | ||||
-rw-r--r-- | tools/profiler/gecko/SaveProfileTask.h | 54 | ||||
-rw-r--r-- | tools/profiler/gecko/ThreadResponsiveness.cpp | 118 | ||||
-rw-r--r-- | tools/profiler/gecko/ThreadResponsiveness.h | 38 | ||||
-rw-r--r-- | tools/profiler/gecko/nsIProfileSaveEvent.idl | 19 | ||||
-rw-r--r-- | tools/profiler/gecko/nsIProfiler.idl | 101 | ||||
-rw-r--r-- | tools/profiler/gecko/nsProfiler.cpp | 308 | ||||
-rw-r--r-- | tools/profiler/gecko/nsProfiler.h | 29 | ||||
-rw-r--r-- | tools/profiler/gecko/nsProfilerCIID.h | 14 | ||||
-rw-r--r-- | tools/profiler/gecko/nsProfilerFactory.cpp | 31 | ||||
-rw-r--r-- | tools/profiler/gecko/nsProfilerStartParams.cpp | 67 | ||||
-rw-r--r-- | tools/profiler/gecko/nsProfilerStartParams.h | 32 |
17 files changed, 1153 insertions, 0 deletions
diff --git a/tools/profiler/gecko/ProfileGatherer.cpp b/tools/profiler/gecko/ProfileGatherer.cpp new file mode 100644 index 000000000..5cd45bee3 --- /dev/null +++ b/tools/profiler/gecko/ProfileGatherer.cpp @@ -0,0 +1,207 @@ +/* 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 diff --git a/tools/profiler/gecko/Profiler.jsm b/tools/profiler/gecko/Profiler.jsm new file mode 100644 index 000000000..c61218875 --- /dev/null +++ b/tools/profiler/gecko/Profiler.jsm @@ -0,0 +1,16 @@ +/* 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 = { + +}; + diff --git a/tools/profiler/gecko/ProfilerIOInterposeObserver.cpp b/tools/profiler/gecko/ProfilerIOInterposeObserver.cpp new file mode 100644 index 000000000..07801535d --- /dev/null +++ b/tools/profiler/gecko/ProfilerIOInterposeObserver.cpp @@ -0,0 +1,30 @@ +/* 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); +} diff --git a/tools/profiler/gecko/ProfilerIOInterposeObserver.h b/tools/profiler/gecko/ProfilerIOInterposeObserver.h new file mode 100644 index 000000000..8661b197e --- /dev/null +++ b/tools/profiler/gecko/ProfilerIOInterposeObserver.h @@ -0,0 +1,28 @@ +/* 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 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 + +#endif // PROFILERIOINTERPOSEOBSERVER_H diff --git a/tools/profiler/gecko/ProfilerTypes.ipdlh b/tools/profiler/gecko/ProfilerTypes.ipdlh new file mode 100644 index 000000000..1ef670b03 --- /dev/null +++ b/tools/profiler/gecko/ProfilerTypes.ipdlh @@ -0,0 +1,16 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +namespace mozilla { + +struct ProfilerInitParams { + bool enabled; + uint32_t entries; + double interval; + nsCString[] threadFilters; + nsCString[] features; +}; + +} // namespace mozilla
\ No newline at end of file diff --git a/tools/profiler/gecko/SaveProfileTask.cpp b/tools/profiler/gecko/SaveProfileTask.cpp new file mode 100644 index 000000000..497385355 --- /dev/null +++ b/tools/profiler/gecko/SaveProfileTask.cpp @@ -0,0 +1,45 @@ +/* -*- 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) && !defined(MOZ_WIDGET_GONK) + 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; +} + diff --git a/tools/profiler/gecko/SaveProfileTask.h b/tools/profiler/gecko/SaveProfileTask.h new file mode 100644 index 000000000..4a215bba0 --- /dev/null +++ b/tools/profiler/gecko/SaveProfileTask.h @@ -0,0 +1,54 @@ +/* -*- 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 + diff --git a/tools/profiler/gecko/ThreadResponsiveness.cpp b/tools/profiler/gecko/ThreadResponsiveness.cpp new file mode 100644 index 000000000..0057251e2 --- /dev/null +++ b/tools/profiler/gecko/ThreadResponsiveness.cpp @@ -0,0 +1,118 @@ +/* -*- 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(); + } +} + diff --git a/tools/profiler/gecko/ThreadResponsiveness.h b/tools/profiler/gecko/ThreadResponsiveness.h new file mode 100644 index 000000000..5454c3c05 --- /dev/null +++ b/tools/profiler/gecko/ThreadResponsiveness.h @@ -0,0 +1,38 @@ +/* -*- 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 + diff --git a/tools/profiler/gecko/nsIProfileSaveEvent.idl b/tools/profiler/gecko/nsIProfileSaveEvent.idl new file mode 100644 index 000000000..c2c4bed02 --- /dev/null +++ b/tools/profiler/gecko/nsIProfileSaveEvent.idl @@ -0,0 +1,19 @@ +/* -*- 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); +}; + + diff --git a/tools/profiler/gecko/nsIProfiler.idl b/tools/profiler/gecko/nsIProfiler.idl new file mode 100644 index 000000000..f9b118650 --- /dev/null +++ b/tools/profiler/gecko/nsIProfiler.idl @@ -0,0 +1,101 @@ +/* -*- 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); +}; diff --git a/tools/profiler/gecko/nsProfiler.cpp b/tools/profiler/gecko/nsProfiler.cpp new file mode 100644 index 000000000..c38447381 --- /dev/null +++ b/tools/profiler/gecko/nsProfiler.cpp @@ -0,0 +1,308 @@ +/* -*- 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; +}
\ No newline at end of file diff --git a/tools/profiler/gecko/nsProfiler.h b/tools/profiler/gecko/nsProfiler.h new file mode 100644 index 000000000..50dabd278 --- /dev/null +++ b/tools/profiler/gecko/nsProfiler.h @@ -0,0 +1,29 @@ +/* -*- 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_ */ + diff --git a/tools/profiler/gecko/nsProfilerCIID.h b/tools/profiler/gecko/nsProfilerCIID.h new file mode 100644 index 000000000..3057a6ae0 --- /dev/null +++ b/tools/profiler/gecko/nsProfilerCIID.h @@ -0,0 +1,14 @@ +/* -*- 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 nsProfilerCIID_h__ +#define nsProfilerCIID_h__ + +#define NS_PROFILER_CID \ +{ 0x25db9b8e, 0x8123, 0x4de1, \ +{ 0xb6, 0x6d, 0x8b, 0xbb, 0xed, 0xf2, 0xcd, 0xf4 } } + +#endif + diff --git a/tools/profiler/gecko/nsProfilerFactory.cpp b/tools/profiler/gecko/nsProfilerFactory.cpp new file mode 100644 index 000000000..0cab23e89 --- /dev/null +++ b/tools/profiler/gecko/nsProfilerFactory.cpp @@ -0,0 +1,31 @@ +/* -*- 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; diff --git a/tools/profiler/gecko/nsProfilerStartParams.cpp b/tools/profiler/gecko/nsProfilerStartParams.cpp new file mode 100644 index 000000000..5335e694e --- /dev/null +++ b/tools/profiler/gecko/nsProfilerStartParams.cpp @@ -0,0 +1,67 @@ +/* -*- 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; +} diff --git a/tools/profiler/gecko/nsProfilerStartParams.h b/tools/profiler/gecko/nsProfilerStartParams.h new file mode 100644 index 000000000..98788077f --- /dev/null +++ b/tools/profiler/gecko/nsProfilerStartParams.h @@ -0,0 +1,32 @@ +/* -*- 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 |