2016-06-09 17:04:42 +00:00
|
|
|
/* -*- 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"
|
|
|
|
|
2023-05-29 15:37:51 +00:00
|
|
|
#include <sstream>
|
|
|
|
|
2024-03-09 23:04:01 +00:00
|
|
|
#if defined(XP_LINUX)
|
|
|
|
# include <fcntl.h>
|
|
|
|
# include <sys/mman.h>
|
|
|
|
#endif
|
|
|
|
|
2023-12-21 18:17:41 +00:00
|
|
|
#include "ETWTools.h"
|
2016-06-09 17:04:42 +00:00
|
|
|
#include "GeckoProfiler.h"
|
2017-06-06 03:45:14 +00:00
|
|
|
#include "nsRFPService.h"
|
2016-06-09 17:04:42 +00:00
|
|
|
#include "PerformanceEntry.h"
|
|
|
|
#include "PerformanceMainThread.h"
|
|
|
|
#include "PerformanceMark.h"
|
|
|
|
#include "PerformanceMeasure.h"
|
|
|
|
#include "PerformanceObserver.h"
|
|
|
|
#include "PerformanceResourceTiming.h"
|
2016-11-17 09:00:05 +00:00
|
|
|
#include "PerformanceService.h"
|
2016-06-09 17:04:42 +00:00
|
|
|
#include "PerformanceWorker.h"
|
2019-12-05 04:44:32 +00:00
|
|
|
#include "mozilla/BasePrincipal.h"
|
2016-06-09 17:04:42 +00:00
|
|
|
#include "mozilla/ErrorResult.h"
|
2022-05-31 16:48:14 +00:00
|
|
|
#include "mozilla/dom/MessagePortBinding.h"
|
2016-06-09 17:04:42 +00:00
|
|
|
#include "mozilla/dom/PerformanceBinding.h"
|
|
|
|
#include "mozilla/dom/PerformanceEntryEvent.h"
|
|
|
|
#include "mozilla/dom/PerformanceNavigationBinding.h"
|
|
|
|
#include "mozilla/dom/PerformanceObserverBinding.h"
|
2016-04-17 20:03:28 +00:00
|
|
|
#include "mozilla/dom/PerformanceNavigationTiming.h"
|
2016-06-09 17:04:42 +00:00
|
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
2023-05-29 15:37:51 +00:00
|
|
|
#include "mozilla/TimeStamp.h"
|
2018-01-31 07:24:08 +00:00
|
|
|
#include "mozilla/dom/WorkerPrivate.h"
|
|
|
|
#include "mozilla/dom/WorkerRunnable.h"
|
2022-04-26 19:30:52 +00:00
|
|
|
#include "mozilla/dom/WorkerScope.h"
|
2016-06-09 17:04:42 +00:00
|
|
|
|
|
|
|
#define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
|
|
|
|
|
2020-11-04 17:04:01 +00:00
|
|
|
namespace mozilla::dom {
|
2016-06-09 17:04:42 +00:00
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
enum class Performance::ResolveTimestampAttribute {
|
|
|
|
Start,
|
|
|
|
End,
|
|
|
|
Duration,
|
|
|
|
};
|
|
|
|
|
2017-08-29 23:02:48 +00:00
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance)
|
2016-06-09 17:04:42 +00:00
|
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance, DOMEventTargetHelper,
|
2019-04-08 23:21:08 +00:00
|
|
|
mUserEntries, mResourceEntries,
|
2020-12-11 22:27:27 +00:00
|
|
|
mSecondaryResourceEntries, mObservers);
|
2016-06-09 17:04:42 +00:00
|
|
|
|
|
|
|
NS_IMPL_ADDREF_INHERITED(Performance, DOMEventTargetHelper)
|
|
|
|
NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper)
|
|
|
|
|
2019-02-25 22:05:29 +00:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Performance> Performance::CreateForMainThread(
|
2016-06-09 17:04:42 +00:00
|
|
|
nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
|
|
|
|
nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel) {
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2020-03-25 14:09:42 +00:00
|
|
|
MOZ_ASSERT(aWindow->AsGlobal());
|
2022-11-29 13:34:19 +00:00
|
|
|
RefPtr<Performance> performance =
|
|
|
|
new PerformanceMainThread(aWindow, aDOMTiming, aChannel);
|
2016-06-09 17:04:42 +00:00
|
|
|
return performance.forget();
|
|
|
|
}
|
|
|
|
|
2019-02-25 22:05:29 +00:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Performance> Performance::CreateForWorker(
|
2023-06-06 06:36:50 +00:00
|
|
|
WorkerGlobalScope* aGlobalScope) {
|
|
|
|
MOZ_ASSERT(aGlobalScope);
|
|
|
|
// aWorkerPrivate->AssertIsOnWorkerThread();
|
2016-06-09 17:04:42 +00:00
|
|
|
|
2023-06-06 06:36:50 +00:00
|
|
|
RefPtr<Performance> performance = new PerformanceWorker(aGlobalScope);
|
2016-06-09 17:04:42 +00:00
|
|
|
return performance.forget();
|
|
|
|
}
|
|
|
|
|
2022-04-26 19:30:52 +00:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Performance> Performance::Get(JSContext* aCx,
|
|
|
|
nsIGlobalObject* aGlobal) {
|
|
|
|
RefPtr<Performance> performance;
|
2024-01-29 17:25:25 +00:00
|
|
|
if (NS_IsMainThread()) {
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
|
|
|
|
if (!window) {
|
2022-04-26 19:30:52 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2024-01-29 17:25:25 +00:00
|
|
|
performance = window->GetPerformance();
|
|
|
|
return performance.forget();
|
2022-04-26 19:30:52 +00:00
|
|
|
}
|
|
|
|
|
2024-01-29 17:25:25 +00:00
|
|
|
const WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
|
|
|
|
if (!workerPrivate) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
WorkerGlobalScope* scope = workerPrivate->GlobalScope();
|
|
|
|
MOZ_ASSERT(scope);
|
|
|
|
performance = scope->GetPerformance();
|
|
|
|
|
2022-04-26 19:30:52 +00:00
|
|
|
return performance.forget();
|
|
|
|
}
|
|
|
|
|
2022-11-29 13:34:19 +00:00
|
|
|
Performance::Performance(nsIGlobalObject* aGlobal)
|
2020-11-20 22:39:58 +00:00
|
|
|
: DOMEventTargetHelper(aGlobal),
|
|
|
|
mResourceTimingBufferSize(kDefaultResourceTimingBufferSize),
|
2016-06-09 17:04:42 +00:00
|
|
|
mPendingNotificationObserversTask(false),
|
2019-04-08 23:21:08 +00:00
|
|
|
mPendingResourceTimingBufferFullEvent(false),
|
2023-01-27 09:17:24 +00:00
|
|
|
mRTPCallerType(aGlobal->GetRTPCallerType()),
|
|
|
|
mCrossOriginIsolated(aGlobal->CrossOriginIsolated()),
|
2023-06-26 07:17:39 +00:00
|
|
|
mShouldResistFingerprinting(aGlobal->ShouldResistFingerprinting(
|
|
|
|
RFPTarget::ReduceTimerPrecision)) {}
|
2016-06-09 17:04:42 +00:00
|
|
|
|
2020-03-04 09:10:03 +00:00
|
|
|
Performance::~Performance() = default;
|
2016-06-09 17:04:42 +00:00
|
|
|
|
2020-09-01 15:02:54 +00:00
|
|
|
DOMHighResTimeStamp Performance::TimeStampToDOMHighResForRendering(
|
|
|
|
TimeStamp aTimeStamp) const {
|
|
|
|
DOMHighResTimeStamp stamp = GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp);
|
2022-11-29 13:34:19 +00:00
|
|
|
// 0 is an inappropriate mixin for this this area; however CSS Animations
|
|
|
|
// needs to have it's Time Reduction Logic refactored, so it's currently
|
|
|
|
// only clamping for RFP mode. RFP mode gives a much lower time precision,
|
|
|
|
// so we accept the security leak here for now.
|
|
|
|
return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp, 0,
|
|
|
|
mRTPCallerType);
|
2020-09-01 15:02:54 +00:00
|
|
|
}
|
|
|
|
|
2016-12-16 08:07:04 +00:00
|
|
|
DOMHighResTimeStamp Performance::Now() {
|
2018-03-10 02:12:53 +00:00
|
|
|
DOMHighResTimeStamp rawTime = NowUnclamped();
|
2020-04-07 16:34:51 +00:00
|
|
|
|
2022-11-29 13:34:19 +00:00
|
|
|
// XXX: Removing this caused functions in pkcs11f.h to fail.
|
|
|
|
// Bug 1628021 investigates the root cause - it involves initializing
|
|
|
|
// the RNG service (part of GetRandomTimelineSeed()) off-main-thread
|
|
|
|
// but the underlying cause hasn't been identified yet.
|
|
|
|
if (mRTPCallerType == RTPCallerType::SystemPrincipal) {
|
2020-04-07 16:34:51 +00:00
|
|
|
return rawTime;
|
|
|
|
}
|
|
|
|
|
2020-03-25 14:09:42 +00:00
|
|
|
return nsRFPService::ReduceTimePrecisionAsMSecs(
|
2022-11-29 13:34:19 +00:00
|
|
|
rawTime, GetRandomTimelineSeed(), mRTPCallerType);
|
2016-12-16 08:07:04 +00:00
|
|
|
}
|
|
|
|
|
2018-03-10 02:12:53 +00:00
|
|
|
DOMHighResTimeStamp Performance::NowUnclamped() const {
|
2021-07-14 18:18:17 +00:00
|
|
|
TimeDuration duration = TimeStamp::Now() - CreationTimeStamp();
|
2018-03-10 02:12:53 +00:00
|
|
|
return duration.ToMilliseconds();
|
|
|
|
}
|
|
|
|
|
2016-11-17 09:00:05 +00:00
|
|
|
DOMHighResTimeStamp Performance::TimeOrigin() {
|
|
|
|
if (!mPerformanceService) {
|
|
|
|
mPerformanceService = PerformanceService::GetOrCreate();
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT(mPerformanceService);
|
2018-03-09 15:29:33 +00:00
|
|
|
DOMHighResTimeStamp rawTimeOrigin =
|
|
|
|
mPerformanceService->TimeOrigin(CreationTimeStamp());
|
2018-03-13 17:36:34 +00:00
|
|
|
// Time Origin is an absolute timestamp, so we supply a 0 context mix-in
|
2022-11-29 13:34:19 +00:00
|
|
|
return nsRFPService::ReduceTimePrecisionAsMSecs(rawTimeOrigin, 0,
|
|
|
|
mRTPCallerType);
|
2016-11-17 09:00:05 +00:00
|
|
|
}
|
|
|
|
|
2016-06-09 17:04:42 +00:00
|
|
|
JSObject* Performance::WrapObject(JSContext* aCx,
|
|
|
|
JS::Handle<JSObject*> aGivenProto) {
|
2018-06-25 21:20:54 +00:00
|
|
|
return Performance_Binding::Wrap(aCx, this, aGivenProto);
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
|
2020-05-05 14:15:00 +00:00
|
|
|
aRetval = mResourceEntries.Clone();
|
2016-06-09 17:04:42 +00:00
|
|
|
aRetval.AppendElements(mUserEntries);
|
|
|
|
aRetval.Sort(PerformanceEntryComparator());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::GetEntriesByType(
|
|
|
|
const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
|
|
|
|
if (aEntryType.EqualsLiteral("resource")) {
|
2020-05-05 14:15:00 +00:00
|
|
|
aRetval = mResourceEntries.Clone();
|
2016-06-09 17:04:42 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aRetval.Clear();
|
|
|
|
|
|
|
|
if (aEntryType.EqualsLiteral("mark") || aEntryType.EqualsLiteral("measure")) {
|
2021-01-24 12:16:55 +00:00
|
|
|
RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
|
2016-06-09 17:04:42 +00:00
|
|
|
for (PerformanceEntry* entry : mUserEntries) {
|
2021-01-24 12:16:55 +00:00
|
|
|
if (entry->GetEntryType() == entryType) {
|
2016-06-09 17:04:42 +00:00
|
|
|
aRetval.AppendElement(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::GetEntriesByName(
|
|
|
|
const nsAString& aName, const Optional<nsAString>& aEntryType,
|
|
|
|
nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
|
|
|
|
aRetval.Clear();
|
|
|
|
|
2021-01-24 12:16:55 +00:00
|
|
|
RefPtr<nsAtom> name = NS_Atomize(aName);
|
|
|
|
RefPtr<nsAtom> entryType =
|
|
|
|
aEntryType.WasPassed() ? NS_Atomize(aEntryType.Value()) : nullptr;
|
|
|
|
|
2021-09-08 19:22:52 +00:00
|
|
|
if (entryType) {
|
|
|
|
if (entryType == nsGkAtoms::mark || entryType == nsGkAtoms::measure) {
|
|
|
|
for (PerformanceEntry* entry : mUserEntries) {
|
|
|
|
if (entry->GetName() == name && entry->GetEntryType() == entryType) {
|
|
|
|
aRetval.AppendElement(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (entryType == nsGkAtoms::resource) {
|
|
|
|
for (PerformanceEntry* entry : mResourceEntries) {
|
|
|
|
MOZ_ASSERT(entry->GetEntryType() == entryType);
|
|
|
|
if (entry->GetName() == name) {
|
|
|
|
aRetval.AppendElement(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Invalid entryType
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsTArray<PerformanceEntry*> qualifiedResourceEntries;
|
|
|
|
nsTArray<PerformanceEntry*> qualifiedUserEntries;
|
2019-10-23 11:42:10 +00:00
|
|
|
// ::Measure expects that results from this function are already
|
|
|
|
// passed through ReduceTimePrecision. mResourceEntries and mUserEntries
|
|
|
|
// are, so the invariant holds.
|
2016-06-09 17:04:42 +00:00
|
|
|
for (PerformanceEntry* entry : mResourceEntries) {
|
2021-09-08 19:22:52 +00:00
|
|
|
if (entry->GetName() == name) {
|
|
|
|
qualifiedResourceEntries.AppendElement(entry);
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (PerformanceEntry* entry : mUserEntries) {
|
2021-09-08 19:22:52 +00:00
|
|
|
if (entry->GetName() == name) {
|
|
|
|
qualifiedUserEntries.AppendElement(entry);
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-08 19:22:52 +00:00
|
|
|
size_t resourceEntriesIdx = 0, userEntriesIdx = 0;
|
|
|
|
aRetval.SetCapacity(qualifiedResourceEntries.Length() +
|
|
|
|
qualifiedUserEntries.Length());
|
|
|
|
|
|
|
|
PerformanceEntryComparator comparator;
|
|
|
|
|
|
|
|
while (resourceEntriesIdx < qualifiedResourceEntries.Length() &&
|
|
|
|
userEntriesIdx < qualifiedUserEntries.Length()) {
|
|
|
|
if (comparator.LessThan(qualifiedResourceEntries[resourceEntriesIdx],
|
|
|
|
qualifiedUserEntries[userEntriesIdx])) {
|
|
|
|
aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
|
|
|
|
++resourceEntriesIdx;
|
|
|
|
} else {
|
|
|
|
aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
|
|
|
|
++userEntriesIdx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (resourceEntriesIdx < qualifiedResourceEntries.Length()) {
|
|
|
|
aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
|
|
|
|
++resourceEntriesIdx;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (userEntriesIdx < qualifiedUserEntries.Length()) {
|
|
|
|
aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
|
|
|
|
++userEntriesIdx;
|
|
|
|
}
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
2021-02-09 18:54:48 +00:00
|
|
|
void Performance::GetEntriesByTypeForObserver(
|
|
|
|
const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
|
|
|
|
GetEntriesByType(aEntryType, aRetval);
|
|
|
|
}
|
|
|
|
|
2016-06-09 17:04:42 +00:00
|
|
|
void Performance::ClearUserEntries(const Optional<nsAString>& aEntryName,
|
|
|
|
const nsAString& aEntryType) {
|
2021-01-24 12:16:55 +00:00
|
|
|
MOZ_ASSERT(!aEntryType.IsEmpty());
|
|
|
|
RefPtr<nsAtom> name =
|
|
|
|
aEntryName.WasPassed() ? NS_Atomize(aEntryName.Value()) : nullptr;
|
|
|
|
RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
|
|
|
|
mUserEntries.RemoveElementsBy([name, entryType](const auto& entry) {
|
|
|
|
return (!name || entry->GetName() == name) &&
|
|
|
|
(entry->GetEntryType() == entryType);
|
2020-06-08 09:01:48 +00:00
|
|
|
});
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::ClearResourceTimings() { mResourceEntries.Clear(); }
|
|
|
|
|
2023-12-21 18:17:41 +00:00
|
|
|
struct UserTimingMarker : public BaseMarkerType<UserTimingMarker> {
|
|
|
|
static constexpr const char* Name = "UserTiming";
|
|
|
|
static constexpr const char* Description =
|
|
|
|
"UserTimingMeasure is created using the DOM API performance.measure().";
|
|
|
|
|
|
|
|
using MS = MarkerSchema;
|
|
|
|
static constexpr MS::PayloadField PayloadFields[] = {
|
|
|
|
{"name", MS::InputType::String, "User Marker Name", MS::Format::String,
|
|
|
|
MS::PayloadFlags::Searchable},
|
|
|
|
{"entryType", MS::InputType::Boolean, "Entry Type"},
|
|
|
|
{"startMark", MS::InputType::String, "Start Mark"},
|
|
|
|
{"endMark", MS::InputType::String, "End Mark"}};
|
|
|
|
|
|
|
|
static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
|
|
|
|
MS::Location::MarkerTable};
|
|
|
|
static constexpr const char* AllLabels = "{marker.data.name}";
|
|
|
|
|
|
|
|
static constexpr MS::ETWMarkerGroup Group = MS::ETWMarkerGroup::UserMarkers;
|
|
|
|
|
2020-11-18 21:53:09 +00:00
|
|
|
static void StreamJSONMarkerData(
|
|
|
|
baseprofiler::SpliceableJSONWriter& aWriter,
|
|
|
|
const ProfilerString16View& aName, bool aIsMeasure,
|
|
|
|
const Maybe<ProfilerString16View>& aStartMark,
|
|
|
|
const Maybe<ProfilerString16View>& aEndMark) {
|
2023-12-21 18:17:41 +00:00
|
|
|
StreamJSONMarkerDataImpl(
|
|
|
|
aWriter, aName,
|
|
|
|
aIsMeasure ? MakeStringSpan("measure") : MakeStringSpan("mark"),
|
|
|
|
aStartMark, aEndMark);
|
2020-11-18 21:53:09 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-04-26 19:30:52 +00:00
|
|
|
already_AddRefed<PerformanceMark> Performance::Mark(
|
|
|
|
JSContext* aCx, const nsAString& aName,
|
|
|
|
const PerformanceMarkOptions& aMarkOptions, ErrorResult& aRv) {
|
|
|
|
nsCOMPtr<nsIGlobalObject> parent = GetParentObject();
|
|
|
|
if (!parent || parent->IsDying() || !parent->HasJSGlobal()) {
|
|
|
|
aRv.ThrowInvalidStateError("Global object is unavailable");
|
|
|
|
return nullptr;
|
2017-06-15 08:48:27 +00:00
|
|
|
}
|
|
|
|
|
2022-04-26 19:30:52 +00:00
|
|
|
GlobalObject global(aCx, parent->GetGlobalJSObject());
|
|
|
|
if (global.Failed()) {
|
|
|
|
aRv.ThrowInvalidStateError("Global object is unavailable");
|
|
|
|
return nullptr;
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<PerformanceMark> performanceMark =
|
2022-04-26 19:30:52 +00:00
|
|
|
PerformanceMark::Constructor(global, aName, aMarkOptions, aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2023-01-26 17:01:35 +00:00
|
|
|
InsertUserEntry(performanceMark);
|
2016-06-09 17:04:42 +00:00
|
|
|
|
2023-12-21 18:17:41 +00:00
|
|
|
if (profiler_is_collecting_markers()) {
|
2019-10-09 21:25:11 +00:00
|
|
|
Maybe<uint64_t> innerWindowId;
|
|
|
|
if (GetOwner()) {
|
|
|
|
innerWindowId = Some(GetOwner()->WindowID());
|
|
|
|
}
|
2023-03-22 10:12:50 +00:00
|
|
|
TimeStamp startTimeStamp =
|
|
|
|
CreationTimeStamp() +
|
|
|
|
TimeDuration::FromMilliseconds(performanceMark->UnclampedStartTime());
|
2020-11-18 21:53:09 +00:00
|
|
|
profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
|
2023-03-22 10:12:50 +00:00
|
|
|
MarkerOptions(MarkerTiming::InstantAt(startTimeStamp),
|
|
|
|
MarkerInnerWindowId(innerWindowId)),
|
|
|
|
UserTimingMarker{}, aName, /* aIsMeasure */ false,
|
|
|
|
Nothing{}, Nothing{});
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
2022-04-26 19:30:52 +00:00
|
|
|
|
|
|
|
return performanceMark.forget();
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::ClearMarks(const Optional<nsAString>& aName) {
|
|
|
|
ClearUserEntries(aName, u"mark"_ns);
|
|
|
|
}
|
|
|
|
|
2022-08-18 21:18:24 +00:00
|
|
|
// To be removed once bug 1124165 lands
|
|
|
|
bool Performance::IsPerformanceTimingAttribute(const nsAString& aName) const {
|
|
|
|
// Note that toJSON is added to this list due to bug 1047848
|
|
|
|
static const char* attributes[] = {"navigationStart",
|
|
|
|
"unloadEventStart",
|
|
|
|
"unloadEventEnd",
|
|
|
|
"redirectStart",
|
|
|
|
"redirectEnd",
|
|
|
|
"fetchStart",
|
|
|
|
"domainLookupStart",
|
|
|
|
"domainLookupEnd",
|
|
|
|
"connectStart",
|
|
|
|
"secureConnectionStart",
|
|
|
|
"connectEnd",
|
|
|
|
"requestStart",
|
|
|
|
"responseStart",
|
|
|
|
"responseEnd",
|
|
|
|
"domLoading",
|
|
|
|
"domInteractive",
|
|
|
|
"domContentLoadedEventStart",
|
|
|
|
"domContentLoadedEventEnd",
|
|
|
|
"domComplete",
|
|
|
|
"loadEventStart",
|
|
|
|
"loadEventEnd",
|
|
|
|
nullptr};
|
|
|
|
|
|
|
|
for (uint32_t i = 0; attributes[i]; ++i) {
|
|
|
|
if (aName.EqualsASCII(attributes[i])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithString(
|
2023-03-22 10:12:50 +00:00
|
|
|
const nsAString& aName, ErrorResult& aRv, bool aReturnUnclamped) {
|
2022-08-18 21:18:25 +00:00
|
|
|
if (IsPerformanceTimingAttribute(aName)) {
|
|
|
|
return ConvertNameToTimestamp(aName, aRv);
|
|
|
|
}
|
|
|
|
|
2024-01-24 18:02:35 +00:00
|
|
|
RefPtr<nsAtom> name = NS_Atomize(aName);
|
|
|
|
// Just loop over the user entries
|
|
|
|
for (const PerformanceEntry* entry : Reversed(mUserEntries)) {
|
|
|
|
if (entry->GetName() == name && entry->GetEntryType() == nsGkAtoms::mark) {
|
|
|
|
if (aReturnUnclamped) {
|
|
|
|
return entry->UnclampedStartTime();
|
|
|
|
}
|
|
|
|
return entry->StartTime();
|
2023-03-22 10:12:50 +00:00
|
|
|
}
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
2022-08-18 21:18:25 +00:00
|
|
|
nsPrintfCString errorMsg("Given mark name, %s, is unknown",
|
|
|
|
NS_ConvertUTF16toUTF8(aName).get());
|
|
|
|
aRv.ThrowSyntaxError(errorMsg);
|
|
|
|
return 0;
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
|
|
|
|
const ResolveTimestampAttribute aAttribute,
|
|
|
|
const DOMHighResTimeStamp aTimestamp, ErrorResult& aRv) {
|
|
|
|
if (aTimestamp < 0) {
|
|
|
|
nsAutoCString attributeName;
|
|
|
|
switch (aAttribute) {
|
|
|
|
case ResolveTimestampAttribute::Start:
|
|
|
|
attributeName = "start";
|
|
|
|
break;
|
|
|
|
case ResolveTimestampAttribute::End:
|
|
|
|
attributeName = "end";
|
|
|
|
break;
|
|
|
|
case ResolveTimestampAttribute::Duration:
|
|
|
|
attributeName = "duration";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsPrintfCString errorMsg("Given attribute %s cannot be negative",
|
|
|
|
attributeName.get());
|
|
|
|
aRv.ThrowTypeError(errorMsg);
|
2017-06-15 08:48:27 +00:00
|
|
|
}
|
2022-05-31 16:48:14 +00:00
|
|
|
return aTimestamp;
|
|
|
|
}
|
2017-06-15 08:48:27 +00:00
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
DOMHighResTimeStamp Performance::ConvertMarkToTimestamp(
|
|
|
|
const ResolveTimestampAttribute aAttribute,
|
2023-03-22 10:12:50 +00:00
|
|
|
const OwningStringOrDouble& aMarkNameOrTimestamp, ErrorResult& aRv,
|
|
|
|
bool aReturnUnclamped) {
|
2022-05-31 16:48:14 +00:00
|
|
|
if (aMarkNameOrTimestamp.IsString()) {
|
|
|
|
return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp.GetAsString(),
|
2023-03-22 10:12:50 +00:00
|
|
|
aRv, aReturnUnclamped);
|
2022-05-31 16:48:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ConvertMarkToTimestampWithDOMHighResTimeStamp(
|
|
|
|
aAttribute, aMarkNameOrTimestamp.GetAsDouble(), aRv);
|
|
|
|
}
|
|
|
|
|
2022-08-18 21:18:25 +00:00
|
|
|
DOMHighResTimeStamp Performance::ConvertNameToTimestamp(const nsAString& aName,
|
|
|
|
ErrorResult& aRv) {
|
|
|
|
if (!IsGlobalObjectWindow()) {
|
|
|
|
nsPrintfCString errorMsg(
|
|
|
|
"Cannot get PerformanceTiming attribute values for non-Window global "
|
|
|
|
"object. Given: %s",
|
|
|
|
NS_ConvertUTF16toUTF8(aName).get());
|
|
|
|
aRv.ThrowTypeError(errorMsg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aName.EqualsASCII("navigationStart")) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We use GetPerformanceTimingFromString, rather than calling the
|
|
|
|
// navigationStart method timing function directly, because the former handles
|
|
|
|
// reducing precision against timing attacks.
|
|
|
|
const DOMHighResTimeStamp startTime =
|
|
|
|
GetPerformanceTimingFromString(u"navigationStart"_ns);
|
|
|
|
const DOMHighResTimeStamp endTime = GetPerformanceTimingFromString(aName);
|
|
|
|
MOZ_ASSERT(endTime >= 0);
|
|
|
|
if (endTime == 0) {
|
|
|
|
nsPrintfCString errorMsg(
|
|
|
|
"Given PerformanceTiming attribute, %s, isn't available yet",
|
|
|
|
NS_ConvertUTF16toUTF8(aName).get());
|
|
|
|
aRv.ThrowInvalidAccessError(errorMsg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return endTime - startTime;
|
|
|
|
}
|
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
DOMHighResTimeStamp Performance::ResolveEndTimeForMeasure(
|
|
|
|
const Optional<nsAString>& aEndMark,
|
2023-03-22 10:12:50 +00:00
|
|
|
const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv,
|
|
|
|
bool aReturnUnclamped) {
|
2016-06-09 17:04:42 +00:00
|
|
|
DOMHighResTimeStamp endTime;
|
2022-05-31 16:48:14 +00:00
|
|
|
if (aEndMark.WasPassed()) {
|
2023-03-22 10:12:50 +00:00
|
|
|
endTime = ConvertMarkToTimestampWithString(aEndMark.Value(), aRv,
|
|
|
|
aReturnUnclamped);
|
2022-05-31 16:48:14 +00:00
|
|
|
} else if (aOptions && aOptions->mEnd.WasPassed()) {
|
2023-03-22 10:12:50 +00:00
|
|
|
endTime =
|
|
|
|
ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
|
|
|
|
aOptions->mEnd.Value(), aRv, aReturnUnclamped);
|
2022-05-31 16:48:14 +00:00
|
|
|
} else if (aOptions && aOptions->mStart.WasPassed() &&
|
|
|
|
aOptions->mDuration.WasPassed()) {
|
2023-03-22 10:12:50 +00:00
|
|
|
const DOMHighResTimeStamp start =
|
|
|
|
ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
|
|
|
|
aOptions->mStart.Value(), aRv, aReturnUnclamped);
|
2022-05-31 16:48:14 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return 0;
|
|
|
|
}
|
2016-06-09 17:04:42 +00:00
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
const DOMHighResTimeStamp duration =
|
|
|
|
ConvertMarkToTimestampWithDOMHighResTimeStamp(
|
|
|
|
ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
|
|
|
|
aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return 0;
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
2022-05-31 16:48:14 +00:00
|
|
|
|
|
|
|
endTime = start + duration;
|
|
|
|
} else {
|
|
|
|
endTime = Now();
|
|
|
|
}
|
|
|
|
|
|
|
|
return endTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
DOMHighResTimeStamp Performance::ResolveStartTimeForMeasure(
|
|
|
|
const Maybe<const nsAString&>& aStartMark,
|
2023-03-22 10:12:50 +00:00
|
|
|
const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv,
|
|
|
|
bool aReturnUnclamped) {
|
2022-05-31 16:48:14 +00:00
|
|
|
DOMHighResTimeStamp startTime;
|
|
|
|
if (aOptions && aOptions->mStart.WasPassed()) {
|
2023-03-22 10:12:50 +00:00
|
|
|
startTime =
|
|
|
|
ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
|
|
|
|
aOptions->mStart.Value(), aRv, aReturnUnclamped);
|
2022-05-31 16:48:14 +00:00
|
|
|
} else if (aOptions && aOptions->mDuration.WasPassed() &&
|
|
|
|
aOptions->mEnd.WasPassed()) {
|
|
|
|
const DOMHighResTimeStamp duration =
|
|
|
|
ConvertMarkToTimestampWithDOMHighResTimeStamp(
|
|
|
|
ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
|
|
|
|
aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-22 10:12:50 +00:00
|
|
|
const DOMHighResTimeStamp end =
|
|
|
|
ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
|
|
|
|
aOptions->mEnd.Value(), aRv, aReturnUnclamped);
|
2022-05-31 16:48:14 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
startTime = end - duration;
|
|
|
|
} else if (aStartMark) {
|
2023-03-22 10:12:50 +00:00
|
|
|
startTime =
|
|
|
|
ConvertMarkToTimestampWithString(*aStartMark, aRv, aReturnUnclamped);
|
2016-06-09 17:04:42 +00:00
|
|
|
} else {
|
|
|
|
startTime = 0;
|
|
|
|
}
|
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
return startTime;
|
|
|
|
}
|
|
|
|
|
2023-05-29 15:37:51 +00:00
|
|
|
static std::string GetMarkerFilename() {
|
|
|
|
std::stringstream s;
|
2023-10-06 20:56:06 +00:00
|
|
|
if (char* markerDir = getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
|
|
|
|
s << markerDir << "/";
|
|
|
|
}
|
2023-05-29 15:37:51 +00:00
|
|
|
#ifdef XP_WIN
|
|
|
|
s << "marker-" << GetCurrentProcessId() << ".txt";
|
|
|
|
#else
|
|
|
|
s << "marker-" << getpid() << ".txt";
|
|
|
|
#endif
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
2023-12-21 18:17:41 +00:00
|
|
|
std::pair<TimeStamp, TimeStamp> Performance::GetTimeStampsForMarker(
|
|
|
|
const Maybe<const nsAString&>& aStartMark,
|
|
|
|
const Optional<nsAString>& aEndMark,
|
|
|
|
const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv) {
|
|
|
|
const DOMHighResTimeStamp unclampedStartTime = ResolveStartTimeForMeasure(
|
|
|
|
aStartMark, aOptions, aRv, /* aReturnUnclamped */ true);
|
|
|
|
const DOMHighResTimeStamp unclampedEndTime =
|
|
|
|
ResolveEndTimeForMeasure(aEndMark, aOptions, aRv, /* aReturnUnclamped */
|
|
|
|
true);
|
|
|
|
|
|
|
|
TimeStamp startTimeStamp =
|
|
|
|
CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedStartTime);
|
|
|
|
TimeStamp endTimeStamp =
|
|
|
|
CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedEndTime);
|
|
|
|
|
|
|
|
return std::make_pair(startTimeStamp, endTimeStamp);
|
|
|
|
}
|
|
|
|
|
2024-03-09 23:04:01 +00:00
|
|
|
static FILE* MaybeOpenMarkerFile() {
|
|
|
|
if (!getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef XP_LINUX
|
|
|
|
// We treat marker files similar to Jitdump files (see PerfSpewer.cpp) and
|
|
|
|
// mmap them if needed.
|
|
|
|
int fd = open(GetMarkerFilename().c_str(), O_CREAT | O_TRUNC | O_RDWR, 0666);
|
|
|
|
FILE* markerFile = fdopen(fd, "w+");
|
|
|
|
|
|
|
|
if (!markerFile) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// On Linux and Android, we need to mmap the file so that the path makes it
|
|
|
|
// into the perf.data file or into samply.
|
|
|
|
// On non-Android, make the mapping executable, otherwise the MMAP event may
|
|
|
|
// not be recorded by perf (see perf_event_open mmap_data).
|
|
|
|
// But on Android, don't make the mapping executable, because doing so can
|
|
|
|
// make the mmap call fail on some Android devices. It's also not required on
|
|
|
|
// Android because simpleperf sets mmap_data = 1 for unrelated reasons (it
|
|
|
|
// wants to know about vdex files for Java JIT profiling, see
|
|
|
|
// SetRecordNotExecutableMaps).
|
|
|
|
int protection = PROT_READ;
|
|
|
|
# ifndef ANDROID
|
|
|
|
protection |= PROT_EXEC;
|
|
|
|
# endif
|
|
|
|
|
|
|
|
// Mmap just the first page - that's enough to ensure the path makes it into
|
|
|
|
// the recording.
|
|
|
|
long page_size = sysconf(_SC_PAGESIZE);
|
|
|
|
void* mmap_address = mmap(nullptr, page_size, protection, MAP_PRIVATE, fd, 0);
|
|
|
|
if (mmap_address == MAP_FAILED) {
|
|
|
|
fclose(markerFile);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return markerFile;
|
|
|
|
#else
|
|
|
|
// On macOS, we just need to `open` or `fopen` the marker file, and samply
|
|
|
|
// will know its path because it hooks those functions - no mmap needed.
|
|
|
|
// On Windows, there's no need to use MOZ_USE_PERFORMANCE_MARKER_FILE because
|
|
|
|
// we have ETW trace events for UserTiming measures. Still, we want this code
|
|
|
|
// to compile successfully on Windows, so we use fopen rather than
|
|
|
|
// open+fdopen.
|
|
|
|
return fopen(GetMarkerFilename().c_str(), "w+");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2023-05-29 15:37:51 +00:00
|
|
|
// This emits markers to an external marker-[pid].txt file for use by an
|
|
|
|
// external profiler like samply or etw-gecko
|
|
|
|
void Performance::MaybeEmitExternalProfilerMarker(
|
|
|
|
const nsAString& aName, Maybe<const PerformanceMeasureOptions&> aOptions,
|
|
|
|
Maybe<const nsAString&> aStartMark, const Optional<nsAString>& aEndMark) {
|
2024-03-09 23:04:01 +00:00
|
|
|
static FILE* markerFile = MaybeOpenMarkerFile();
|
2023-05-29 15:37:51 +00:00
|
|
|
if (!markerFile) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-07 20:26:03 +00:00
|
|
|
#if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
|
2023-12-21 18:17:41 +00:00
|
|
|
ErrorResult rv;
|
|
|
|
auto [startTimeStamp, endTimeStamp] =
|
|
|
|
GetTimeStampsForMarker(aStartMark, aEndMark, aOptions, rv);
|
|
|
|
|
2023-05-29 15:37:51 +00:00
|
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
|
|
return;
|
|
|
|
}
|
2024-03-07 20:26:03 +00:00
|
|
|
#endif
|
2023-05-29 15:37:51 +00:00
|
|
|
|
|
|
|
#ifdef XP_LINUX
|
|
|
|
uint64_t rawStart = startTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
|
|
|
|
uint64_t rawEnd = endTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
|
|
|
|
#elif XP_WIN
|
|
|
|
uint64_t rawStart = startTimeStamp.RawQueryPerformanceCounterValue().value();
|
|
|
|
uint64_t rawEnd = endTimeStamp.RawQueryPerformanceCounterValue().value();
|
|
|
|
#elif XP_MACOSX
|
Bug 1876415 - Make timestamp formats consistent between Jitdump and the marker file. r=glandium
On Linux and Android, both jitdump and the marker file will keep using
`CLOCK_MONOTONIC` nanoseconds, as before.
On macOS, both jitdump and the marker file will now be using
`TimeStamp::RawMachAbsoluteTimeNanoseconds()` , i.e. "nanoseconds since
mach_absolute_time origin".
This value has the advantage that it is also relatively easy to obtain
in other browser engines, because their internal timestamp value is stored
in milliseconds or nanoseconds rather than in `mach_absolute_time` ticks.
In the past, on macOS, Firefox was using `CLOCK_MONOTONIC` nanoseconds for
jitdump and `TimeStamp::RawMachAbsoluteTimeValue()` for the marker file.
This inconsistency is now fixed.
I will update samply to change how it treats jitdump timestamps on macOS.
There are no other consumers of jitdump files on macOS that I know of.
On Windows, we will keep using raw QPC values for the marker file - this
matches what's in the ETW events. Jitdump on Windows is mostly unused but
I'm updating it to match.
Furthermore, this fixes the order in mozglue/misc/moz.build to make sure
we always use the TimeStamp_darwin implementation on Darwin (and not just
due to a broken configure check, see bug 1681445), and it fixes the #ifdef
in TimeStamp.h to match the Darwin check.
Differential Revision: https://phabricator.services.mozilla.com/D199592
2024-01-26 03:38:54 +00:00
|
|
|
uint64_t rawStart = startTimeStamp.RawMachAbsoluteTimeNanoseconds();
|
|
|
|
uint64_t rawEnd = endTimeStamp.RawMachAbsoluteTimeNanoseconds();
|
2023-05-29 15:37:51 +00:00
|
|
|
#else
|
|
|
|
uint64_t rawStart = 0;
|
|
|
|
uint64_t rawEnd = 0;
|
|
|
|
MOZ_CRASH("no timestamp");
|
|
|
|
#endif
|
|
|
|
// Write a line for this measure to the marker file. The marker file uses a
|
|
|
|
// text-based format where every line is one marker, and each line has the
|
|
|
|
// format:
|
|
|
|
// `<raw_start_timestamp> <raw_end_timestamp> <measure_name>`
|
|
|
|
//
|
|
|
|
// The timestamp value is OS specific.
|
|
|
|
fprintf(markerFile, "%" PRIu64 " %" PRIu64 " %s\n", rawStart, rawEnd,
|
|
|
|
NS_ConvertUTF16toUTF8(aName).get());
|
|
|
|
fflush(markerFile);
|
|
|
|
}
|
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
already_AddRefed<PerformanceMeasure> Performance::Measure(
|
|
|
|
JSContext* aCx, const nsAString& aName,
|
|
|
|
const StringOrPerformanceMeasureOptions& aStartOrMeasureOptions,
|
|
|
|
const Optional<nsAString>& aEndMark, ErrorResult& aRv) {
|
2022-07-27 18:17:17 +00:00
|
|
|
if (!GetParentObject()) {
|
|
|
|
aRv.ThrowInvalidStateError("Global object is unavailable");
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
// Maybe is more readable than using the union type directly.
|
|
|
|
Maybe<const PerformanceMeasureOptions&> options;
|
|
|
|
if (aStartOrMeasureOptions.IsPerformanceMeasureOptions()) {
|
|
|
|
options.emplace(aStartOrMeasureOptions.GetAsPerformanceMeasureOptions());
|
|
|
|
}
|
|
|
|
|
|
|
|
const bool isOptionsNotEmpty =
|
|
|
|
options.isSome() &&
|
|
|
|
(!options->mDetail.isUndefined() || options->mStart.WasPassed() ||
|
|
|
|
options->mEnd.WasPassed() || options->mDuration.WasPassed());
|
|
|
|
if (isOptionsNotEmpty) {
|
|
|
|
if (aEndMark.WasPassed()) {
|
|
|
|
aRv.ThrowTypeError(
|
|
|
|
"Cannot provide separate endMark argument if "
|
|
|
|
"PerformanceMeasureOptions argument is given");
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!options->mStart.WasPassed() && !options->mEnd.WasPassed()) {
|
|
|
|
aRv.ThrowTypeError(
|
|
|
|
"PerformanceMeasureOptions must have start and/or end member");
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options->mStart.WasPassed() && options->mDuration.WasPassed() &&
|
|
|
|
options->mEnd.WasPassed()) {
|
|
|
|
aRv.ThrowTypeError(
|
|
|
|
"PerformanceMeasureOptions cannot have all of the following members: "
|
|
|
|
"start, duration, and end");
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-22 10:12:50 +00:00
|
|
|
const DOMHighResTimeStamp endTime = ResolveEndTimeForMeasure(
|
|
|
|
aEndMark, options, aRv, /* aReturnUnclamped */ false);
|
2022-05-31 16:48:14 +00:00
|
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to Maybe for consistency with options.
|
|
|
|
Maybe<const nsAString&> startMark;
|
|
|
|
if (aStartOrMeasureOptions.IsString()) {
|
|
|
|
startMark.emplace(aStartOrMeasureOptions.GetAsString());
|
|
|
|
}
|
2023-03-22 10:12:50 +00:00
|
|
|
const DOMHighResTimeStamp startTime = ResolveStartTimeForMeasure(
|
|
|
|
startMark, options, aRv, /* aReturnUnclamped */ false);
|
2022-05-31 16:48:14 +00:00
|
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> detail(aCx);
|
|
|
|
if (options && !options->mDetail.isNullOrUndefined()) {
|
|
|
|
StructuredSerializeOptions serializeOptions;
|
|
|
|
JS::Rooted<JS::Value> valueToClone(aCx, options->mDetail);
|
|
|
|
nsContentUtils::StructuredClone(aCx, GetParentObject(), valueToClone,
|
|
|
|
serializeOptions, &detail, aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-05-31 16:48:14 +00:00
|
|
|
detail.setNull();
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 16:48:14 +00:00
|
|
|
RefPtr<PerformanceMeasure> performanceMeasure = new PerformanceMeasure(
|
|
|
|
GetParentObject(), aName, startTime, endTime, detail);
|
2016-06-09 17:04:42 +00:00
|
|
|
InsertUserEntry(performanceMeasure);
|
2017-04-24 15:15:11 +00:00
|
|
|
|
2024-01-05 19:00:39 +00:00
|
|
|
MaybeEmitExternalProfilerMarker(aName, options, startMark, aEndMark);
|
|
|
|
|
2023-12-21 18:17:41 +00:00
|
|
|
if (profiler_is_collecting_markers()) {
|
|
|
|
auto [startTimeStamp, endTimeStamp] =
|
|
|
|
GetTimeStampsForMarker(startMark, aEndMark, options, aRv);
|
2018-03-08 22:36:53 +00:00
|
|
|
|
|
|
|
Maybe<nsString> endMark;
|
|
|
|
if (aEndMark.WasPassed()) {
|
|
|
|
endMark.emplace(aEndMark.Value());
|
|
|
|
}
|
|
|
|
|
2019-10-09 21:25:11 +00:00
|
|
|
Maybe<uint64_t> innerWindowId;
|
|
|
|
if (GetOwner()) {
|
|
|
|
innerWindowId = Some(GetOwner()->WindowID());
|
|
|
|
}
|
2020-11-18 21:53:09 +00:00
|
|
|
profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
|
|
|
|
{MarkerTiming::Interval(startTimeStamp, endTimeStamp),
|
|
|
|
MarkerInnerWindowId(innerWindowId)},
|
|
|
|
UserTimingMarker{}, aName, /* aIsMeasure */ true,
|
|
|
|
startMark, endMark);
|
2017-04-24 15:15:11 +00:00
|
|
|
}
|
2022-05-31 16:48:14 +00:00
|
|
|
|
|
|
|
return performanceMeasure.forget();
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::ClearMeasures(const Optional<nsAString>& aName) {
|
|
|
|
ClearUserEntries(aName, u"measure"_ns);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::LogEntry(PerformanceEntry* aEntry,
|
|
|
|
const nsACString& aOwner) const {
|
2021-01-24 12:16:55 +00:00
|
|
|
PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n",
|
|
|
|
aOwner.BeginReading(),
|
|
|
|
NS_ConvertUTF16toUTF8(aEntry->GetEntryType()->GetUTF16String()).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(aEntry->GetName()->GetUTF16String()).get(),
|
|
|
|
aEntry->StartTime(), aEntry->Duration(),
|
|
|
|
static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::TimingNotification(PerformanceEntry* aEntry,
|
|
|
|
const nsACString& aOwner,
|
2022-05-18 17:22:15 +00:00
|
|
|
const double aEpoch) {
|
2016-06-09 17:04:42 +00:00
|
|
|
PerformanceEntryEventInit init;
|
|
|
|
init.mBubbles = false;
|
|
|
|
init.mCancelable = false;
|
2021-01-24 12:16:55 +00:00
|
|
|
aEntry->GetName(init.mName);
|
|
|
|
aEntry->GetEntryType(init.mEntryType);
|
2016-06-09 17:04:42 +00:00
|
|
|
init.mStartTime = aEntry->StartTime();
|
|
|
|
init.mDuration = aEntry->Duration();
|
|
|
|
init.mEpoch = aEpoch;
|
2020-09-02 09:54:37 +00:00
|
|
|
CopyUTF8toUTF16(aOwner, init.mOrigin);
|
2016-06-09 17:04:42 +00:00
|
|
|
|
|
|
|
RefPtr<PerformanceEntryEvent> perfEntryEvent =
|
|
|
|
PerformanceEntryEvent::Constructor(this, u"performanceentry"_ns, init);
|
|
|
|
|
|
|
|
nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner());
|
|
|
|
if (et) {
|
2018-04-05 17:42:41 +00:00
|
|
|
et->DispatchEvent(*perfEntryEvent);
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::InsertUserEntry(PerformanceEntry* aEntry) {
|
|
|
|
mUserEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
|
|
|
|
|
|
|
|
QueueEntry(aEntry);
|
|
|
|
}
|
|
|
|
|
2019-04-08 23:21:08 +00:00
|
|
|
/*
|
|
|
|
* Steps are labeled according to the description found at
|
|
|
|
* https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
|
|
|
|
*
|
|
|
|
* Buffer Full Event
|
|
|
|
*/
|
|
|
|
void Performance::BufferEvent() {
|
|
|
|
/*
|
|
|
|
* While resource timing secondary buffer is not empty,
|
|
|
|
* run the following substeps:
|
|
|
|
*/
|
|
|
|
while (!mSecondaryResourceEntries.IsEmpty()) {
|
|
|
|
uint32_t secondaryResourceEntriesBeforeCount = 0;
|
|
|
|
uint32_t secondaryResourceEntriesAfterCount = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Let number of excess entries before be resource
|
|
|
|
* timing secondary buffer current size.
|
|
|
|
*/
|
|
|
|
secondaryResourceEntriesBeforeCount = mSecondaryResourceEntries.Length();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If can add resource timing entry returns false,
|
|
|
|
* then fire an event named resourcetimingbufferfull
|
|
|
|
* at the Performance object.
|
|
|
|
*/
|
|
|
|
if (!CanAddResourceTimingEntry()) {
|
|
|
|
DispatchBufferFullEvent();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Run copy secondary buffer.
|
|
|
|
*
|
|
|
|
* While resource timing secondary buffer is not
|
|
|
|
* empty and can add resource timing entry returns
|
|
|
|
* true ...
|
|
|
|
*/
|
|
|
|
while (!mSecondaryResourceEntries.IsEmpty() &&
|
|
|
|
CanAddResourceTimingEntry()) {
|
|
|
|
/*
|
|
|
|
* Let entry be the oldest PerformanceResourceTiming
|
|
|
|
* in resource timing secondary buffer. Add entry to
|
|
|
|
* the end of performance entry buffer. Increment
|
|
|
|
* resource timing buffer current size by 1.
|
|
|
|
*/
|
|
|
|
mResourceEntries.InsertElementSorted(
|
|
|
|
mSecondaryResourceEntries.ElementAt(0), PerformanceEntryComparator());
|
|
|
|
/*
|
|
|
|
* Remove entry from resource timing secondary buffer.
|
|
|
|
* Decrement resource timing secondary buffer current
|
|
|
|
* size by 1.
|
|
|
|
*/
|
|
|
|
mSecondaryResourceEntries.RemoveElementAt(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Let number of excess entries after be resource
|
|
|
|
* timing secondary buffer current size.
|
|
|
|
*/
|
|
|
|
secondaryResourceEntriesAfterCount = mSecondaryResourceEntries.Length();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If number of excess entries before is lower than
|
|
|
|
* or equals number of excess entries after, then
|
|
|
|
* remove all entries from resource timing secondary
|
|
|
|
* buffer, set resource timing secondary buffer current
|
|
|
|
* size to 0, and abort these steps.
|
|
|
|
*/
|
|
|
|
if (secondaryResourceEntriesBeforeCount <=
|
|
|
|
secondaryResourceEntriesAfterCount) {
|
|
|
|
mSecondaryResourceEntries.Clear();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Set resource timing buffer full event pending flag
|
|
|
|
* to false.
|
|
|
|
*/
|
|
|
|
mPendingResourceTimingBufferFullEvent = false;
|
|
|
|
}
|
|
|
|
|
2016-06-09 17:04:42 +00:00
|
|
|
void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize) {
|
|
|
|
mResourceTimingBufferSize = aMaxSize;
|
|
|
|
}
|
|
|
|
|
2019-04-08 23:21:08 +00:00
|
|
|
/*
|
|
|
|
* Steps are labeled according to the description found at
|
|
|
|
* https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
|
|
|
|
*
|
|
|
|
* Can Add Resource Timing Entry
|
|
|
|
*/
|
|
|
|
MOZ_ALWAYS_INLINE bool Performance::CanAddResourceTimingEntry() {
|
|
|
|
/*
|
|
|
|
* If resource timing buffer current size is smaller than resource timing
|
|
|
|
* buffer size limit, return true. [Otherwise,] [r]eturn false.
|
|
|
|
*/
|
|
|
|
return mResourceEntries.Length() < mResourceTimingBufferSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Steps are labeled according to the description found at
|
|
|
|
* https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
|
|
|
|
*
|
|
|
|
* Add a PerformanceResourceTiming Entry
|
|
|
|
*/
|
2016-06-09 17:04:42 +00:00
|
|
|
void Performance::InsertResourceEntry(PerformanceEntry* aEntry) {
|
|
|
|
MOZ_ASSERT(aEntry);
|
2017-06-15 08:48:27 +00:00
|
|
|
|
2023-01-16 18:36:09 +00:00
|
|
|
QueueEntry(aEntry);
|
|
|
|
|
2019-04-08 23:21:08 +00:00
|
|
|
/*
|
|
|
|
* Let new entry be the input PerformanceEntry to be added.
|
|
|
|
*
|
|
|
|
* If can add resource timing entry returns true and resource
|
|
|
|
* timing buffer full event pending flag is false ...
|
|
|
|
*/
|
|
|
|
if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent) {
|
|
|
|
/*
|
|
|
|
* Add new entry to the performance entry buffer.
|
|
|
|
* Increase resource timing buffer current size by 1.
|
|
|
|
*/
|
|
|
|
mResourceEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
|
2016-06-09 17:04:42 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-08 23:21:08 +00:00
|
|
|
/*
|
|
|
|
* If resource timing buffer full event pending flag is
|
|
|
|
* false ...
|
|
|
|
*/
|
|
|
|
if (!mPendingResourceTimingBufferFullEvent) {
|
|
|
|
/*
|
|
|
|
* Set resource timing buffer full event pending flag
|
|
|
|
* to true.
|
|
|
|
*/
|
|
|
|
mPendingResourceTimingBufferFullEvent = true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Queue a task to run fire a buffer full event.
|
|
|
|
*/
|
|
|
|
NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
|
|
|
|
"Performance::BufferEvent", this, &Performance::BufferEvent));
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
2019-04-08 23:21:08 +00:00
|
|
|
/*
|
|
|
|
* Add new entry to the resource timing secondary buffer.
|
|
|
|
* Increase resource timing secondary buffer current size
|
|
|
|
* by 1.
|
|
|
|
*/
|
|
|
|
mSecondaryResourceEntries.InsertElementSorted(aEntry,
|
|
|
|
PerformanceEntryComparator());
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::AddObserver(PerformanceObserver* aObserver) {
|
|
|
|
mObservers.AppendElementUnlessExists(aObserver);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::RemoveObserver(PerformanceObserver* aObserver) {
|
|
|
|
mObservers.RemoveElement(aObserver);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::NotifyObservers() {
|
|
|
|
mPendingNotificationObserversTask = false;
|
2020-06-15 09:05:00 +00:00
|
|
|
NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, Notify, ());
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::CancelNotificationObservers() {
|
|
|
|
mPendingNotificationObserversTask = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
class NotifyObserversTask final : public CancelableRunnable {
|
|
|
|
public:
|
|
|
|
explicit NotifyObserversTask(Performance* aPerformance)
|
2017-06-12 19:34:10 +00:00
|
|
|
: CancelableRunnable("dom::NotifyObserversTask"),
|
|
|
|
mPerformance(aPerformance) {
|
2016-06-09 17:04:42 +00:00
|
|
|
MOZ_ASSERT(mPerformance);
|
|
|
|
}
|
|
|
|
|
2019-03-19 05:24:39 +00:00
|
|
|
// MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
|
|
|
|
// MOZ_CAN_RUN_SCRIPT.
|
|
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
2016-06-09 17:04:42 +00:00
|
|
|
NS_IMETHOD Run() override {
|
|
|
|
MOZ_ASSERT(mPerformance);
|
2019-03-19 05:24:39 +00:00
|
|
|
RefPtr<Performance> performance(mPerformance);
|
|
|
|
performance->NotifyObservers();
|
2016-06-09 17:04:42 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult Cancel() override {
|
|
|
|
mPerformance->CancelNotificationObservers();
|
|
|
|
mPerformance = nullptr;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2020-03-04 09:10:03 +00:00
|
|
|
~NotifyObserversTask() = default;
|
2016-06-09 17:04:42 +00:00
|
|
|
|
|
|
|
RefPtr<Performance> mPerformance;
|
|
|
|
};
|
|
|
|
|
2020-05-18 11:53:06 +00:00
|
|
|
void Performance::QueueNotificationObserversTask() {
|
|
|
|
if (!mPendingNotificationObserversTask) {
|
|
|
|
RunNotificationObserversTask();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-09 17:04:42 +00:00
|
|
|
void Performance::RunNotificationObserversTask() {
|
|
|
|
mPendingNotificationObserversTask = true;
|
|
|
|
nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(this);
|
2018-02-08 20:54:00 +00:00
|
|
|
nsresult rv;
|
Bug 1624819 - Remove TaskCategory and other quantum dom remnants. r=smaug,media-playback-reviewers,credential-management-reviewers,cookie-reviewers,places-reviewers,win-reviewers,valentin,mhowell,sgalich,alwu
Sorry this is not a particularly easy patch to review. But it should be
mostly straight-forward.
I kept Document::Dispatch mostly for convenience, but could be
cleaned-up too / changed by SchedulerGroup::Dispatch. Similarly maybe
that can just be NS_DispatchToMainThread if we add an NS_IsMainThread
check there or something (to preserve shutdown semantics).
Differential Revision: https://phabricator.services.mozilla.com/D190450
2023-10-10 08:51:12 +00:00
|
|
|
if (nsIGlobalObject* global = GetOwnerGlobal()) {
|
|
|
|
rv = global->Dispatch(task.forget());
|
2018-02-08 20:54:00 +00:00
|
|
|
} else {
|
Bug 1624819 - Remove TaskCategory and other quantum dom remnants. r=smaug,media-playback-reviewers,credential-management-reviewers,cookie-reviewers,places-reviewers,win-reviewers,valentin,mhowell,sgalich,alwu
Sorry this is not a particularly easy patch to review. But it should be
mostly straight-forward.
I kept Document::Dispatch mostly for convenience, but could be
cleaned-up too / changed by SchedulerGroup::Dispatch. Similarly maybe
that can just be NS_DispatchToMainThread if we add an NS_IsMainThread
check there or something (to preserve shutdown semantics).
Differential Revision: https://phabricator.services.mozilla.com/D190450
2023-10-10 08:51:12 +00:00
|
|
|
rv = NS_DispatchToCurrentThread(task.forget());
|
2018-02-08 20:54:00 +00:00
|
|
|
}
|
2016-06-09 17:04:42 +00:00
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
|
|
mPendingNotificationObserversTask = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Performance::QueueEntry(PerformanceEntry* aEntry) {
|
2019-04-12 16:29:48 +00:00
|
|
|
nsTObserverArray<PerformanceObserver*> interestedObservers;
|
2021-02-09 18:54:47 +00:00
|
|
|
if (!mObservers.IsEmpty()) {
|
|
|
|
const auto [begin, end] = mObservers.NonObservingRange();
|
|
|
|
std::copy_if(begin, end, MakeBackInserter(interestedObservers),
|
|
|
|
[aEntry](PerformanceObserver* observer) {
|
|
|
|
return observer->ObservesTypeOfEntry(aEntry);
|
|
|
|
});
|
2019-04-12 16:29:48 +00:00
|
|
|
}
|
|
|
|
|
2020-06-15 09:05:00 +00:00
|
|
|
NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers, QueueEntry,
|
|
|
|
(aEntry));
|
2016-06-09 17:04:42 +00:00
|
|
|
|
2021-02-09 18:54:47 +00:00
|
|
|
aEntry->BufferEntryIfNeeded();
|
|
|
|
|
|
|
|
if (!interestedObservers.IsEmpty()) {
|
|
|
|
QueueNotificationObserversTask();
|
|
|
|
}
|
2016-06-09 17:04:42 +00:00
|
|
|
}
|
|
|
|
|
2023-05-29 00:00:24 +00:00
|
|
|
// We could clear User entries here, but doing so could break sites that call
|
|
|
|
// performance.measure() if the marks disappeared without warning. Chrome
|
|
|
|
// allows "infinite" entries.
|
|
|
|
void Performance::MemoryPressure() {}
|
2017-07-20 10:57:08 +00:00
|
|
|
|
2017-07-27 07:05:51 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-11-04 17:04:01 +00:00
|
|
|
} // namespace mozilla::dom
|