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
)
63 // Used to dump performance timing information to a local file.
64 // Only defined when the env variable MOZ_USE_PERFORMANCE_MARKER_FILE
65 // is set and initialized by MaybeOpenMarkerFile().
66 static FILE* sMarkerFile
= nullptr;
69 already_AddRefed
<Performance
> Performance::CreateForMainThread(
70 nsPIDOMWindowInner
* aWindow
, nsIPrincipal
* aPrincipal
,
71 nsDOMNavigationTiming
* aDOMTiming
, nsITimedChannel
* aChannel
) {
72 MOZ_ASSERT(NS_IsMainThread());
74 MOZ_ASSERT(aWindow
->AsGlobal());
75 RefPtr
<Performance
> performance
=
76 new PerformanceMainThread(aWindow
, aDOMTiming
, aChannel
);
77 return performance
.forget();
81 already_AddRefed
<Performance
> Performance::CreateForWorker(
82 WorkerGlobalScope
* aGlobalScope
) {
83 MOZ_ASSERT(aGlobalScope
);
84 // aWorkerPrivate->AssertIsOnWorkerThread();
86 RefPtr
<Performance
> performance
= new PerformanceWorker(aGlobalScope
);
87 return performance
.forget();
91 already_AddRefed
<Performance
> Performance::Get(JSContext
* aCx
,
92 nsIGlobalObject
* aGlobal
) {
93 RefPtr
<Performance
> performance
;
94 if (NS_IsMainThread()) {
95 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(aGlobal
);
100 performance
= window
->GetPerformance();
101 return performance
.forget();
104 const WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(aCx
);
105 if (!workerPrivate
) {
109 WorkerGlobalScope
* scope
= workerPrivate
->GlobalScope();
111 performance
= scope
->GetPerformance();
113 return performance
.forget();
116 Performance::Performance(nsIGlobalObject
* aGlobal
)
117 : DOMEventTargetHelper(aGlobal
),
118 mResourceTimingBufferSize(kDefaultResourceTimingBufferSize
),
119 mPendingNotificationObserversTask(false),
120 mPendingResourceTimingBufferFullEvent(false),
121 mRTPCallerType(aGlobal
->GetRTPCallerType()),
122 mCrossOriginIsolated(aGlobal
->CrossOriginIsolated()),
123 mShouldResistFingerprinting(aGlobal
->ShouldResistFingerprinting(
124 RFPTarget::ReduceTimerPrecision
)) {}
126 Performance::~Performance() = default;
128 DOMHighResTimeStamp
Performance::TimeStampToDOMHighResForRendering(
129 TimeStamp aTimeStamp
) const {
130 DOMHighResTimeStamp stamp
= GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp
);
131 // 0 is an inappropriate mixin for this this area; however CSS Animations
132 // needs to have it's Time Reduction Logic refactored, so it's currently
133 // only clamping for RFP mode. RFP mode gives a much lower time precision,
134 // so we accept the security leak here for now.
135 return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp
, 0,
139 DOMHighResTimeStamp
Performance::Now() {
140 DOMHighResTimeStamp rawTime
= NowUnclamped();
142 // XXX: Removing this caused functions in pkcs11f.h to fail.
143 // Bug 1628021 investigates the root cause - it involves initializing
144 // the RNG service (part of GetRandomTimelineSeed()) off-main-thread
145 // but the underlying cause hasn't been identified yet.
146 if (mRTPCallerType
== RTPCallerType::SystemPrincipal
) {
150 return nsRFPService::ReduceTimePrecisionAsMSecs(
151 rawTime
, GetRandomTimelineSeed(), mRTPCallerType
);
154 DOMHighResTimeStamp
Performance::NowUnclamped() const {
155 TimeDuration duration
= TimeStamp::Now() - CreationTimeStamp();
156 return duration
.ToMilliseconds();
159 DOMHighResTimeStamp
Performance::TimeOrigin() {
160 if (!mPerformanceService
) {
161 mPerformanceService
= PerformanceService::GetOrCreate();
164 MOZ_ASSERT(mPerformanceService
);
165 DOMHighResTimeStamp rawTimeOrigin
=
166 mPerformanceService
->TimeOrigin(CreationTimeStamp());
167 // Time Origin is an absolute timestamp, so we supply a 0 context mix-in
168 return nsRFPService::ReduceTimePrecisionAsMSecs(rawTimeOrigin
, 0,
172 JSObject
* Performance::WrapObject(JSContext
* aCx
,
173 JS::Handle
<JSObject
*> aGivenProto
) {
174 return Performance_Binding::Wrap(aCx
, this, aGivenProto
);
177 void Performance::GetEntries(nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
178 aRetval
= mResourceEntries
.Clone();
179 aRetval
.AppendElements(mUserEntries
);
180 aRetval
.Sort(PerformanceEntryComparator());
183 void Performance::GetEntriesByType(
184 const nsAString
& aEntryType
, nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
185 if (aEntryType
.EqualsLiteral("resource")) {
186 aRetval
= mResourceEntries
.Clone();
192 if (aEntryType
.EqualsLiteral("mark") || aEntryType
.EqualsLiteral("measure")) {
193 RefPtr
<nsAtom
> entryType
= NS_Atomize(aEntryType
);
194 for (PerformanceEntry
* entry
: mUserEntries
) {
195 if (entry
->GetEntryType() == entryType
) {
196 aRetval
.AppendElement(entry
);
202 void Performance::GetEntriesByName(
203 const nsAString
& aName
, const Optional
<nsAString
>& aEntryType
,
204 nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
207 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
208 RefPtr
<nsAtom
> entryType
=
209 aEntryType
.WasPassed() ? NS_Atomize(aEntryType
.Value()) : nullptr;
212 if (entryType
== nsGkAtoms::mark
|| entryType
== nsGkAtoms::measure
) {
213 for (PerformanceEntry
* entry
: mUserEntries
) {
214 if (entry
->GetName() == name
&& entry
->GetEntryType() == entryType
) {
215 aRetval
.AppendElement(entry
);
220 if (entryType
== nsGkAtoms::resource
) {
221 for (PerformanceEntry
* entry
: mResourceEntries
) {
222 MOZ_ASSERT(entry
->GetEntryType() == entryType
);
223 if (entry
->GetName() == name
) {
224 aRetval
.AppendElement(entry
);
233 nsTArray
<PerformanceEntry
*> qualifiedResourceEntries
;
234 nsTArray
<PerformanceEntry
*> qualifiedUserEntries
;
235 // ::Measure expects that results from this function are already
236 // passed through ReduceTimePrecision. mResourceEntries and mUserEntries
237 // are, so the invariant holds.
238 for (PerformanceEntry
* entry
: mResourceEntries
) {
239 if (entry
->GetName() == name
) {
240 qualifiedResourceEntries
.AppendElement(entry
);
244 for (PerformanceEntry
* entry
: mUserEntries
) {
245 if (entry
->GetName() == name
) {
246 qualifiedUserEntries
.AppendElement(entry
);
250 size_t resourceEntriesIdx
= 0, userEntriesIdx
= 0;
251 aRetval
.SetCapacity(qualifiedResourceEntries
.Length() +
252 qualifiedUserEntries
.Length());
254 PerformanceEntryComparator comparator
;
256 while (resourceEntriesIdx
< qualifiedResourceEntries
.Length() &&
257 userEntriesIdx
< qualifiedUserEntries
.Length()) {
258 if (comparator
.LessThan(qualifiedResourceEntries
[resourceEntriesIdx
],
259 qualifiedUserEntries
[userEntriesIdx
])) {
260 aRetval
.AppendElement(qualifiedResourceEntries
[resourceEntriesIdx
]);
261 ++resourceEntriesIdx
;
263 aRetval
.AppendElement(qualifiedUserEntries
[userEntriesIdx
]);
268 while (resourceEntriesIdx
< qualifiedResourceEntries
.Length()) {
269 aRetval
.AppendElement(qualifiedResourceEntries
[resourceEntriesIdx
]);
270 ++resourceEntriesIdx
;
273 while (userEntriesIdx
< qualifiedUserEntries
.Length()) {
274 aRetval
.AppendElement(qualifiedUserEntries
[userEntriesIdx
]);
279 void Performance::GetEntriesByTypeForObserver(
280 const nsAString
& aEntryType
, nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
281 GetEntriesByType(aEntryType
, aRetval
);
284 void Performance::ClearUserEntries(const Optional
<nsAString
>& aEntryName
,
285 const nsAString
& aEntryType
) {
286 MOZ_ASSERT(!aEntryType
.IsEmpty());
287 RefPtr
<nsAtom
> name
=
288 aEntryName
.WasPassed() ? NS_Atomize(aEntryName
.Value()) : nullptr;
289 RefPtr
<nsAtom
> entryType
= NS_Atomize(aEntryType
);
290 mUserEntries
.RemoveElementsBy([name
, entryType
](const auto& entry
) {
291 return (!name
|| entry
->GetName() == name
) &&
292 (entry
->GetEntryType() == entryType
);
296 void Performance::ClearResourceTimings() { mResourceEntries
.Clear(); }
298 struct UserTimingMarker
: public BaseMarkerType
<UserTimingMarker
> {
299 static constexpr const char* Name
= "UserTiming";
300 static constexpr const char* Description
=
301 "UserTimingMeasure is created using the DOM API performance.measure().";
303 using MS
= MarkerSchema
;
304 static constexpr MS::PayloadField PayloadFields
[] = {
305 {"name", MS::InputType::String
, "User Marker Name", MS::Format::String
,
306 MS::PayloadFlags::Searchable
},
307 {"entryType", MS::InputType::Boolean
, "Entry Type"},
308 {"startMark", MS::InputType::String
, "Start Mark"},
309 {"endMark", MS::InputType::String
, "End Mark"}};
311 static constexpr MS::Location Locations
[] = {MS::Location::MarkerChart
,
312 MS::Location::MarkerTable
};
313 static constexpr const char* AllLabels
= "{marker.data.name}";
315 static constexpr MS::ETWMarkerGroup Group
= MS::ETWMarkerGroup::UserMarkers
;
317 static void StreamJSONMarkerData(
318 baseprofiler::SpliceableJSONWriter
& aWriter
,
319 const ProfilerString16View
& aName
, bool aIsMeasure
,
320 const Maybe
<ProfilerString16View
>& aStartMark
,
321 const Maybe
<ProfilerString16View
>& aEndMark
) {
322 StreamJSONMarkerDataImpl(
324 aIsMeasure
? MakeStringSpan("measure") : MakeStringSpan("mark"),
325 aStartMark
, aEndMark
);
329 already_AddRefed
<PerformanceMark
> Performance::Mark(
330 JSContext
* aCx
, const nsAString
& aName
,
331 const PerformanceMarkOptions
& aMarkOptions
, ErrorResult
& aRv
) {
332 nsCOMPtr
<nsIGlobalObject
> parent
= GetParentObject();
333 if (!parent
|| parent
->IsDying() || !parent
->HasJSGlobal()) {
334 aRv
.ThrowInvalidStateError("Global object is unavailable");
338 GlobalObject
global(aCx
, parent
->GetGlobalJSObject());
339 if (global
.Failed()) {
340 aRv
.ThrowInvalidStateError("Global object is unavailable");
344 RefPtr
<PerformanceMark
> performanceMark
=
345 PerformanceMark::Constructor(global
, aName
, aMarkOptions
, aRv
);
350 InsertUserEntry(performanceMark
);
352 if (profiler_thread_is_being_profiled_for_markers()) {
353 Maybe
<uint64_t> innerWindowId
;
354 if (nsGlobalWindowInner
* owner
= GetOwnerWindow()) {
355 innerWindowId
= Some(owner
->WindowID());
357 TimeStamp startTimeStamp
=
358 CreationTimeStamp() +
359 TimeDuration::FromMilliseconds(performanceMark
->UnclampedStartTime());
360 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
361 MarkerOptions(MarkerTiming::InstantAt(startTimeStamp
),
362 MarkerInnerWindowId(innerWindowId
)),
363 UserTimingMarker
{}, aName
, /* aIsMeasure */ false,
364 Nothing
{}, Nothing
{});
367 return performanceMark
.forget();
370 void Performance::ClearMarks(const Optional
<nsAString
>& aName
) {
371 ClearUserEntries(aName
, u
"mark"_ns
);
374 // To be removed once bug 1124165 lands
375 bool Performance::IsPerformanceTimingAttribute(const nsAString
& aName
) const {
376 // Note that toJSON is added to this list due to bug 1047848
377 static const char* attributes
[] = {"navigationStart",
386 "secureConnectionStart",
393 "domContentLoadedEventStart",
394 "domContentLoadedEventEnd",
400 for (uint32_t i
= 0; attributes
[i
]; ++i
) {
401 if (aName
.EqualsASCII(attributes
[i
])) {
409 DOMHighResTimeStamp
Performance::ConvertMarkToTimestampWithString(
410 const nsAString
& aName
, ErrorResult
& aRv
, bool aReturnUnclamped
) {
411 if (IsPerformanceTimingAttribute(aName
)) {
412 return ConvertNameToTimestamp(aName
, aRv
);
415 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
416 // Just loop over the user entries
417 for (const PerformanceEntry
* entry
: Reversed(mUserEntries
)) {
418 if (entry
->GetName() == name
&& entry
->GetEntryType() == nsGkAtoms::mark
) {
419 if (aReturnUnclamped
) {
420 return entry
->UnclampedStartTime();
422 return entry
->StartTime();
426 nsPrintfCString
errorMsg("Given mark name, %s, is unknown",
427 NS_ConvertUTF16toUTF8(aName
).get());
428 aRv
.ThrowSyntaxError(errorMsg
);
432 DOMHighResTimeStamp
Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
433 const ResolveTimestampAttribute aAttribute
,
434 const DOMHighResTimeStamp aTimestamp
, ErrorResult
& aRv
) {
435 if (aTimestamp
< 0) {
436 nsAutoCString attributeName
;
437 switch (aAttribute
) {
438 case ResolveTimestampAttribute::Start
:
439 attributeName
= "start";
441 case ResolveTimestampAttribute::End
:
442 attributeName
= "end";
444 case ResolveTimestampAttribute::Duration
:
445 attributeName
= "duration";
449 nsPrintfCString
errorMsg("Given attribute %s cannot be negative",
450 attributeName
.get());
451 aRv
.ThrowTypeError(errorMsg
);
456 DOMHighResTimeStamp
Performance::ConvertMarkToTimestamp(
457 const ResolveTimestampAttribute aAttribute
,
458 const OwningStringOrDouble
& aMarkNameOrTimestamp
, ErrorResult
& aRv
,
459 bool aReturnUnclamped
) {
460 if (aMarkNameOrTimestamp
.IsString()) {
461 return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp
.GetAsString(),
462 aRv
, aReturnUnclamped
);
465 return ConvertMarkToTimestampWithDOMHighResTimeStamp(
466 aAttribute
, aMarkNameOrTimestamp
.GetAsDouble(), aRv
);
469 DOMHighResTimeStamp
Performance::ConvertNameToTimestamp(const nsAString
& aName
,
471 if (!IsGlobalObjectWindow()) {
472 nsPrintfCString
errorMsg(
473 "Cannot get PerformanceTiming attribute values for non-Window global "
475 NS_ConvertUTF16toUTF8(aName
).get());
476 aRv
.ThrowTypeError(errorMsg
);
480 if (aName
.EqualsASCII("navigationStart")) {
484 // We use GetPerformanceTimingFromString, rather than calling the
485 // navigationStart method timing function directly, because the former handles
486 // reducing precision against timing attacks.
487 const DOMHighResTimeStamp startTime
=
488 GetPerformanceTimingFromString(u
"navigationStart"_ns
);
489 const DOMHighResTimeStamp endTime
= GetPerformanceTimingFromString(aName
);
490 MOZ_ASSERT(endTime
>= 0);
492 nsPrintfCString
errorMsg(
493 "Given PerformanceTiming attribute, %s, isn't available yet",
494 NS_ConvertUTF16toUTF8(aName
).get());
495 aRv
.ThrowInvalidAccessError(errorMsg
);
499 return endTime
- startTime
;
502 DOMHighResTimeStamp
Performance::ResolveEndTimeForMeasure(
503 const Optional
<nsAString
>& aEndMark
,
504 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
,
505 bool aReturnUnclamped
) {
506 DOMHighResTimeStamp endTime
;
507 if (aEndMark
.WasPassed()) {
508 endTime
= ConvertMarkToTimestampWithString(aEndMark
.Value(), aRv
,
510 } else if (aOptions
&& aOptions
->mEnd
.WasPassed()) {
512 ConvertMarkToTimestamp(ResolveTimestampAttribute::End
,
513 aOptions
->mEnd
.Value(), aRv
, aReturnUnclamped
);
514 } else if (aOptions
&& aOptions
->mStart
.WasPassed() &&
515 aOptions
->mDuration
.WasPassed()) {
516 const DOMHighResTimeStamp start
=
517 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start
,
518 aOptions
->mStart
.Value(), aRv
, aReturnUnclamped
);
523 const DOMHighResTimeStamp duration
=
524 ConvertMarkToTimestampWithDOMHighResTimeStamp(
525 ResolveTimestampAttribute::Duration
, aOptions
->mDuration
.Value(),
531 endTime
= start
+ duration
;
532 } else if (aReturnUnclamped
) {
533 MOZ_DIAGNOSTIC_ASSERT(sMarkerFile
||
534 profiler_thread_is_being_profiled_for_markers());
535 endTime
= NowUnclamped();
543 DOMHighResTimeStamp
Performance::ResolveStartTimeForMeasure(
544 const Maybe
<const nsAString
&>& aStartMark
,
545 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
,
546 bool aReturnUnclamped
) {
547 DOMHighResTimeStamp startTime
;
548 if (aOptions
&& aOptions
->mStart
.WasPassed()) {
550 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start
,
551 aOptions
->mStart
.Value(), aRv
, aReturnUnclamped
);
552 } else if (aOptions
&& aOptions
->mDuration
.WasPassed() &&
553 aOptions
->mEnd
.WasPassed()) {
554 const DOMHighResTimeStamp duration
=
555 ConvertMarkToTimestampWithDOMHighResTimeStamp(
556 ResolveTimestampAttribute::Duration
, aOptions
->mDuration
.Value(),
562 const DOMHighResTimeStamp end
=
563 ConvertMarkToTimestamp(ResolveTimestampAttribute::End
,
564 aOptions
->mEnd
.Value(), aRv
, aReturnUnclamped
);
569 startTime
= end
- duration
;
570 } else if (aStartMark
) {
572 ConvertMarkToTimestampWithString(*aStartMark
, aRv
, aReturnUnclamped
);
580 static std::string
GetMarkerFilename() {
582 if (char* markerDir
= getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
583 s
<< markerDir
<< "/";
586 s
<< "marker-" << GetCurrentProcessId() << ".txt";
588 s
<< "marker-" << getpid() << ".txt";
593 std::pair
<TimeStamp
, TimeStamp
> Performance::GetTimeStampsForMarker(
594 const Maybe
<const nsAString
&>& aStartMark
,
595 const Optional
<nsAString
>& aEndMark
,
596 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
) {
597 const DOMHighResTimeStamp unclampedStartTime
= ResolveStartTimeForMeasure(
598 aStartMark
, aOptions
, aRv
, /* aReturnUnclamped */ true);
599 const DOMHighResTimeStamp unclampedEndTime
=
600 ResolveEndTimeForMeasure(aEndMark
, aOptions
, aRv
, /* aReturnUnclamped */
603 TimeStamp startTimeStamp
=
604 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedStartTime
);
605 TimeStamp endTimeStamp
=
606 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedEndTime
);
608 return std::make_pair(startTimeStamp
, endTimeStamp
);
611 // Try to open the marker file for writing performance markers.
612 // If successful, returns true and sMarkerFile will be defined
613 // to the file handle. Otherwise, return false and sMarkerFile
615 static bool MaybeOpenMarkerFile() {
616 if (!getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")) {
620 // Check if it's already open.
626 // We treat marker files similar to Jitdump files (see PerfSpewer.cpp) and
627 // mmap them if needed.
628 int fd
= open(GetMarkerFilename().c_str(), O_CREAT
| O_TRUNC
| O_RDWR
, 0666);
629 sMarkerFile
= fdopen(fd
, "w+");
634 // On Linux and Android, we need to mmap the file so that the path makes it
635 // into the perf.data file or into samply.
636 // On non-Android, make the mapping executable, otherwise the MMAP event may
637 // not be recorded by perf (see perf_event_open mmap_data).
638 // But on Android, don't make the mapping executable, because doing so can
639 // make the mmap call fail on some Android devices. It's also not required on
640 // Android because simpleperf sets mmap_data = 1 for unrelated reasons (it
641 // wants to know about vdex files for Java JIT profiling, see
642 // SetRecordNotExecutableMaps).
643 int protection
= PROT_READ
;
645 protection
|= PROT_EXEC
;
648 // Mmap just the first page - that's enough to ensure the path makes it into
650 long page_size
= sysconf(_SC_PAGESIZE
);
651 void* mmap_address
= mmap(nullptr, page_size
, protection
, MAP_PRIVATE
, fd
, 0);
652 if (mmap_address
== MAP_FAILED
) {
654 sMarkerFile
= nullptr;
658 // On macOS, we just need to `open` or `fopen` the marker file, and samply
659 // will know its path because it hooks those functions - no mmap needed.
660 // On Windows, there's no need to use MOZ_USE_PERFORMANCE_MARKER_FILE because
661 // we have ETW trace events for UserTiming measures. Still, we want this code
662 // to compile successfully on Windows, so we use fopen rather than
664 sMarkerFile
= fopen(GetMarkerFilename().c_str(), "w+");
672 // This emits markers to an external marker-[pid].txt file for use by an
673 // external profiler like samply or etw-gecko
674 void Performance::MaybeEmitExternalProfilerMarker(
675 const nsAString
& aName
, Maybe
<const PerformanceMeasureOptions
&> aOptions
,
676 Maybe
<const nsAString
&> aStartMark
, const Optional
<nsAString
>& aEndMark
) {
677 if (!MaybeOpenMarkerFile()) {
681 #if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
683 auto [startTimeStamp
, endTimeStamp
] =
684 GetTimeStampsForMarker(aStartMark
, aEndMark
, aOptions
, rv
);
686 if (NS_WARN_IF(rv
.Failed())) {
692 uint64_t rawStart
= startTimeStamp
.RawClockMonotonicNanosecondsSinceBoot();
693 uint64_t rawEnd
= endTimeStamp
.RawClockMonotonicNanosecondsSinceBoot();
695 uint64_t rawStart
= startTimeStamp
.RawQueryPerformanceCounterValue().value();
696 uint64_t rawEnd
= endTimeStamp
.RawQueryPerformanceCounterValue().value();
698 uint64_t rawStart
= startTimeStamp
.RawMachAbsoluteTimeNanoseconds();
699 uint64_t rawEnd
= endTimeStamp
.RawMachAbsoluteTimeNanoseconds();
701 uint64_t rawStart
= 0;
703 MOZ_CRASH("no timestamp");
705 // Write a line for this measure to the marker file. The marker file uses a
706 // text-based format where every line is one marker, and each line has the
708 // `<raw_start_timestamp> <raw_end_timestamp> <measure_name>`
710 // The timestamp value is OS specific.
711 fprintf(sMarkerFile
, "%" PRIu64
" %" PRIu64
" %s\n", rawStart
, rawEnd
,
712 NS_ConvertUTF16toUTF8(aName
).get());
716 already_AddRefed
<PerformanceMeasure
> Performance::Measure(
717 JSContext
* aCx
, const nsAString
& aName
,
718 const StringOrPerformanceMeasureOptions
& aStartOrMeasureOptions
,
719 const Optional
<nsAString
>& aEndMark
, ErrorResult
& aRv
) {
720 if (!GetParentObject()) {
721 aRv
.ThrowInvalidStateError("Global object is unavailable");
725 // Maybe is more readable than using the union type directly.
726 Maybe
<const PerformanceMeasureOptions
&> options
;
727 if (aStartOrMeasureOptions
.IsPerformanceMeasureOptions()) {
728 options
.emplace(aStartOrMeasureOptions
.GetAsPerformanceMeasureOptions());
731 const bool isOptionsNotEmpty
=
733 (!options
->mDetail
.isUndefined() || options
->mStart
.WasPassed() ||
734 options
->mEnd
.WasPassed() || options
->mDuration
.WasPassed());
735 if (isOptionsNotEmpty
) {
736 if (aEndMark
.WasPassed()) {
738 "Cannot provide separate endMark argument if "
739 "PerformanceMeasureOptions argument is given");
743 if (!options
->mStart
.WasPassed() && !options
->mEnd
.WasPassed()) {
745 "PerformanceMeasureOptions must have start and/or end member");
749 if (options
->mStart
.WasPassed() && options
->mDuration
.WasPassed() &&
750 options
->mEnd
.WasPassed()) {
752 "PerformanceMeasureOptions cannot have all of the following members: "
753 "start, duration, and end");
758 const DOMHighResTimeStamp endTime
= ResolveEndTimeForMeasure(
759 aEndMark
, options
, aRv
, /* aReturnUnclamped */ false);
760 if (NS_WARN_IF(aRv
.Failed())) {
764 // Convert to Maybe for consistency with options.
765 Maybe
<const nsAString
&> startMark
;
766 if (aStartOrMeasureOptions
.IsString()) {
767 startMark
.emplace(aStartOrMeasureOptions
.GetAsString());
769 const DOMHighResTimeStamp startTime
= ResolveStartTimeForMeasure(
770 startMark
, options
, aRv
, /* aReturnUnclamped */ false);
771 if (NS_WARN_IF(aRv
.Failed())) {
775 JS::Rooted
<JS::Value
> detail(aCx
);
776 if (options
&& !options
->mDetail
.isNullOrUndefined()) {
777 StructuredSerializeOptions serializeOptions
;
778 JS::Rooted
<JS::Value
> valueToClone(aCx
, options
->mDetail
);
779 nsContentUtils::StructuredClone(aCx
, GetParentObject(), valueToClone
,
780 serializeOptions
, &detail
, aRv
);
789 // Perfetto requires that events are properly nested within each category.
790 // Since this is not a guarantee here, we need to define a dynamic category
791 // for each measurement so it's not prematurely ended by another measurement
792 // that overlaps. We also use the usertiming category to guard these markers
793 // so it's easy to toggle.
794 if (TRACE_EVENT_CATEGORY_ENABLED("usertiming")) {
795 NS_ConvertUTF16toUTF8
str(aName
);
796 perfetto::DynamicCategory category
{str
.get()};
797 TimeStamp startTimeStamp
=
798 CreationTimeStamp() + TimeDuration::FromMilliseconds(startTime
);
799 TimeStamp endTimeStamp
=
800 CreationTimeStamp() + TimeDuration::FromMilliseconds(endTime
);
801 PERFETTO_TRACE_EVENT_BEGIN(category
, perfetto::DynamicString
{str
.get()},
803 PERFETTO_TRACE_EVENT_END(category
, endTimeStamp
);
807 RefPtr
<PerformanceMeasure
> performanceMeasure
= new PerformanceMeasure(
808 GetParentObject(), aName
, startTime
, endTime
, detail
);
809 InsertUserEntry(performanceMeasure
);
811 MaybeEmitExternalProfilerMarker(aName
, options
, startMark
, aEndMark
);
813 if (profiler_thread_is_being_profiled_for_markers()) {
814 auto [startTimeStamp
, endTimeStamp
] =
815 GetTimeStampsForMarker(startMark
, aEndMark
, options
, aRv
);
817 Maybe
<nsString
> endMark
;
818 if (aEndMark
.WasPassed()) {
819 endMark
.emplace(aEndMark
.Value());
822 Maybe
<uint64_t> innerWindowId
;
823 if (nsGlobalWindowInner
* owner
= GetOwnerWindow()) {
824 innerWindowId
= Some(owner
->WindowID());
826 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
827 {MarkerTiming::Interval(startTimeStamp
, endTimeStamp
),
828 MarkerInnerWindowId(innerWindowId
)},
829 UserTimingMarker
{}, aName
, /* aIsMeasure */ true,
833 return performanceMeasure
.forget();
836 void Performance::ClearMeasures(const Optional
<nsAString
>& aName
) {
837 ClearUserEntries(aName
, u
"measure"_ns
);
840 void Performance::LogEntry(PerformanceEntry
* aEntry
,
841 const nsACString
& aOwner
) const {
842 PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64
"\n",
843 aOwner
.BeginReading(),
844 NS_ConvertUTF16toUTF8(aEntry
->GetEntryType()->GetUTF16String()).get(),
845 NS_ConvertUTF16toUTF8(aEntry
->GetName()->GetUTF16String()).get(),
846 aEntry
->StartTime(), aEntry
->Duration(),
847 static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC
));
850 void Performance::TimingNotification(PerformanceEntry
* aEntry
,
851 const nsACString
& aOwner
,
852 const double aEpoch
) {
853 PerformanceEntryEventInit init
;
854 init
.mBubbles
= false;
855 init
.mCancelable
= false;
856 aEntry
->GetName(init
.mName
);
857 aEntry
->GetEntryType(init
.mEntryType
);
858 init
.mStartTime
= aEntry
->StartTime();
859 init
.mDuration
= aEntry
->Duration();
860 init
.mEpoch
= aEpoch
;
861 CopyUTF8toUTF16(aOwner
, init
.mOrigin
);
863 RefPtr
<PerformanceEntryEvent
> perfEntryEvent
=
864 PerformanceEntryEvent::Constructor(this, u
"performanceentry"_ns
, init
);
865 if (RefPtr
<nsGlobalWindowInner
> owner
= GetOwnerWindow()) {
866 owner
->DispatchEvent(*perfEntryEvent
);
870 void Performance::InsertUserEntry(PerformanceEntry
* aEntry
) {
871 mUserEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
877 * Steps are labeled according to the description found at
878 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
882 void Performance::BufferEvent() {
884 * While resource timing secondary buffer is not empty,
885 * run the following substeps:
887 while (!mSecondaryResourceEntries
.IsEmpty()) {
888 uint32_t secondaryResourceEntriesBeforeCount
= 0;
889 uint32_t secondaryResourceEntriesAfterCount
= 0;
892 * Let number of excess entries before be resource
893 * timing secondary buffer current size.
895 secondaryResourceEntriesBeforeCount
= mSecondaryResourceEntries
.Length();
898 * If can add resource timing entry returns false,
899 * then fire an event named resourcetimingbufferfull
900 * at the Performance object.
902 if (!CanAddResourceTimingEntry()) {
903 DispatchBufferFullEvent();
907 * Run copy secondary buffer.
909 * While resource timing secondary buffer is not
910 * empty and can add resource timing entry returns
913 while (!mSecondaryResourceEntries
.IsEmpty() &&
914 CanAddResourceTimingEntry()) {
916 * Let entry be the oldest PerformanceResourceTiming
917 * in resource timing secondary buffer. Add entry to
918 * the end of performance entry buffer. Increment
919 * resource timing buffer current size by 1.
921 mResourceEntries
.InsertElementSorted(
922 mSecondaryResourceEntries
.ElementAt(0), PerformanceEntryComparator());
924 * Remove entry from resource timing secondary buffer.
925 * Decrement resource timing secondary buffer current
928 mSecondaryResourceEntries
.RemoveElementAt(0);
932 * Let number of excess entries after be resource
933 * timing secondary buffer current size.
935 secondaryResourceEntriesAfterCount
= mSecondaryResourceEntries
.Length();
938 * If number of excess entries before is lower than
939 * or equals number of excess entries after, then
940 * remove all entries from resource timing secondary
941 * buffer, set resource timing secondary buffer current
942 * size to 0, and abort these steps.
944 if (secondaryResourceEntriesBeforeCount
<=
945 secondaryResourceEntriesAfterCount
) {
946 mSecondaryResourceEntries
.Clear();
951 * Set resource timing buffer full event pending flag
954 mPendingResourceTimingBufferFullEvent
= false;
957 void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize
) {
958 mResourceTimingBufferSize
= aMaxSize
;
962 * Steps are labeled according to the description found at
963 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
965 * Can Add Resource Timing Entry
967 MOZ_ALWAYS_INLINE
bool Performance::CanAddResourceTimingEntry() {
969 * If resource timing buffer current size is smaller than resource timing
970 * buffer size limit, return true. [Otherwise,] [r]eturn false.
972 return mResourceEntries
.Length() < mResourceTimingBufferSize
;
976 * Steps are labeled according to the description found at
977 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
979 * Add a PerformanceResourceTiming Entry
981 void Performance::InsertResourceEntry(PerformanceEntry
* aEntry
) {
987 * Let new entry be the input PerformanceEntry to be added.
989 * If can add resource timing entry returns true and resource
990 * timing buffer full event pending flag is false ...
992 if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent
) {
994 * Add new entry to the performance entry buffer.
995 * Increase resource timing buffer current size by 1.
997 mResourceEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
1002 * If resource timing buffer full event pending flag is
1005 if (!mPendingResourceTimingBufferFullEvent
) {
1007 * Set resource timing buffer full event pending flag
1010 mPendingResourceTimingBufferFullEvent
= true;
1013 * Queue a task to run fire a buffer full event.
1015 NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
1016 "Performance::BufferEvent", this, &Performance::BufferEvent
));
1019 * Add new entry to the resource timing secondary buffer.
1020 * Increase resource timing secondary buffer current size
1023 mSecondaryResourceEntries
.InsertElementSorted(aEntry
,
1024 PerformanceEntryComparator());
1027 void Performance::AddObserver(PerformanceObserver
* aObserver
) {
1028 mObservers
.AppendElementUnlessExists(aObserver
);
1031 void Performance::RemoveObserver(PerformanceObserver
* aObserver
) {
1032 mObservers
.RemoveElement(aObserver
);
1035 void Performance::NotifyObservers() {
1036 mPendingNotificationObserversTask
= false;
1037 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers
, Notify
, ());
1040 void Performance::CancelNotificationObservers() {
1041 mPendingNotificationObserversTask
= false;
1044 class NotifyObserversTask final
: public CancelableRunnable
{
1046 explicit NotifyObserversTask(Performance
* aPerformance
)
1047 : CancelableRunnable("dom::NotifyObserversTask"),
1048 mPerformance(aPerformance
) {
1049 MOZ_ASSERT(mPerformance
);
1052 // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
1053 // MOZ_CAN_RUN_SCRIPT.
1054 MOZ_CAN_RUN_SCRIPT_BOUNDARY
1055 NS_IMETHOD
Run() override
{
1056 MOZ_ASSERT(mPerformance
);
1057 RefPtr
<Performance
> performance(mPerformance
);
1058 performance
->NotifyObservers();
1062 nsresult
Cancel() override
{
1063 mPerformance
->CancelNotificationObservers();
1064 mPerformance
= nullptr;
1069 ~NotifyObserversTask() = default;
1071 RefPtr
<Performance
> mPerformance
;
1074 void Performance::QueueNotificationObserversTask() {
1075 if (!mPendingNotificationObserversTask
) {
1076 RunNotificationObserversTask();
1080 void Performance::RunNotificationObserversTask() {
1081 mPendingNotificationObserversTask
= true;
1082 nsCOMPtr
<nsIRunnable
> task
= new NotifyObserversTask(this);
1084 if (nsIGlobalObject
* global
= GetOwnerGlobal()) {
1085 rv
= global
->Dispatch(task
.forget());
1087 rv
= NS_DispatchToCurrentThread(task
.forget());
1089 if (NS_WARN_IF(NS_FAILED(rv
))) {
1090 mPendingNotificationObserversTask
= false;
1094 void Performance::QueueEntry(PerformanceEntry
* aEntry
) {
1095 nsTObserverArray
<PerformanceObserver
*> interestedObservers
;
1096 if (!mObservers
.IsEmpty()) {
1097 const auto [begin
, end
] = mObservers
.NonObservingRange();
1098 std::copy_if(begin
, end
, MakeBackInserter(interestedObservers
),
1099 [aEntry
](PerformanceObserver
* observer
) {
1100 return observer
->ObservesTypeOfEntry(aEntry
);
1104 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers
, QueueEntry
,
1107 aEntry
->BufferEntryIfNeeded();
1109 if (!interestedObservers
.IsEmpty()) {
1110 QueueNotificationObserversTask();
1114 // We could clear User entries here, but doing so could break sites that call
1115 // performance.measure() if the marks disappeared without warning. Chrome
1116 // allows "infinite" entries.
1117 void Performance::MemoryPressure() {}
1119 size_t Performance::SizeOfUserEntries(
1120 mozilla::MallocSizeOf aMallocSizeOf
) const {
1121 size_t userEntries
= 0;
1122 for (const PerformanceEntry
* entry
: mUserEntries
) {
1123 userEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
1128 size_t Performance::SizeOfResourceEntries(
1129 mozilla::MallocSizeOf aMallocSizeOf
) const {
1130 size_t resourceEntries
= 0;
1131 for (const PerformanceEntry
* entry
: mResourceEntries
) {
1132 resourceEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
1134 return resourceEntries
;
1137 } // namespace mozilla::dom