mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 17:55:50 +00:00
8a68e6fb83
Currently the Gecko Profiler defines a moderate amount of stuff when MOZ_GECKO_PROFILER is undefined. It also #includes various headers, including JS ones. This is making it difficult to separate Gecko's media stack for inclusion in Servo. This patch greatly simplifies how things are exposed. The starting point is: - GeckoProfiler.h can be #included unconditionally; - everything else from the profiler must be guarded by MOZ_GECKO_PROFILER. In practice this introduces way too many #ifdefs, so the patch loosens it by adding no-op macros for a number of the most common operations. The net result is that #ifdefs and macros are used a bit more, but almost nothing is exposed in non-MOZ_GECKO_PROFILER builds (including ProfilerMarkerPayload.h and GeckoProfiler.h), and understanding what is exposed is much simpler than before. Note also that in BHR, ThreadStackHelper is now entirely absent in non-MOZ_GECKO_PROFILER builds.
585 lines
15 KiB
C++
585 lines
15 KiB
C++
/* -*- 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 "nsRFPService.h"
|
|
#include "PerformanceEntry.h"
|
|
#include "PerformanceMainThread.h"
|
|
#include "PerformanceMark.h"
|
|
#include "PerformanceMeasure.h"
|
|
#include "PerformanceObserver.h"
|
|
#include "PerformanceResourceTiming.h"
|
|
#include "PerformanceService.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/dom/PerformanceNavigationTiming.h"
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "WorkerPrivate.h"
|
|
#include "WorkerRunnable.h"
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
#include "ProfilerMarkerPayload.h"
|
|
#endif
|
|
|
|
#define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
using namespace workers;
|
|
|
|
namespace {
|
|
|
|
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(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)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<Performance> performance =
|
|
new PerformanceMainThread(aWindow, aDOMTiming, aChannel);
|
|
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()
|
|
{}
|
|
|
|
DOMHighResTimeStamp
|
|
Performance::Now() const
|
|
{
|
|
TimeDuration duration = TimeStamp::Now() - CreationTimeStamp();
|
|
return RoundTime(duration.ToMilliseconds());
|
|
}
|
|
|
|
DOMHighResTimeStamp
|
|
Performance::TimeOrigin()
|
|
{
|
|
if (!mPerformanceService) {
|
|
mPerformanceService = PerformanceService::GetOrCreate();
|
|
}
|
|
|
|
MOZ_ASSERT(mPerformanceService);
|
|
return mPerformanceService->TimeOrigin(CreationTimeStamp());
|
|
}
|
|
|
|
JSObject*
|
|
Performance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return PerformanceBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
void
|
|
Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
|
|
{
|
|
// We return an empty list when 'privacy.resistFingerprinting' is on.
|
|
if (nsContentUtils::ShouldResistFingerprinting()) {
|
|
aRetval.Clear();
|
|
return;
|
|
}
|
|
|
|
aRetval = mResourceEntries;
|
|
aRetval.AppendElements(mUserEntries);
|
|
aRetval.Sort(PerformanceEntryComparator());
|
|
}
|
|
|
|
void
|
|
Performance::GetEntriesByType(const nsAString& aEntryType,
|
|
nsTArray<RefPtr<PerformanceEntry>>& aRetval)
|
|
{
|
|
// We return an empty list when 'privacy.resistFingerprinting' is on.
|
|
if (nsContentUtils::ShouldResistFingerprinting()) {
|
|
aRetval.Clear();
|
|
return;
|
|
}
|
|
|
|
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();
|
|
|
|
// We return an empty list when 'privacy.resistFingerprinting' is on.
|
|
if (nsContentUtils::ShouldResistFingerprinting()) {
|
|
return;
|
|
}
|
|
|
|
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 5us, because if the timer is too accurate people
|
|
// can do nasty timing attacks with it. See similar code in the worker
|
|
// Performance implementation.
|
|
const double maxResolutionMs = 0.005;
|
|
return nsRFPService::ReduceTimePrecisionAsMSecs(
|
|
floor(aTime / maxResolutionMs) * maxResolutionMs);
|
|
}
|
|
|
|
|
|
void
|
|
Performance::Mark(const nsAString& aName, ErrorResult& aRv)
|
|
{
|
|
// We add nothing when 'privacy.resistFingerprinting' is on.
|
|
if (nsContentUtils::ShouldResistFingerprinting()) {
|
|
return;
|
|
}
|
|
|
|
if (IsPerformanceTimingAttribute(aName)) {
|
|
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
|
return;
|
|
}
|
|
|
|
RefPtr<PerformanceMark> performanceMark =
|
|
new PerformanceMark(GetParentObject(), aName, Now());
|
|
InsertUserEntry(performanceMark);
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
if (profiler_is_active()) {
|
|
profiler_add_marker(
|
|
"UserTiming",
|
|
MakeUnique<UserTimingMarkerPayload>(aName, TimeStamp::Now()));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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)
|
|
{
|
|
// We add nothing when 'privacy.resistFingerprinting' is on.
|
|
if (nsContentUtils::ShouldResistFingerprinting()) {
|
|
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(GetParentObject(), aName, startTime, endTime);
|
|
InsertUserEntry(performanceMeasure);
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
if (profiler_is_active()) {
|
|
TimeStamp startTimeStamp = CreationTimeStamp() +
|
|
TimeDuration::FromMilliseconds(startTime);
|
|
TimeStamp endTimeStamp = CreationTimeStamp() +
|
|
TimeDuration::FromMilliseconds(endTime);
|
|
profiler_add_marker(
|
|
"UserTiming",
|
|
MakeUnique<UserTimingMarkerPayload>(aName, startTimeStamp, endTimeStamp));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
|
|
// We won't add an entry when 'privacy.resistFingerprint' is true.
|
|
if (nsContentUtils::ShouldResistFingerprinting()) {
|
|
return;
|
|
}
|
|
|
|
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)
|
|
: CancelableRunnable("dom::NotifyObserversTask")
|
|
, 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::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();
|
|
}
|
|
|
|
void
|
|
Performance::MemoryPressure()
|
|
{
|
|
mUserEntries.Clear();
|
|
}
|
|
|
|
size_t
|
|
Performance::SizeOfUserEntries(mozilla::MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
size_t userEntries = 0;
|
|
for (const PerformanceEntry* entry : mUserEntries) {
|
|
userEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
return userEntries;
|
|
}
|
|
|
|
size_t
|
|
Performance::SizeOfResourceEntries(mozilla::MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
size_t resourceEntries = 0;
|
|
for (const PerformanceEntry* entry : mResourceEntries) {
|
|
resourceEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
return resourceEntries;
|
|
}
|
|
|
|
} // dom namespace
|
|
} // mozilla namespace
|