diff options
Diffstat (limited to 'dom/performance/Performance.cpp')
-rw-r--r-- | dom/performance/Performance.cpp | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp new file mode 100644 index 000000000..17273c55d --- /dev/null +++ b/dom/performance/Performance.cpp @@ -0,0 +1,536 @@ +/* -*- 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 "Performance.h" + +#include "GeckoProfiler.h" +#include "PerformanceEntry.h" +#include "PerformanceMainThread.h" +#include "PerformanceMark.h" +#include "PerformanceMeasure.h" +#include "PerformanceObserver.h" +#include "PerformanceResourceTiming.h" +#include "PerformanceWorker.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/PerformanceBinding.h" +#include "mozilla/dom/PerformanceEntryEvent.h" +#include "mozilla/dom/PerformanceNavigationBinding.h" +#include "mozilla/dom/PerformanceObserverBinding.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Preferences.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" + +#ifdef MOZ_WIDGET_GONK +#define PERFLOG(msg, ...) __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__) +#else +#define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__) +#endif + +namespace mozilla { +namespace dom { + +using namespace workers; + +namespace { + +// Helper classes +class MOZ_STACK_CLASS PerformanceEntryComparator final +{ +public: + bool Equals(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const + { + MOZ_ASSERT(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() == aElem2->StartTime(); + } + + bool LessThan(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const + { + MOZ_ASSERT(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() < aElem2->StartTime(); + } +}; + +class PrefEnabledRunnable final + : public WorkerCheckAPIExposureOnMainThreadRunnable +{ +public: + PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate, + const nsCString& aPrefName) + : WorkerCheckAPIExposureOnMainThreadRunnable(aWorkerPrivate) + , mEnabled(false) + , mPrefName(aPrefName) + { } + + bool MainThreadRun() override + { + MOZ_ASSERT(NS_IsMainThread()); + mEnabled = Preferences::GetBool(mPrefName.get(), false); + return true; + } + + bool IsEnabled() const + { + return mEnabled; + } + +private: + bool mEnabled; + nsCString mPrefName; +}; + +} // anonymous namespace + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Performance) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance, + DOMEventTargetHelper, + mUserEntries, + mResourceEntries); + +NS_IMPL_ADDREF_INHERITED(Performance, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper) + +/* static */ already_AddRefed<Performance> +Performance::CreateForMainThread(nsPIDOMWindowInner* aWindow, + nsDOMNavigationTiming* aDOMTiming, + nsITimedChannel* aChannel, + Performance* aParentPerformance) +{ + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<Performance> performance = + new PerformanceMainThread(aWindow, aDOMTiming, aChannel, + aParentPerformance); + return performance.forget(); +} + +/* static */ already_AddRefed<Performance> +Performance::CreateForWorker(workers::WorkerPrivate* aWorkerPrivate) +{ + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr<Performance> performance = new PerformanceWorker(aWorkerPrivate); + return performance.forget(); +} + +Performance::Performance() + : mResourceTimingBufferSize(kDefaultResourceTimingBufferSize) + , mPendingNotificationObserversTask(false) +{ + MOZ_ASSERT(!NS_IsMainThread()); +} + +Performance::Performance(nsPIDOMWindowInner* aWindow) + : DOMEventTargetHelper(aWindow) + , mResourceTimingBufferSize(kDefaultResourceTimingBufferSize) + , mPendingNotificationObserversTask(false) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +Performance::~Performance() +{} + +JSObject* +Performance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return PerformanceBinding::Wrap(aCx, this, aGivenProto); +} + +void +Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) +{ + aRetval = mResourceEntries; + aRetval.AppendElements(mUserEntries); + aRetval.Sort(PerformanceEntryComparator()); +} + +void +Performance::GetEntriesByType(const nsAString& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval) +{ + if (aEntryType.EqualsLiteral("resource")) { + aRetval = mResourceEntries; + return; + } + + aRetval.Clear(); + + if (aEntryType.EqualsLiteral("mark") || + aEntryType.EqualsLiteral("measure")) { + for (PerformanceEntry* entry : mUserEntries) { + if (entry->GetEntryType().Equals(aEntryType)) { + aRetval.AppendElement(entry); + } + } + } +} + +void +Performance::GetEntriesByName(const nsAString& aName, + const Optional<nsAString>& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval) +{ + aRetval.Clear(); + + for (PerformanceEntry* entry : mResourceEntries) { + if (entry->GetName().Equals(aName) && + (!aEntryType.WasPassed() || + entry->GetEntryType().Equals(aEntryType.Value()))) { + aRetval.AppendElement(entry); + } + } + + for (PerformanceEntry* entry : mUserEntries) { + if (entry->GetName().Equals(aName) && + (!aEntryType.WasPassed() || + entry->GetEntryType().Equals(aEntryType.Value()))) { + aRetval.AppendElement(entry); + } + } + + aRetval.Sort(PerformanceEntryComparator()); +} + +void +Performance::ClearUserEntries(const Optional<nsAString>& aEntryName, + const nsAString& aEntryType) +{ + for (uint32_t i = 0; i < mUserEntries.Length();) { + if ((!aEntryName.WasPassed() || + mUserEntries[i]->GetName().Equals(aEntryName.Value())) && + (aEntryType.IsEmpty() || + mUserEntries[i]->GetEntryType().Equals(aEntryType))) { + mUserEntries.RemoveElementAt(i); + } else { + ++i; + } + } +} + +void +Performance::ClearResourceTimings() +{ + MOZ_ASSERT(NS_IsMainThread()); + mResourceEntries.Clear(); +} + +DOMHighResTimeStamp +Performance::RoundTime(double aTime) const +{ + // Round down to the nearest 20us, because if the timer is too accurate people + // can do nasty timing attacks with it. + const double maxResolutionMs = 0.020; + return floor(aTime / maxResolutionMs) * maxResolutionMs; +} + + +void +Performance::Mark(const nsAString& aName, ErrorResult& aRv) +{ + // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003. + if (mUserEntries.Length() >= mResourceTimingBufferSize) { + return; + } + + if (IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + RefPtr<PerformanceMark> performanceMark = + new PerformanceMark(GetAsISupports(), aName, Now()); + InsertUserEntry(performanceMark); + + if (profiler_is_active()) { + PROFILER_MARKER(NS_ConvertUTF16toUTF8(aName).get()); + } +} + +void +Performance::ClearMarks(const Optional<nsAString>& aName) +{ + ClearUserEntries(aName, NS_LITERAL_STRING("mark")); +} + +DOMHighResTimeStamp +Performance::ResolveTimestampFromName(const nsAString& aName, + ErrorResult& aRv) +{ + AutoTArray<RefPtr<PerformanceEntry>, 1> arr; + DOMHighResTimeStamp ts; + Optional<nsAString> typeParam; + nsAutoString str; + str.AssignLiteral("mark"); + typeParam = &str; + GetEntriesByName(aName, typeParam, arr); + if (!arr.IsEmpty()) { + return arr.LastElement()->StartTime(); + } + + if (!IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return 0; + } + + ts = GetPerformanceTimingFromString(aName); + if (!ts) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return 0; + } + + return ts - CreationTime(); +} + +void +Performance::Measure(const nsAString& aName, + const Optional<nsAString>& aStartMark, + const Optional<nsAString>& aEndMark, + ErrorResult& aRv) +{ + // Don't add the entry if the buffer is full. XXX should be removed by bug + // 1159003. + if (mUserEntries.Length() >= mResourceTimingBufferSize) { + return; + } + + DOMHighResTimeStamp startTime; + DOMHighResTimeStamp endTime; + + if (IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + if (aStartMark.WasPassed()) { + startTime = ResolveTimestampFromName(aStartMark.Value(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } else { + // Navigation start is used in this case, but since DOMHighResTimeStamp is + // in relation to navigation start, this will be zero if a name is not + // passed. + startTime = 0; + } + + if (aEndMark.WasPassed()) { + endTime = ResolveTimestampFromName(aEndMark.Value(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } else { + endTime = Now(); + } + + RefPtr<PerformanceMeasure> performanceMeasure = + new PerformanceMeasure(GetAsISupports(), aName, startTime, endTime); + InsertUserEntry(performanceMeasure); +} + +void +Performance::ClearMeasures(const Optional<nsAString>& aName) +{ + ClearUserEntries(aName, NS_LITERAL_STRING("measure")); +} + +void +Performance::LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const +{ + PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n", + aOwner.BeginReading(), + NS_ConvertUTF16toUTF8(aEntry->GetEntryType()).get(), + NS_ConvertUTF16toUTF8(aEntry->GetName()).get(), + aEntry->StartTime(), + aEntry->Duration(), + static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC)); +} + +void +Performance::TimingNotification(PerformanceEntry* aEntry, + const nsACString& aOwner, uint64_t aEpoch) +{ + PerformanceEntryEventInit init; + init.mBubbles = false; + init.mCancelable = false; + init.mName = aEntry->GetName(); + init.mEntryType = aEntry->GetEntryType(); + init.mStartTime = aEntry->StartTime(); + init.mDuration = aEntry->Duration(); + init.mEpoch = aEpoch; + init.mOrigin = NS_ConvertUTF8toUTF16(aOwner.BeginReading()); + + RefPtr<PerformanceEntryEvent> perfEntryEvent = + PerformanceEntryEvent::Constructor(this, NS_LITERAL_STRING("performanceentry"), init); + + nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner()); + if (et) { + bool dummy = false; + et->DispatchEvent(perfEntryEvent, &dummy); + } +} + +void +Performance::InsertUserEntry(PerformanceEntry* aEntry) +{ + mUserEntries.InsertElementSorted(aEntry, + PerformanceEntryComparator()); + + QueueEntry(aEntry); +} + +void +Performance::SetResourceTimingBufferSize(uint64_t aMaxSize) +{ + mResourceTimingBufferSize = aMaxSize; +} + +void +Performance::InsertResourceEntry(PerformanceEntry* aEntry) +{ + MOZ_ASSERT(aEntry); + MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize); + if (mResourceEntries.Length() >= mResourceTimingBufferSize) { + return; + } + + mResourceEntries.InsertElementSorted(aEntry, + PerformanceEntryComparator()); + if (mResourceEntries.Length() == mResourceTimingBufferSize) { + // call onresourcetimingbufferfull + DispatchBufferFullEvent(); + } + QueueEntry(aEntry); +} + +void +Performance::AddObserver(PerformanceObserver* aObserver) +{ + mObservers.AppendElementUnlessExists(aObserver); +} + +void +Performance::RemoveObserver(PerformanceObserver* aObserver) +{ + mObservers.RemoveElement(aObserver); +} + +void +Performance::NotifyObservers() +{ + mPendingNotificationObserversTask = false; + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, + PerformanceObserver, + Notify, ()); +} + +void +Performance::CancelNotificationObservers() +{ + mPendingNotificationObserversTask = false; +} + +class NotifyObserversTask final : public CancelableRunnable +{ +public: + explicit NotifyObserversTask(Performance* aPerformance) + : mPerformance(aPerformance) + { + MOZ_ASSERT(mPerformance); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(mPerformance); + mPerformance->NotifyObservers(); + return NS_OK; + } + + nsresult Cancel() override + { + mPerformance->CancelNotificationObservers(); + mPerformance = nullptr; + return NS_OK; + } + +private: + ~NotifyObserversTask() + { + } + + RefPtr<Performance> mPerformance; +}; + +void +Performance::RunNotificationObserversTask() +{ + mPendingNotificationObserversTask = true; + nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(this); + nsresult rv = NS_DispatchToCurrentThread(task); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPendingNotificationObserversTask = false; + } +} + +void +Performance::QueueEntry(PerformanceEntry* aEntry) +{ + if (mObservers.IsEmpty()) { + return; + } + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, + PerformanceObserver, + QueueEntry, (aEntry)); + + if (!mPendingNotificationObserversTask) { + RunNotificationObserversTask(); + } +} + +/* static */ bool +Performance::IsEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.enable_user_timing", false); + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + RefPtr<PrefEnabledRunnable> runnable = + new PrefEnabledRunnable(workerPrivate, + NS_LITERAL_CSTRING("dom.enable_user_timing")); + return runnable->Dispatch() && runnable->IsEnabled(); +} + +/* static */ bool +Performance::IsObserverEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.enable_performance_observer", false); + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + RefPtr<PrefEnabledRunnable> runnable = + new PrefEnabledRunnable(workerPrivate, + NS_LITERAL_CSTRING("dom.enable_performance_observer")); + + return runnable->Dispatch() && runnable->IsEnabled(); +} + +} // dom namespace +} // mozilla namespace |