Bug 1874684 - Part 6: Limit day length calculations to safe integers. r=mgaudet
[gecko.git] / dom / performance / Performance.cpp
blob070bca9ec4b0670b7bba809c279b0862fd521b64
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"
9 #include <sstream>
11 #if defined(XP_LINUX)
12 # include <fcntl.h>
13 # include <sys/mman.h>
14 #endif
16 #include "ETWTools.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 {
48 Start,
49 End,
50 Duration,
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 /* static */
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();
75 /* static */
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();
85 /* static */
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);
91 if (!window) {
92 return nullptr;
95 performance = window->GetPerformance();
96 return performance.forget();
99 const WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
100 if (!workerPrivate) {
101 return nullptr;
104 WorkerGlobalScope* scope = workerPrivate->GlobalScope();
105 MOZ_ASSERT(scope);
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,
131 mRTPCallerType);
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) {
142 return rawTime;
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,
164 mRTPCallerType);
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();
182 return;
185 aRetval.Clear();
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) {
200 aRetval.Clear();
202 RefPtr<nsAtom> name = NS_Atomize(aName);
203 RefPtr<nsAtom> entryType =
204 aEntryType.WasPassed() ? NS_Atomize(aEntryType.Value()) : nullptr;
206 if (entryType) {
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);
213 return;
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);
222 return;
224 // Invalid entryType
225 return;
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;
257 } else {
258 aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
259 ++userEntriesIdx;
263 while (resourceEntriesIdx < qualifiedResourceEntries.Length()) {
264 aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
265 ++resourceEntriesIdx;
268 while (userEntriesIdx < qualifiedUserEntries.Length()) {
269 aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
270 ++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(
318 aWriter, aName,
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");
330 return nullptr;
333 GlobalObject global(aCx, parent->GetGlobalJSObject());
334 if (global.Failed()) {
335 aRv.ThrowInvalidStateError("Global object is unavailable");
336 return nullptr;
339 RefPtr<PerformanceMark> performanceMark =
340 PerformanceMark::Constructor(global, aName, aMarkOptions, aRv);
341 if (aRv.Failed()) {
342 return nullptr;
345 InsertUserEntry(performanceMark);
347 if (profiler_is_collecting_markers()) {
348 Maybe<uint64_t> innerWindowId;
349 if (GetOwner()) {
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",
373 "unloadEventStart",
374 "unloadEventEnd",
375 "redirectStart",
376 "redirectEnd",
377 "fetchStart",
378 "domainLookupStart",
379 "domainLookupEnd",
380 "connectStart",
381 "secureConnectionStart",
382 "connectEnd",
383 "requestStart",
384 "responseStart",
385 "responseEnd",
386 "domLoading",
387 "domInteractive",
388 "domContentLoadedEventStart",
389 "domContentLoadedEventEnd",
390 "domComplete",
391 "loadEventStart",
392 "loadEventEnd",
393 nullptr};
395 for (uint32_t i = 0; attributes[i]; ++i) {
396 if (aName.EqualsASCII(attributes[i])) {
397 return true;
401 return false;
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);
424 return 0;
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";
435 break;
436 case ResolveTimestampAttribute::End:
437 attributeName = "end";
438 break;
439 case ResolveTimestampAttribute::Duration:
440 attributeName = "duration";
441 break;
444 nsPrintfCString errorMsg("Given attribute %s cannot be negative",
445 attributeName.get());
446 aRv.ThrowTypeError(errorMsg);
448 return aTimestamp;
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,
465 ErrorResult& aRv) {
466 if (!IsGlobalObjectWindow()) {
467 nsPrintfCString errorMsg(
468 "Cannot get PerformanceTiming attribute values for non-Window global "
469 "object. Given: %s",
470 NS_ConvertUTF16toUTF8(aName).get());
471 aRv.ThrowTypeError(errorMsg);
472 return 0;
475 if (aName.EqualsASCII("navigationStart")) {
476 return 0;
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);
486 if (endTime == 0) {
487 nsPrintfCString errorMsg(
488 "Given PerformanceTiming attribute, %s, isn't available yet",
489 NS_ConvertUTF16toUTF8(aName).get());
490 aRv.ThrowInvalidAccessError(errorMsg);
491 return 0;
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,
504 aReturnUnclamped);
505 } else if (aOptions && aOptions->mEnd.WasPassed()) {
506 endTime =
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);
514 if (aRv.Failed()) {
515 return 0;
518 const DOMHighResTimeStamp duration =
519 ConvertMarkToTimestampWithDOMHighResTimeStamp(
520 ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
521 aRv);
522 if (aRv.Failed()) {
523 return 0;
526 endTime = start + duration;
527 } else {
528 endTime = Now();
531 return endTime;
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()) {
540 startTime =
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(),
548 aRv);
549 if (aRv.Failed()) {
550 return 0;
553 const DOMHighResTimeStamp end =
554 ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
555 aOptions->mEnd.Value(), aRv, aReturnUnclamped);
556 if (aRv.Failed()) {
557 return 0;
560 startTime = end - duration;
561 } else if (aStartMark) {
562 startTime =
563 ConvertMarkToTimestampWithString(*aStartMark, aRv, aReturnUnclamped);
564 } else {
565 startTime = 0;
568 return startTime;
571 static std::string GetMarkerFilename() {
572 std::stringstream s;
573 if (char* markerDir = getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
574 s << markerDir << "/";
576 #ifdef XP_WIN
577 s << "marker-" << GetCurrentProcessId() << ".txt";
578 #else
579 s << "marker-" << getpid() << ".txt";
580 #endif
581 return s.str();
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 */
592 true);
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")) {
604 return nullptr;
607 #ifdef XP_LINUX
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+");
613 if (!markerFile) {
614 return nullptr;
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;
627 # ifndef ANDROID
628 protection |= PROT_EXEC;
629 # endif
631 // Mmap just the first page - that's enough to ensure the path makes it into
632 // the recording.
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) {
636 fclose(markerFile);
637 return nullptr;
639 return markerFile;
640 #else
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
646 // open+fdopen.
647 return fopen(GetMarkerFilename().c_str(), "w+");
648 #endif
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();
657 if (!markerFile) {
658 return;
661 #if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
662 ErrorResult rv;
663 auto [startTimeStamp, endTimeStamp] =
664 GetTimeStampsForMarker(aStartMark, aEndMark, aOptions, rv);
666 if (NS_WARN_IF(rv.Failed())) {
667 return;
669 #endif
671 #ifdef XP_LINUX
672 uint64_t rawStart = startTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
673 uint64_t rawEnd = endTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
674 #elif XP_WIN
675 uint64_t rawStart = startTimeStamp.RawQueryPerformanceCounterValue().value();
676 uint64_t rawEnd = endTimeStamp.RawQueryPerformanceCounterValue().value();
677 #elif XP_MACOSX
678 uint64_t rawStart = startTimeStamp.RawMachAbsoluteTimeNanoseconds();
679 uint64_t rawEnd = endTimeStamp.RawMachAbsoluteTimeNanoseconds();
680 #else
681 uint64_t rawStart = 0;
682 uint64_t rawEnd = 0;
683 MOZ_CRASH("no timestamp");
684 #endif
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
687 // format:
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());
693 fflush(markerFile);
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");
702 return nullptr;
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 =
712 options.isSome() &&
713 (!options->mDetail.isUndefined() || options->mStart.WasPassed() ||
714 options->mEnd.WasPassed() || options->mDuration.WasPassed());
715 if (isOptionsNotEmpty) {
716 if (aEndMark.WasPassed()) {
717 aRv.ThrowTypeError(
718 "Cannot provide separate endMark argument if "
719 "PerformanceMeasureOptions argument is given");
720 return nullptr;
723 if (!options->mStart.WasPassed() && !options->mEnd.WasPassed()) {
724 aRv.ThrowTypeError(
725 "PerformanceMeasureOptions must have start and/or end member");
726 return nullptr;
729 if (options->mStart.WasPassed() && options->mDuration.WasPassed() &&
730 options->mEnd.WasPassed()) {
731 aRv.ThrowTypeError(
732 "PerformanceMeasureOptions cannot have all of the following members: "
733 "start, duration, and end");
734 return nullptr;
738 const DOMHighResTimeStamp endTime = ResolveEndTimeForMeasure(
739 aEndMark, options, aRv, /* aReturnUnclamped */ false);
740 if (NS_WARN_IF(aRv.Failed())) {
741 return nullptr;
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())) {
752 return nullptr;
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);
761 if (aRv.Failed()) {
762 return nullptr;
764 } else {
765 detail.setNull();
768 #ifdef MOZ_PERFETTO
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()},
782 startTimeStamp);
783 PERFETTO_TRACE_EVENT_END(category, endTimeStamp);
785 #endif
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;
803 if (GetOwner()) {
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,
810 startMark, endMark);
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());
847 if (et) {
848 et->DispatchEvent(*perfEntryEvent);
852 void Performance::InsertUserEntry(PerformanceEntry* aEntry) {
853 mUserEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
855 QueueEntry(aEntry);
859 * Steps are labeled according to the description found at
860 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
862 * Buffer Full Event
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
893 * true ...
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
908 * size by 1.
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();
929 break;
933 * Set resource timing buffer full event pending flag
934 * to false.
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) {
964 MOZ_ASSERT(aEntry);
966 QueueEntry(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());
980 return;
984 * If resource timing buffer full event pending flag is
985 * false ...
987 if (!mPendingResourceTimingBufferFullEvent) {
989 * Set resource timing buffer full event pending flag
990 * to true.
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
1003 * by 1.
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 {
1027 public:
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();
1041 return NS_OK;
1044 nsresult Cancel() override {
1045 mPerformance->CancelNotificationObservers();
1046 mPerformance = nullptr;
1047 return NS_OK;
1050 private:
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);
1065 nsresult rv;
1066 if (nsIGlobalObject* global = GetOwnerGlobal()) {
1067 rv = global->Dispatch(task.forget());
1068 } else {
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,
1087 (aEntry));
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);
1107 return userEntries;
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