1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "Performance.h"
13 # include <sys/mman.h>
17 #include "GeckoProfiler.h"
18 #include "nsRFPService.h"
19 #include "PerformanceEntry.h"
20 #include "PerformanceMainThread.h"
21 #include "PerformanceMark.h"
22 #include "PerformanceMeasure.h"
23 #include "PerformanceObserver.h"
24 #include "PerformanceResourceTiming.h"
25 #include "PerformanceService.h"
26 #include "PerformanceWorker.h"
27 #include "mozilla/BasePrincipal.h"
28 #include "mozilla/ErrorResult.h"
29 #include "mozilla/dom/MessagePortBinding.h"
30 #include "mozilla/dom/PerformanceBinding.h"
31 #include "mozilla/dom/PerformanceEntryEvent.h"
32 #include "mozilla/dom/PerformanceNavigationBinding.h"
33 #include "mozilla/dom/PerformanceObserverBinding.h"
34 #include "mozilla/dom/PerformanceNavigationTiming.h"
35 #include "mozilla/IntegerPrintfMacros.h"
36 #include "mozilla/Perfetto.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/TimeStamp.h"
39 #include "mozilla/dom/WorkerPrivate.h"
40 #include "mozilla/dom/WorkerRunnable.h"
41 #include "mozilla/dom/WorkerScope.h"
43 #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
45 namespace mozilla::dom
{
47 enum class Performance::ResolveTimestampAttribute
{
53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance
)
54 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
56 NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance
, DOMEventTargetHelper
,
57 mUserEntries
, mResourceEntries
,
58 mSecondaryResourceEntries
, mObservers
);
60 NS_IMPL_ADDREF_INHERITED(Performance
, DOMEventTargetHelper
)
61 NS_IMPL_RELEASE_INHERITED(Performance
, DOMEventTargetHelper
)
64 already_AddRefed
<Performance
> Performance::CreateForMainThread(
65 nsPIDOMWindowInner
* aWindow
, nsIPrincipal
* aPrincipal
,
66 nsDOMNavigationTiming
* aDOMTiming
, nsITimedChannel
* aChannel
) {
67 MOZ_ASSERT(NS_IsMainThread());
69 MOZ_ASSERT(aWindow
->AsGlobal());
70 RefPtr
<Performance
> performance
=
71 new PerformanceMainThread(aWindow
, aDOMTiming
, aChannel
);
72 return performance
.forget();
76 already_AddRefed
<Performance
> Performance::CreateForWorker(
77 WorkerGlobalScope
* aGlobalScope
) {
78 MOZ_ASSERT(aGlobalScope
);
79 // aWorkerPrivate->AssertIsOnWorkerThread();
81 RefPtr
<Performance
> performance
= new PerformanceWorker(aGlobalScope
);
82 return performance
.forget();
86 already_AddRefed
<Performance
> Performance::Get(JSContext
* aCx
,
87 nsIGlobalObject
* aGlobal
) {
88 RefPtr
<Performance
> performance
;
89 if (NS_IsMainThread()) {
90 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(aGlobal
);
95 performance
= window
->GetPerformance();
96 return performance
.forget();
99 const WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(aCx
);
100 if (!workerPrivate
) {
104 WorkerGlobalScope
* scope
= workerPrivate
->GlobalScope();
106 performance
= scope
->GetPerformance();
108 return performance
.forget();
111 Performance::Performance(nsIGlobalObject
* aGlobal
)
112 : DOMEventTargetHelper(aGlobal
),
113 mResourceTimingBufferSize(kDefaultResourceTimingBufferSize
),
114 mPendingNotificationObserversTask(false),
115 mPendingResourceTimingBufferFullEvent(false),
116 mRTPCallerType(aGlobal
->GetRTPCallerType()),
117 mCrossOriginIsolated(aGlobal
->CrossOriginIsolated()),
118 mShouldResistFingerprinting(aGlobal
->ShouldResistFingerprinting(
119 RFPTarget::ReduceTimerPrecision
)) {}
121 Performance::~Performance() = default;
123 DOMHighResTimeStamp
Performance::TimeStampToDOMHighResForRendering(
124 TimeStamp aTimeStamp
) const {
125 DOMHighResTimeStamp stamp
= GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp
);
126 // 0 is an inappropriate mixin for this this area; however CSS Animations
127 // needs to have it's Time Reduction Logic refactored, so it's currently
128 // only clamping for RFP mode. RFP mode gives a much lower time precision,
129 // so we accept the security leak here for now.
130 return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp
, 0,
134 DOMHighResTimeStamp
Performance::Now() {
135 DOMHighResTimeStamp rawTime
= NowUnclamped();
137 // XXX: Removing this caused functions in pkcs11f.h to fail.
138 // Bug 1628021 investigates the root cause - it involves initializing
139 // the RNG service (part of GetRandomTimelineSeed()) off-main-thread
140 // but the underlying cause hasn't been identified yet.
141 if (mRTPCallerType
== RTPCallerType::SystemPrincipal
) {
145 return nsRFPService::ReduceTimePrecisionAsMSecs(
146 rawTime
, GetRandomTimelineSeed(), mRTPCallerType
);
149 DOMHighResTimeStamp
Performance::NowUnclamped() const {
150 TimeDuration duration
= TimeStamp::Now() - CreationTimeStamp();
151 return duration
.ToMilliseconds();
154 DOMHighResTimeStamp
Performance::TimeOrigin() {
155 if (!mPerformanceService
) {
156 mPerformanceService
= PerformanceService::GetOrCreate();
159 MOZ_ASSERT(mPerformanceService
);
160 DOMHighResTimeStamp rawTimeOrigin
=
161 mPerformanceService
->TimeOrigin(CreationTimeStamp());
162 // Time Origin is an absolute timestamp, so we supply a 0 context mix-in
163 return nsRFPService::ReduceTimePrecisionAsMSecs(rawTimeOrigin
, 0,
167 JSObject
* Performance::WrapObject(JSContext
* aCx
,
168 JS::Handle
<JSObject
*> aGivenProto
) {
169 return Performance_Binding::Wrap(aCx
, this, aGivenProto
);
172 void Performance::GetEntries(nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
173 aRetval
= mResourceEntries
.Clone();
174 aRetval
.AppendElements(mUserEntries
);
175 aRetval
.Sort(PerformanceEntryComparator());
178 void Performance::GetEntriesByType(
179 const nsAString
& aEntryType
, nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
180 if (aEntryType
.EqualsLiteral("resource")) {
181 aRetval
= mResourceEntries
.Clone();
187 if (aEntryType
.EqualsLiteral("mark") || aEntryType
.EqualsLiteral("measure")) {
188 RefPtr
<nsAtom
> entryType
= NS_Atomize(aEntryType
);
189 for (PerformanceEntry
* entry
: mUserEntries
) {
190 if (entry
->GetEntryType() == entryType
) {
191 aRetval
.AppendElement(entry
);
197 void Performance::GetEntriesByName(
198 const nsAString
& aName
, const Optional
<nsAString
>& aEntryType
,
199 nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
202 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
203 RefPtr
<nsAtom
> entryType
=
204 aEntryType
.WasPassed() ? NS_Atomize(aEntryType
.Value()) : nullptr;
207 if (entryType
== nsGkAtoms::mark
|| entryType
== nsGkAtoms::measure
) {
208 for (PerformanceEntry
* entry
: mUserEntries
) {
209 if (entry
->GetName() == name
&& entry
->GetEntryType() == entryType
) {
210 aRetval
.AppendElement(entry
);
215 if (entryType
== nsGkAtoms::resource
) {
216 for (PerformanceEntry
* entry
: mResourceEntries
) {
217 MOZ_ASSERT(entry
->GetEntryType() == entryType
);
218 if (entry
->GetName() == name
) {
219 aRetval
.AppendElement(entry
);
228 nsTArray
<PerformanceEntry
*> qualifiedResourceEntries
;
229 nsTArray
<PerformanceEntry
*> qualifiedUserEntries
;
230 // ::Measure expects that results from this function are already
231 // passed through ReduceTimePrecision. mResourceEntries and mUserEntries
232 // are, so the invariant holds.
233 for (PerformanceEntry
* entry
: mResourceEntries
) {
234 if (entry
->GetName() == name
) {
235 qualifiedResourceEntries
.AppendElement(entry
);
239 for (PerformanceEntry
* entry
: mUserEntries
) {
240 if (entry
->GetName() == name
) {
241 qualifiedUserEntries
.AppendElement(entry
);
245 size_t resourceEntriesIdx
= 0, userEntriesIdx
= 0;
246 aRetval
.SetCapacity(qualifiedResourceEntries
.Length() +
247 qualifiedUserEntries
.Length());
249 PerformanceEntryComparator comparator
;
251 while (resourceEntriesIdx
< qualifiedResourceEntries
.Length() &&
252 userEntriesIdx
< qualifiedUserEntries
.Length()) {
253 if (comparator
.LessThan(qualifiedResourceEntries
[resourceEntriesIdx
],
254 qualifiedUserEntries
[userEntriesIdx
])) {
255 aRetval
.AppendElement(qualifiedResourceEntries
[resourceEntriesIdx
]);
256 ++resourceEntriesIdx
;
258 aRetval
.AppendElement(qualifiedUserEntries
[userEntriesIdx
]);
263 while (resourceEntriesIdx
< qualifiedResourceEntries
.Length()) {
264 aRetval
.AppendElement(qualifiedResourceEntries
[resourceEntriesIdx
]);
265 ++resourceEntriesIdx
;
268 while (userEntriesIdx
< qualifiedUserEntries
.Length()) {
269 aRetval
.AppendElement(qualifiedUserEntries
[userEntriesIdx
]);
274 void Performance::GetEntriesByTypeForObserver(
275 const nsAString
& aEntryType
, nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
276 GetEntriesByType(aEntryType
, aRetval
);
279 void Performance::ClearUserEntries(const Optional
<nsAString
>& aEntryName
,
280 const nsAString
& aEntryType
) {
281 MOZ_ASSERT(!aEntryType
.IsEmpty());
282 RefPtr
<nsAtom
> name
=
283 aEntryName
.WasPassed() ? NS_Atomize(aEntryName
.Value()) : nullptr;
284 RefPtr
<nsAtom
> entryType
= NS_Atomize(aEntryType
);
285 mUserEntries
.RemoveElementsBy([name
, entryType
](const auto& entry
) {
286 return (!name
|| entry
->GetName() == name
) &&
287 (entry
->GetEntryType() == entryType
);
291 void Performance::ClearResourceTimings() { mResourceEntries
.Clear(); }
293 struct UserTimingMarker
: public BaseMarkerType
<UserTimingMarker
> {
294 static constexpr const char* Name
= "UserTiming";
295 static constexpr const char* Description
=
296 "UserTimingMeasure is created using the DOM API performance.measure().";
298 using MS
= MarkerSchema
;
299 static constexpr MS::PayloadField PayloadFields
[] = {
300 {"name", MS::InputType::String
, "User Marker Name", MS::Format::String
,
301 MS::PayloadFlags::Searchable
},
302 {"entryType", MS::InputType::Boolean
, "Entry Type"},
303 {"startMark", MS::InputType::String
, "Start Mark"},
304 {"endMark", MS::InputType::String
, "End Mark"}};
306 static constexpr MS::Location Locations
[] = {MS::Location::MarkerChart
,
307 MS::Location::MarkerTable
};
308 static constexpr const char* AllLabels
= "{marker.data.name}";
310 static constexpr MS::ETWMarkerGroup Group
= MS::ETWMarkerGroup::UserMarkers
;
312 static void StreamJSONMarkerData(
313 baseprofiler::SpliceableJSONWriter
& aWriter
,
314 const ProfilerString16View
& aName
, bool aIsMeasure
,
315 const Maybe
<ProfilerString16View
>& aStartMark
,
316 const Maybe
<ProfilerString16View
>& aEndMark
) {
317 StreamJSONMarkerDataImpl(
319 aIsMeasure
? MakeStringSpan("measure") : MakeStringSpan("mark"),
320 aStartMark
, aEndMark
);
324 already_AddRefed
<PerformanceMark
> Performance::Mark(
325 JSContext
* aCx
, const nsAString
& aName
,
326 const PerformanceMarkOptions
& aMarkOptions
, ErrorResult
& aRv
) {
327 nsCOMPtr
<nsIGlobalObject
> parent
= GetParentObject();
328 if (!parent
|| parent
->IsDying() || !parent
->HasJSGlobal()) {
329 aRv
.ThrowInvalidStateError("Global object is unavailable");
333 GlobalObject
global(aCx
, parent
->GetGlobalJSObject());
334 if (global
.Failed()) {
335 aRv
.ThrowInvalidStateError("Global object is unavailable");
339 RefPtr
<PerformanceMark
> performanceMark
=
340 PerformanceMark::Constructor(global
, aName
, aMarkOptions
, aRv
);
345 InsertUserEntry(performanceMark
);
347 if (profiler_is_collecting_markers()) {
348 Maybe
<uint64_t> innerWindowId
;
350 innerWindowId
= Some(GetOwner()->WindowID());
352 TimeStamp startTimeStamp
=
353 CreationTimeStamp() +
354 TimeDuration::FromMilliseconds(performanceMark
->UnclampedStartTime());
355 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
356 MarkerOptions(MarkerTiming::InstantAt(startTimeStamp
),
357 MarkerInnerWindowId(innerWindowId
)),
358 UserTimingMarker
{}, aName
, /* aIsMeasure */ false,
359 Nothing
{}, Nothing
{});
362 return performanceMark
.forget();
365 void Performance::ClearMarks(const Optional
<nsAString
>& aName
) {
366 ClearUserEntries(aName
, u
"mark"_ns
);
369 // To be removed once bug 1124165 lands
370 bool Performance::IsPerformanceTimingAttribute(const nsAString
& aName
) const {
371 // Note that toJSON is added to this list due to bug 1047848
372 static const char* attributes
[] = {"navigationStart",
381 "secureConnectionStart",
388 "domContentLoadedEventStart",
389 "domContentLoadedEventEnd",
395 for (uint32_t i
= 0; attributes
[i
]; ++i
) {
396 if (aName
.EqualsASCII(attributes
[i
])) {
404 DOMHighResTimeStamp
Performance::ConvertMarkToTimestampWithString(
405 const nsAString
& aName
, ErrorResult
& aRv
, bool aReturnUnclamped
) {
406 if (IsPerformanceTimingAttribute(aName
)) {
407 return ConvertNameToTimestamp(aName
, aRv
);
410 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
411 // Just loop over the user entries
412 for (const PerformanceEntry
* entry
: Reversed(mUserEntries
)) {
413 if (entry
->GetName() == name
&& entry
->GetEntryType() == nsGkAtoms::mark
) {
414 if (aReturnUnclamped
) {
415 return entry
->UnclampedStartTime();
417 return entry
->StartTime();
421 nsPrintfCString
errorMsg("Given mark name, %s, is unknown",
422 NS_ConvertUTF16toUTF8(aName
).get());
423 aRv
.ThrowSyntaxError(errorMsg
);
427 DOMHighResTimeStamp
Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
428 const ResolveTimestampAttribute aAttribute
,
429 const DOMHighResTimeStamp aTimestamp
, ErrorResult
& aRv
) {
430 if (aTimestamp
< 0) {
431 nsAutoCString attributeName
;
432 switch (aAttribute
) {
433 case ResolveTimestampAttribute::Start
:
434 attributeName
= "start";
436 case ResolveTimestampAttribute::End
:
437 attributeName
= "end";
439 case ResolveTimestampAttribute::Duration
:
440 attributeName
= "duration";
444 nsPrintfCString
errorMsg("Given attribute %s cannot be negative",
445 attributeName
.get());
446 aRv
.ThrowTypeError(errorMsg
);
451 DOMHighResTimeStamp
Performance::ConvertMarkToTimestamp(
452 const ResolveTimestampAttribute aAttribute
,
453 const OwningStringOrDouble
& aMarkNameOrTimestamp
, ErrorResult
& aRv
,
454 bool aReturnUnclamped
) {
455 if (aMarkNameOrTimestamp
.IsString()) {
456 return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp
.GetAsString(),
457 aRv
, aReturnUnclamped
);
460 return ConvertMarkToTimestampWithDOMHighResTimeStamp(
461 aAttribute
, aMarkNameOrTimestamp
.GetAsDouble(), aRv
);
464 DOMHighResTimeStamp
Performance::ConvertNameToTimestamp(const nsAString
& aName
,
466 if (!IsGlobalObjectWindow()) {
467 nsPrintfCString
errorMsg(
468 "Cannot get PerformanceTiming attribute values for non-Window global "
470 NS_ConvertUTF16toUTF8(aName
).get());
471 aRv
.ThrowTypeError(errorMsg
);
475 if (aName
.EqualsASCII("navigationStart")) {
479 // We use GetPerformanceTimingFromString, rather than calling the
480 // navigationStart method timing function directly, because the former handles
481 // reducing precision against timing attacks.
482 const DOMHighResTimeStamp startTime
=
483 GetPerformanceTimingFromString(u
"navigationStart"_ns
);
484 const DOMHighResTimeStamp endTime
= GetPerformanceTimingFromString(aName
);
485 MOZ_ASSERT(endTime
>= 0);
487 nsPrintfCString
errorMsg(
488 "Given PerformanceTiming attribute, %s, isn't available yet",
489 NS_ConvertUTF16toUTF8(aName
).get());
490 aRv
.ThrowInvalidAccessError(errorMsg
);
494 return endTime
- startTime
;
497 DOMHighResTimeStamp
Performance::ResolveEndTimeForMeasure(
498 const Optional
<nsAString
>& aEndMark
,
499 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
,
500 bool aReturnUnclamped
) {
501 DOMHighResTimeStamp endTime
;
502 if (aEndMark
.WasPassed()) {
503 endTime
= ConvertMarkToTimestampWithString(aEndMark
.Value(), aRv
,
505 } else if (aOptions
&& aOptions
->mEnd
.WasPassed()) {
507 ConvertMarkToTimestamp(ResolveTimestampAttribute::End
,
508 aOptions
->mEnd
.Value(), aRv
, aReturnUnclamped
);
509 } else if (aOptions
&& aOptions
->mStart
.WasPassed() &&
510 aOptions
->mDuration
.WasPassed()) {
511 const DOMHighResTimeStamp start
=
512 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start
,
513 aOptions
->mStart
.Value(), aRv
, aReturnUnclamped
);
518 const DOMHighResTimeStamp duration
=
519 ConvertMarkToTimestampWithDOMHighResTimeStamp(
520 ResolveTimestampAttribute::Duration
, aOptions
->mDuration
.Value(),
526 endTime
= start
+ duration
;
534 DOMHighResTimeStamp
Performance::ResolveStartTimeForMeasure(
535 const Maybe
<const nsAString
&>& aStartMark
,
536 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
,
537 bool aReturnUnclamped
) {
538 DOMHighResTimeStamp startTime
;
539 if (aOptions
&& aOptions
->mStart
.WasPassed()) {
541 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start
,
542 aOptions
->mStart
.Value(), aRv
, aReturnUnclamped
);
543 } else if (aOptions
&& aOptions
->mDuration
.WasPassed() &&
544 aOptions
->mEnd
.WasPassed()) {
545 const DOMHighResTimeStamp duration
=
546 ConvertMarkToTimestampWithDOMHighResTimeStamp(
547 ResolveTimestampAttribute::Duration
, aOptions
->mDuration
.Value(),
553 const DOMHighResTimeStamp end
=
554 ConvertMarkToTimestamp(ResolveTimestampAttribute::End
,
555 aOptions
->mEnd
.Value(), aRv
, aReturnUnclamped
);
560 startTime
= end
- duration
;
561 } else if (aStartMark
) {
563 ConvertMarkToTimestampWithString(*aStartMark
, aRv
, aReturnUnclamped
);
571 static std::string
GetMarkerFilename() {
573 if (char* markerDir
= getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
574 s
<< markerDir
<< "/";
577 s
<< "marker-" << GetCurrentProcessId() << ".txt";
579 s
<< "marker-" << getpid() << ".txt";
584 std::pair
<TimeStamp
, TimeStamp
> Performance::GetTimeStampsForMarker(
585 const Maybe
<const nsAString
&>& aStartMark
,
586 const Optional
<nsAString
>& aEndMark
,
587 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
) {
588 const DOMHighResTimeStamp unclampedStartTime
= ResolveStartTimeForMeasure(
589 aStartMark
, aOptions
, aRv
, /* aReturnUnclamped */ true);
590 const DOMHighResTimeStamp unclampedEndTime
=
591 ResolveEndTimeForMeasure(aEndMark
, aOptions
, aRv
, /* aReturnUnclamped */
594 TimeStamp startTimeStamp
=
595 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedStartTime
);
596 TimeStamp endTimeStamp
=
597 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedEndTime
);
599 return std::make_pair(startTimeStamp
, endTimeStamp
);
602 static FILE* MaybeOpenMarkerFile() {
603 if (!getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")) {
608 // We treat marker files similar to Jitdump files (see PerfSpewer.cpp) and
609 // mmap them if needed.
610 int fd
= open(GetMarkerFilename().c_str(), O_CREAT
| O_TRUNC
| O_RDWR
, 0666);
611 FILE* markerFile
= fdopen(fd
, "w+");
617 // On Linux and Android, we need to mmap the file so that the path makes it
618 // into the perf.data file or into samply.
619 // On non-Android, make the mapping executable, otherwise the MMAP event may
620 // not be recorded by perf (see perf_event_open mmap_data).
621 // But on Android, don't make the mapping executable, because doing so can
622 // make the mmap call fail on some Android devices. It's also not required on
623 // Android because simpleperf sets mmap_data = 1 for unrelated reasons (it
624 // wants to know about vdex files for Java JIT profiling, see
625 // SetRecordNotExecutableMaps).
626 int protection
= PROT_READ
;
628 protection
|= PROT_EXEC
;
631 // Mmap just the first page - that's enough to ensure the path makes it into
633 long page_size
= sysconf(_SC_PAGESIZE
);
634 void* mmap_address
= mmap(nullptr, page_size
, protection
, MAP_PRIVATE
, fd
, 0);
635 if (mmap_address
== MAP_FAILED
) {
641 // On macOS, we just need to `open` or `fopen` the marker file, and samply
642 // will know its path because it hooks those functions - no mmap needed.
643 // On Windows, there's no need to use MOZ_USE_PERFORMANCE_MARKER_FILE because
644 // we have ETW trace events for UserTiming measures. Still, we want this code
645 // to compile successfully on Windows, so we use fopen rather than
647 return fopen(GetMarkerFilename().c_str(), "w+");
651 // This emits markers to an external marker-[pid].txt file for use by an
652 // external profiler like samply or etw-gecko
653 void Performance::MaybeEmitExternalProfilerMarker(
654 const nsAString
& aName
, Maybe
<const PerformanceMeasureOptions
&> aOptions
,
655 Maybe
<const nsAString
&> aStartMark
, const Optional
<nsAString
>& aEndMark
) {
656 static FILE* markerFile
= MaybeOpenMarkerFile();
661 #if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
663 auto [startTimeStamp
, endTimeStamp
] =
664 GetTimeStampsForMarker(aStartMark
, aEndMark
, aOptions
, rv
);
666 if (NS_WARN_IF(rv
.Failed())) {
672 uint64_t rawStart
= startTimeStamp
.RawClockMonotonicNanosecondsSinceBoot();
673 uint64_t rawEnd
= endTimeStamp
.RawClockMonotonicNanosecondsSinceBoot();
675 uint64_t rawStart
= startTimeStamp
.RawQueryPerformanceCounterValue().value();
676 uint64_t rawEnd
= endTimeStamp
.RawQueryPerformanceCounterValue().value();
678 uint64_t rawStart
= startTimeStamp
.RawMachAbsoluteTimeNanoseconds();
679 uint64_t rawEnd
= endTimeStamp
.RawMachAbsoluteTimeNanoseconds();
681 uint64_t rawStart
= 0;
683 MOZ_CRASH("no timestamp");
685 // Write a line for this measure to the marker file. The marker file uses a
686 // text-based format where every line is one marker, and each line has the
688 // `<raw_start_timestamp> <raw_end_timestamp> <measure_name>`
690 // The timestamp value is OS specific.
691 fprintf(markerFile
, "%" PRIu64
" %" PRIu64
" %s\n", rawStart
, rawEnd
,
692 NS_ConvertUTF16toUTF8(aName
).get());
696 already_AddRefed
<PerformanceMeasure
> Performance::Measure(
697 JSContext
* aCx
, const nsAString
& aName
,
698 const StringOrPerformanceMeasureOptions
& aStartOrMeasureOptions
,
699 const Optional
<nsAString
>& aEndMark
, ErrorResult
& aRv
) {
700 if (!GetParentObject()) {
701 aRv
.ThrowInvalidStateError("Global object is unavailable");
705 // Maybe is more readable than using the union type directly.
706 Maybe
<const PerformanceMeasureOptions
&> options
;
707 if (aStartOrMeasureOptions
.IsPerformanceMeasureOptions()) {
708 options
.emplace(aStartOrMeasureOptions
.GetAsPerformanceMeasureOptions());
711 const bool isOptionsNotEmpty
=
713 (!options
->mDetail
.isUndefined() || options
->mStart
.WasPassed() ||
714 options
->mEnd
.WasPassed() || options
->mDuration
.WasPassed());
715 if (isOptionsNotEmpty
) {
716 if (aEndMark
.WasPassed()) {
718 "Cannot provide separate endMark argument if "
719 "PerformanceMeasureOptions argument is given");
723 if (!options
->mStart
.WasPassed() && !options
->mEnd
.WasPassed()) {
725 "PerformanceMeasureOptions must have start and/or end member");
729 if (options
->mStart
.WasPassed() && options
->mDuration
.WasPassed() &&
730 options
->mEnd
.WasPassed()) {
732 "PerformanceMeasureOptions cannot have all of the following members: "
733 "start, duration, and end");
738 const DOMHighResTimeStamp endTime
= ResolveEndTimeForMeasure(
739 aEndMark
, options
, aRv
, /* aReturnUnclamped */ false);
740 if (NS_WARN_IF(aRv
.Failed())) {
744 // Convert to Maybe for consistency with options.
745 Maybe
<const nsAString
&> startMark
;
746 if (aStartOrMeasureOptions
.IsString()) {
747 startMark
.emplace(aStartOrMeasureOptions
.GetAsString());
749 const DOMHighResTimeStamp startTime
= ResolveStartTimeForMeasure(
750 startMark
, options
, aRv
, /* aReturnUnclamped */ false);
751 if (NS_WARN_IF(aRv
.Failed())) {
755 JS::Rooted
<JS::Value
> detail(aCx
);
756 if (options
&& !options
->mDetail
.isNullOrUndefined()) {
757 StructuredSerializeOptions serializeOptions
;
758 JS::Rooted
<JS::Value
> valueToClone(aCx
, options
->mDetail
);
759 nsContentUtils::StructuredClone(aCx
, GetParentObject(), valueToClone
,
760 serializeOptions
, &detail
, aRv
);
769 // Perfetto requires that events are properly nested within each category.
770 // Since this is not a guarantee here, we need to define a dynamic category
771 // for each measurement so it's not prematurely ended by another measurement
772 // that overlaps. We also use the usertiming category to guard these markers
773 // so it's easy to toggle.
774 if (TRACE_EVENT_CATEGORY_ENABLED("usertiming")) {
775 NS_ConvertUTF16toUTF8
str(aName
);
776 perfetto::DynamicCategory category
{str
.get()};
777 TimeStamp startTimeStamp
=
778 CreationTimeStamp() + TimeDuration::FromMilliseconds(startTime
);
779 TimeStamp endTimeStamp
=
780 CreationTimeStamp() + TimeDuration::FromMilliseconds(endTime
);
781 PERFETTO_TRACE_EVENT_BEGIN(category
, perfetto::DynamicString
{str
.get()},
783 PERFETTO_TRACE_EVENT_END(category
, endTimeStamp
);
787 RefPtr
<PerformanceMeasure
> performanceMeasure
= new PerformanceMeasure(
788 GetParentObject(), aName
, startTime
, endTime
, detail
);
789 InsertUserEntry(performanceMeasure
);
791 MaybeEmitExternalProfilerMarker(aName
, options
, startMark
, aEndMark
);
793 if (profiler_is_collecting_markers()) {
794 auto [startTimeStamp
, endTimeStamp
] =
795 GetTimeStampsForMarker(startMark
, aEndMark
, options
, aRv
);
797 Maybe
<nsString
> endMark
;
798 if (aEndMark
.WasPassed()) {
799 endMark
.emplace(aEndMark
.Value());
802 Maybe
<uint64_t> innerWindowId
;
804 innerWindowId
= Some(GetOwner()->WindowID());
806 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
807 {MarkerTiming::Interval(startTimeStamp
, endTimeStamp
),
808 MarkerInnerWindowId(innerWindowId
)},
809 UserTimingMarker
{}, aName
, /* aIsMeasure */ true,
813 return performanceMeasure
.forget();
816 void Performance::ClearMeasures(const Optional
<nsAString
>& aName
) {
817 ClearUserEntries(aName
, u
"measure"_ns
);
820 void Performance::LogEntry(PerformanceEntry
* aEntry
,
821 const nsACString
& aOwner
) const {
822 PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64
"\n",
823 aOwner
.BeginReading(),
824 NS_ConvertUTF16toUTF8(aEntry
->GetEntryType()->GetUTF16String()).get(),
825 NS_ConvertUTF16toUTF8(aEntry
->GetName()->GetUTF16String()).get(),
826 aEntry
->StartTime(), aEntry
->Duration(),
827 static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC
));
830 void Performance::TimingNotification(PerformanceEntry
* aEntry
,
831 const nsACString
& aOwner
,
832 const double aEpoch
) {
833 PerformanceEntryEventInit init
;
834 init
.mBubbles
= false;
835 init
.mCancelable
= false;
836 aEntry
->GetName(init
.mName
);
837 aEntry
->GetEntryType(init
.mEntryType
);
838 init
.mStartTime
= aEntry
->StartTime();
839 init
.mDuration
= aEntry
->Duration();
840 init
.mEpoch
= aEpoch
;
841 CopyUTF8toUTF16(aOwner
, init
.mOrigin
);
843 RefPtr
<PerformanceEntryEvent
> perfEntryEvent
=
844 PerformanceEntryEvent::Constructor(this, u
"performanceentry"_ns
, init
);
846 nsCOMPtr
<EventTarget
> et
= do_QueryInterface(GetOwner());
848 et
->DispatchEvent(*perfEntryEvent
);
852 void Performance::InsertUserEntry(PerformanceEntry
* aEntry
) {
853 mUserEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
859 * Steps are labeled according to the description found at
860 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
864 void Performance::BufferEvent() {
866 * While resource timing secondary buffer is not empty,
867 * run the following substeps:
869 while (!mSecondaryResourceEntries
.IsEmpty()) {
870 uint32_t secondaryResourceEntriesBeforeCount
= 0;
871 uint32_t secondaryResourceEntriesAfterCount
= 0;
874 * Let number of excess entries before be resource
875 * timing secondary buffer current size.
877 secondaryResourceEntriesBeforeCount
= mSecondaryResourceEntries
.Length();
880 * If can add resource timing entry returns false,
881 * then fire an event named resourcetimingbufferfull
882 * at the Performance object.
884 if (!CanAddResourceTimingEntry()) {
885 DispatchBufferFullEvent();
889 * Run copy secondary buffer.
891 * While resource timing secondary buffer is not
892 * empty and can add resource timing entry returns
895 while (!mSecondaryResourceEntries
.IsEmpty() &&
896 CanAddResourceTimingEntry()) {
898 * Let entry be the oldest PerformanceResourceTiming
899 * in resource timing secondary buffer. Add entry to
900 * the end of performance entry buffer. Increment
901 * resource timing buffer current size by 1.
903 mResourceEntries
.InsertElementSorted(
904 mSecondaryResourceEntries
.ElementAt(0), PerformanceEntryComparator());
906 * Remove entry from resource timing secondary buffer.
907 * Decrement resource timing secondary buffer current
910 mSecondaryResourceEntries
.RemoveElementAt(0);
914 * Let number of excess entries after be resource
915 * timing secondary buffer current size.
917 secondaryResourceEntriesAfterCount
= mSecondaryResourceEntries
.Length();
920 * If number of excess entries before is lower than
921 * or equals number of excess entries after, then
922 * remove all entries from resource timing secondary
923 * buffer, set resource timing secondary buffer current
924 * size to 0, and abort these steps.
926 if (secondaryResourceEntriesBeforeCount
<=
927 secondaryResourceEntriesAfterCount
) {
928 mSecondaryResourceEntries
.Clear();
933 * Set resource timing buffer full event pending flag
936 mPendingResourceTimingBufferFullEvent
= false;
939 void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize
) {
940 mResourceTimingBufferSize
= aMaxSize
;
944 * Steps are labeled according to the description found at
945 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
947 * Can Add Resource Timing Entry
949 MOZ_ALWAYS_INLINE
bool Performance::CanAddResourceTimingEntry() {
951 * If resource timing buffer current size is smaller than resource timing
952 * buffer size limit, return true. [Otherwise,] [r]eturn false.
954 return mResourceEntries
.Length() < mResourceTimingBufferSize
;
958 * Steps are labeled according to the description found at
959 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
961 * Add a PerformanceResourceTiming Entry
963 void Performance::InsertResourceEntry(PerformanceEntry
* aEntry
) {
969 * Let new entry be the input PerformanceEntry to be added.
971 * If can add resource timing entry returns true and resource
972 * timing buffer full event pending flag is false ...
974 if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent
) {
976 * Add new entry to the performance entry buffer.
977 * Increase resource timing buffer current size by 1.
979 mResourceEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
984 * If resource timing buffer full event pending flag is
987 if (!mPendingResourceTimingBufferFullEvent
) {
989 * Set resource timing buffer full event pending flag
992 mPendingResourceTimingBufferFullEvent
= true;
995 * Queue a task to run fire a buffer full event.
997 NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
998 "Performance::BufferEvent", this, &Performance::BufferEvent
));
1001 * Add new entry to the resource timing secondary buffer.
1002 * Increase resource timing secondary buffer current size
1005 mSecondaryResourceEntries
.InsertElementSorted(aEntry
,
1006 PerformanceEntryComparator());
1009 void Performance::AddObserver(PerformanceObserver
* aObserver
) {
1010 mObservers
.AppendElementUnlessExists(aObserver
);
1013 void Performance::RemoveObserver(PerformanceObserver
* aObserver
) {
1014 mObservers
.RemoveElement(aObserver
);
1017 void Performance::NotifyObservers() {
1018 mPendingNotificationObserversTask
= false;
1019 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers
, Notify
, ());
1022 void Performance::CancelNotificationObservers() {
1023 mPendingNotificationObserversTask
= false;
1026 class NotifyObserversTask final
: public CancelableRunnable
{
1028 explicit NotifyObserversTask(Performance
* aPerformance
)
1029 : CancelableRunnable("dom::NotifyObserversTask"),
1030 mPerformance(aPerformance
) {
1031 MOZ_ASSERT(mPerformance
);
1034 // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
1035 // MOZ_CAN_RUN_SCRIPT.
1036 MOZ_CAN_RUN_SCRIPT_BOUNDARY
1037 NS_IMETHOD
Run() override
{
1038 MOZ_ASSERT(mPerformance
);
1039 RefPtr
<Performance
> performance(mPerformance
);
1040 performance
->NotifyObservers();
1044 nsresult
Cancel() override
{
1045 mPerformance
->CancelNotificationObservers();
1046 mPerformance
= nullptr;
1051 ~NotifyObserversTask() = default;
1053 RefPtr
<Performance
> mPerformance
;
1056 void Performance::QueueNotificationObserversTask() {
1057 if (!mPendingNotificationObserversTask
) {
1058 RunNotificationObserversTask();
1062 void Performance::RunNotificationObserversTask() {
1063 mPendingNotificationObserversTask
= true;
1064 nsCOMPtr
<nsIRunnable
> task
= new NotifyObserversTask(this);
1066 if (nsIGlobalObject
* global
= GetOwnerGlobal()) {
1067 rv
= global
->Dispatch(task
.forget());
1069 rv
= NS_DispatchToCurrentThread(task
.forget());
1071 if (NS_WARN_IF(NS_FAILED(rv
))) {
1072 mPendingNotificationObserversTask
= false;
1076 void Performance::QueueEntry(PerformanceEntry
* aEntry
) {
1077 nsTObserverArray
<PerformanceObserver
*> interestedObservers
;
1078 if (!mObservers
.IsEmpty()) {
1079 const auto [begin
, end
] = mObservers
.NonObservingRange();
1080 std::copy_if(begin
, end
, MakeBackInserter(interestedObservers
),
1081 [aEntry
](PerformanceObserver
* observer
) {
1082 return observer
->ObservesTypeOfEntry(aEntry
);
1086 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers
, QueueEntry
,
1089 aEntry
->BufferEntryIfNeeded();
1091 if (!interestedObservers
.IsEmpty()) {
1092 QueueNotificationObserversTask();
1096 // We could clear User entries here, but doing so could break sites that call
1097 // performance.measure() if the marks disappeared without warning. Chrome
1098 // allows "infinite" entries.
1099 void Performance::MemoryPressure() {}
1101 size_t Performance::SizeOfUserEntries(
1102 mozilla::MallocSizeOf aMallocSizeOf
) const {
1103 size_t userEntries
= 0;
1104 for (const PerformanceEntry
* entry
: mUserEntries
) {
1105 userEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
1110 size_t Performance::SizeOfResourceEntries(
1111 mozilla::MallocSizeOf aMallocSizeOf
) const {
1112 size_t resourceEntries
= 0;
1113 for (const PerformanceEntry
* entry
: mResourceEntries
) {
1114 resourceEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
1116 return resourceEntries
;
1119 } // namespace mozilla::dom