no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / performance / Performance.cpp
blobecbc3b4c687530438ad233e06e8c468680ec679a
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 #include "ETWTools.h"
12 #include "GeckoProfiler.h"
13 #include "nsRFPService.h"
14 #include "PerformanceEntry.h"
15 #include "PerformanceMainThread.h"
16 #include "PerformanceMark.h"
17 #include "PerformanceMeasure.h"
18 #include "PerformanceObserver.h"
19 #include "PerformanceResourceTiming.h"
20 #include "PerformanceService.h"
21 #include "PerformanceWorker.h"
22 #include "mozilla/BasePrincipal.h"
23 #include "mozilla/ErrorResult.h"
24 #include "mozilla/dom/MessagePortBinding.h"
25 #include "mozilla/dom/PerformanceBinding.h"
26 #include "mozilla/dom/PerformanceEntryEvent.h"
27 #include "mozilla/dom/PerformanceNavigationBinding.h"
28 #include "mozilla/dom/PerformanceObserverBinding.h"
29 #include "mozilla/dom/PerformanceNavigationTiming.h"
30 #include "mozilla/IntegerPrintfMacros.h"
31 #include "mozilla/Preferences.h"
32 #include "mozilla/TimeStamp.h"
33 #include "mozilla/dom/WorkerPrivate.h"
34 #include "mozilla/dom/WorkerRunnable.h"
35 #include "mozilla/dom/WorkerScope.h"
37 #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
39 namespace mozilla::dom {
41 enum class Performance::ResolveTimestampAttribute {
42 Start,
43 End,
44 Duration,
47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance)
48 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
50 NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance, DOMEventTargetHelper,
51 mUserEntries, mResourceEntries,
52 mSecondaryResourceEntries, mObservers);
54 NS_IMPL_ADDREF_INHERITED(Performance, DOMEventTargetHelper)
55 NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper)
57 /* static */
58 already_AddRefed<Performance> Performance::CreateForMainThread(
59 nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
60 nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel) {
61 MOZ_ASSERT(NS_IsMainThread());
63 MOZ_ASSERT(aWindow->AsGlobal());
64 RefPtr<Performance> performance =
65 new PerformanceMainThread(aWindow, aDOMTiming, aChannel);
66 return performance.forget();
69 /* static */
70 already_AddRefed<Performance> Performance::CreateForWorker(
71 WorkerGlobalScope* aGlobalScope) {
72 MOZ_ASSERT(aGlobalScope);
73 // aWorkerPrivate->AssertIsOnWorkerThread();
75 RefPtr<Performance> performance = new PerformanceWorker(aGlobalScope);
76 return performance.forget();
79 /* static */
80 already_AddRefed<Performance> Performance::Get(JSContext* aCx,
81 nsIGlobalObject* aGlobal) {
82 RefPtr<Performance> performance;
83 if (NS_IsMainThread()) {
84 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
85 if (!window) {
86 return nullptr;
89 performance = window->GetPerformance();
90 return performance.forget();
93 const WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
94 if (!workerPrivate) {
95 return nullptr;
98 WorkerGlobalScope* scope = workerPrivate->GlobalScope();
99 MOZ_ASSERT(scope);
100 performance = scope->GetPerformance();
102 return performance.forget();
105 Performance::Performance(nsIGlobalObject* aGlobal)
106 : DOMEventTargetHelper(aGlobal),
107 mResourceTimingBufferSize(kDefaultResourceTimingBufferSize),
108 mPendingNotificationObserversTask(false),
109 mPendingResourceTimingBufferFullEvent(false),
110 mRTPCallerType(aGlobal->GetRTPCallerType()),
111 mCrossOriginIsolated(aGlobal->CrossOriginIsolated()),
112 mShouldResistFingerprinting(aGlobal->ShouldResistFingerprinting(
113 RFPTarget::ReduceTimerPrecision)) {}
115 Performance::~Performance() = default;
117 DOMHighResTimeStamp Performance::TimeStampToDOMHighResForRendering(
118 TimeStamp aTimeStamp) const {
119 DOMHighResTimeStamp stamp = GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp);
120 // 0 is an inappropriate mixin for this this area; however CSS Animations
121 // needs to have it's Time Reduction Logic refactored, so it's currently
122 // only clamping for RFP mode. RFP mode gives a much lower time precision,
123 // so we accept the security leak here for now.
124 return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp, 0,
125 mRTPCallerType);
128 DOMHighResTimeStamp Performance::Now() {
129 DOMHighResTimeStamp rawTime = NowUnclamped();
131 // XXX: Removing this caused functions in pkcs11f.h to fail.
132 // Bug 1628021 investigates the root cause - it involves initializing
133 // the RNG service (part of GetRandomTimelineSeed()) off-main-thread
134 // but the underlying cause hasn't been identified yet.
135 if (mRTPCallerType == RTPCallerType::SystemPrincipal) {
136 return rawTime;
139 return nsRFPService::ReduceTimePrecisionAsMSecs(
140 rawTime, GetRandomTimelineSeed(), mRTPCallerType);
143 DOMHighResTimeStamp Performance::NowUnclamped() const {
144 TimeDuration duration = TimeStamp::Now() - CreationTimeStamp();
145 return duration.ToMilliseconds();
148 DOMHighResTimeStamp Performance::TimeOrigin() {
149 if (!mPerformanceService) {
150 mPerformanceService = PerformanceService::GetOrCreate();
153 MOZ_ASSERT(mPerformanceService);
154 DOMHighResTimeStamp rawTimeOrigin =
155 mPerformanceService->TimeOrigin(CreationTimeStamp());
156 // Time Origin is an absolute timestamp, so we supply a 0 context mix-in
157 return nsRFPService::ReduceTimePrecisionAsMSecs(rawTimeOrigin, 0,
158 mRTPCallerType);
161 JSObject* Performance::WrapObject(JSContext* aCx,
162 JS::Handle<JSObject*> aGivenProto) {
163 return Performance_Binding::Wrap(aCx, this, aGivenProto);
166 void Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
167 aRetval = mResourceEntries.Clone();
168 aRetval.AppendElements(mUserEntries);
169 aRetval.Sort(PerformanceEntryComparator());
172 void Performance::GetEntriesByType(
173 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
174 if (aEntryType.EqualsLiteral("resource")) {
175 aRetval = mResourceEntries.Clone();
176 return;
179 aRetval.Clear();
181 if (aEntryType.EqualsLiteral("mark") || aEntryType.EqualsLiteral("measure")) {
182 RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
183 for (PerformanceEntry* entry : mUserEntries) {
184 if (entry->GetEntryType() == entryType) {
185 aRetval.AppendElement(entry);
191 void Performance::GetEntriesByName(
192 const nsAString& aName, const Optional<nsAString>& aEntryType,
193 nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
194 aRetval.Clear();
196 RefPtr<nsAtom> name = NS_Atomize(aName);
197 RefPtr<nsAtom> entryType =
198 aEntryType.WasPassed() ? NS_Atomize(aEntryType.Value()) : nullptr;
200 if (entryType) {
201 if (entryType == nsGkAtoms::mark || entryType == nsGkAtoms::measure) {
202 for (PerformanceEntry* entry : mUserEntries) {
203 if (entry->GetName() == name && entry->GetEntryType() == entryType) {
204 aRetval.AppendElement(entry);
207 return;
209 if (entryType == nsGkAtoms::resource) {
210 for (PerformanceEntry* entry : mResourceEntries) {
211 MOZ_ASSERT(entry->GetEntryType() == entryType);
212 if (entry->GetName() == name) {
213 aRetval.AppendElement(entry);
216 return;
218 // Invalid entryType
219 return;
222 nsTArray<PerformanceEntry*> qualifiedResourceEntries;
223 nsTArray<PerformanceEntry*> qualifiedUserEntries;
224 // ::Measure expects that results from this function are already
225 // passed through ReduceTimePrecision. mResourceEntries and mUserEntries
226 // are, so the invariant holds.
227 for (PerformanceEntry* entry : mResourceEntries) {
228 if (entry->GetName() == name) {
229 qualifiedResourceEntries.AppendElement(entry);
233 for (PerformanceEntry* entry : mUserEntries) {
234 if (entry->GetName() == name) {
235 qualifiedUserEntries.AppendElement(entry);
239 size_t resourceEntriesIdx = 0, userEntriesIdx = 0;
240 aRetval.SetCapacity(qualifiedResourceEntries.Length() +
241 qualifiedUserEntries.Length());
243 PerformanceEntryComparator comparator;
245 while (resourceEntriesIdx < qualifiedResourceEntries.Length() &&
246 userEntriesIdx < qualifiedUserEntries.Length()) {
247 if (comparator.LessThan(qualifiedResourceEntries[resourceEntriesIdx],
248 qualifiedUserEntries[userEntriesIdx])) {
249 aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
250 ++resourceEntriesIdx;
251 } else {
252 aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
253 ++userEntriesIdx;
257 while (resourceEntriesIdx < qualifiedResourceEntries.Length()) {
258 aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
259 ++resourceEntriesIdx;
262 while (userEntriesIdx < qualifiedUserEntries.Length()) {
263 aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
264 ++userEntriesIdx;
268 void Performance::GetEntriesByTypeForObserver(
269 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
270 GetEntriesByType(aEntryType, aRetval);
273 void Performance::ClearUserEntries(const Optional<nsAString>& aEntryName,
274 const nsAString& aEntryType) {
275 MOZ_ASSERT(!aEntryType.IsEmpty());
276 RefPtr<nsAtom> name =
277 aEntryName.WasPassed() ? NS_Atomize(aEntryName.Value()) : nullptr;
278 RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
279 mUserEntries.RemoveElementsBy([name, entryType](const auto& entry) {
280 return (!name || entry->GetName() == name) &&
281 (entry->GetEntryType() == entryType);
285 void Performance::ClearResourceTimings() { mResourceEntries.Clear(); }
287 struct UserTimingMarker : public BaseMarkerType<UserTimingMarker> {
288 static constexpr const char* Name = "UserTiming";
289 static constexpr const char* Description =
290 "UserTimingMeasure is created using the DOM API performance.measure().";
292 using MS = MarkerSchema;
293 static constexpr MS::PayloadField PayloadFields[] = {
294 {"name", MS::InputType::String, "User Marker Name", MS::Format::String,
295 MS::PayloadFlags::Searchable},
296 {"entryType", MS::InputType::Boolean, "Entry Type"},
297 {"startMark", MS::InputType::String, "Start Mark"},
298 {"endMark", MS::InputType::String, "End Mark"}};
300 static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
301 MS::Location::MarkerTable};
302 static constexpr const char* AllLabels = "{marker.data.name}";
304 static constexpr MS::ETWMarkerGroup Group = MS::ETWMarkerGroup::UserMarkers;
306 static void StreamJSONMarkerData(
307 baseprofiler::SpliceableJSONWriter& aWriter,
308 const ProfilerString16View& aName, bool aIsMeasure,
309 const Maybe<ProfilerString16View>& aStartMark,
310 const Maybe<ProfilerString16View>& aEndMark) {
311 StreamJSONMarkerDataImpl(
312 aWriter, aName,
313 aIsMeasure ? MakeStringSpan("measure") : MakeStringSpan("mark"),
314 aStartMark, aEndMark);
318 already_AddRefed<PerformanceMark> Performance::Mark(
319 JSContext* aCx, const nsAString& aName,
320 const PerformanceMarkOptions& aMarkOptions, ErrorResult& aRv) {
321 nsCOMPtr<nsIGlobalObject> parent = GetParentObject();
322 if (!parent || parent->IsDying() || !parent->HasJSGlobal()) {
323 aRv.ThrowInvalidStateError("Global object is unavailable");
324 return nullptr;
327 GlobalObject global(aCx, parent->GetGlobalJSObject());
328 if (global.Failed()) {
329 aRv.ThrowInvalidStateError("Global object is unavailable");
330 return nullptr;
333 RefPtr<PerformanceMark> performanceMark =
334 PerformanceMark::Constructor(global, aName, aMarkOptions, aRv);
335 if (aRv.Failed()) {
336 return nullptr;
339 InsertUserEntry(performanceMark);
341 if (profiler_is_collecting_markers()) {
342 Maybe<uint64_t> innerWindowId;
343 if (GetOwner()) {
344 innerWindowId = Some(GetOwner()->WindowID());
346 TimeStamp startTimeStamp =
347 CreationTimeStamp() +
348 TimeDuration::FromMilliseconds(performanceMark->UnclampedStartTime());
349 profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
350 MarkerOptions(MarkerTiming::InstantAt(startTimeStamp),
351 MarkerInnerWindowId(innerWindowId)),
352 UserTimingMarker{}, aName, /* aIsMeasure */ false,
353 Nothing{}, Nothing{});
356 return performanceMark.forget();
359 void Performance::ClearMarks(const Optional<nsAString>& aName) {
360 ClearUserEntries(aName, u"mark"_ns);
363 // To be removed once bug 1124165 lands
364 bool Performance::IsPerformanceTimingAttribute(const nsAString& aName) const {
365 // Note that toJSON is added to this list due to bug 1047848
366 static const char* attributes[] = {"navigationStart",
367 "unloadEventStart",
368 "unloadEventEnd",
369 "redirectStart",
370 "redirectEnd",
371 "fetchStart",
372 "domainLookupStart",
373 "domainLookupEnd",
374 "connectStart",
375 "secureConnectionStart",
376 "connectEnd",
377 "requestStart",
378 "responseStart",
379 "responseEnd",
380 "domLoading",
381 "domInteractive",
382 "domContentLoadedEventStart",
383 "domContentLoadedEventEnd",
384 "domComplete",
385 "loadEventStart",
386 "loadEventEnd",
387 nullptr};
389 for (uint32_t i = 0; attributes[i]; ++i) {
390 if (aName.EqualsASCII(attributes[i])) {
391 return true;
395 return false;
398 DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithString(
399 const nsAString& aName, ErrorResult& aRv, bool aReturnUnclamped) {
400 if (IsPerformanceTimingAttribute(aName)) {
401 return ConvertNameToTimestamp(aName, aRv);
404 RefPtr<nsAtom> name = NS_Atomize(aName);
405 // Just loop over the user entries
406 for (const PerformanceEntry* entry : Reversed(mUserEntries)) {
407 if (entry->GetName() == name && entry->GetEntryType() == nsGkAtoms::mark) {
408 if (aReturnUnclamped) {
409 return entry->UnclampedStartTime();
411 return entry->StartTime();
415 nsPrintfCString errorMsg("Given mark name, %s, is unknown",
416 NS_ConvertUTF16toUTF8(aName).get());
417 aRv.ThrowSyntaxError(errorMsg);
418 return 0;
421 DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
422 const ResolveTimestampAttribute aAttribute,
423 const DOMHighResTimeStamp aTimestamp, ErrorResult& aRv) {
424 if (aTimestamp < 0) {
425 nsAutoCString attributeName;
426 switch (aAttribute) {
427 case ResolveTimestampAttribute::Start:
428 attributeName = "start";
429 break;
430 case ResolveTimestampAttribute::End:
431 attributeName = "end";
432 break;
433 case ResolveTimestampAttribute::Duration:
434 attributeName = "duration";
435 break;
438 nsPrintfCString errorMsg("Given attribute %s cannot be negative",
439 attributeName.get());
440 aRv.ThrowTypeError(errorMsg);
442 return aTimestamp;
445 DOMHighResTimeStamp Performance::ConvertMarkToTimestamp(
446 const ResolveTimestampAttribute aAttribute,
447 const OwningStringOrDouble& aMarkNameOrTimestamp, ErrorResult& aRv,
448 bool aReturnUnclamped) {
449 if (aMarkNameOrTimestamp.IsString()) {
450 return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp.GetAsString(),
451 aRv, aReturnUnclamped);
454 return ConvertMarkToTimestampWithDOMHighResTimeStamp(
455 aAttribute, aMarkNameOrTimestamp.GetAsDouble(), aRv);
458 DOMHighResTimeStamp Performance::ConvertNameToTimestamp(const nsAString& aName,
459 ErrorResult& aRv) {
460 if (!IsGlobalObjectWindow()) {
461 nsPrintfCString errorMsg(
462 "Cannot get PerformanceTiming attribute values for non-Window global "
463 "object. Given: %s",
464 NS_ConvertUTF16toUTF8(aName).get());
465 aRv.ThrowTypeError(errorMsg);
466 return 0;
469 if (aName.EqualsASCII("navigationStart")) {
470 return 0;
473 // We use GetPerformanceTimingFromString, rather than calling the
474 // navigationStart method timing function directly, because the former handles
475 // reducing precision against timing attacks.
476 const DOMHighResTimeStamp startTime =
477 GetPerformanceTimingFromString(u"navigationStart"_ns);
478 const DOMHighResTimeStamp endTime = GetPerformanceTimingFromString(aName);
479 MOZ_ASSERT(endTime >= 0);
480 if (endTime == 0) {
481 nsPrintfCString errorMsg(
482 "Given PerformanceTiming attribute, %s, isn't available yet",
483 NS_ConvertUTF16toUTF8(aName).get());
484 aRv.ThrowInvalidAccessError(errorMsg);
485 return 0;
488 return endTime - startTime;
491 DOMHighResTimeStamp Performance::ResolveEndTimeForMeasure(
492 const Optional<nsAString>& aEndMark,
493 const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv,
494 bool aReturnUnclamped) {
495 DOMHighResTimeStamp endTime;
496 if (aEndMark.WasPassed()) {
497 endTime = ConvertMarkToTimestampWithString(aEndMark.Value(), aRv,
498 aReturnUnclamped);
499 } else if (aOptions && aOptions->mEnd.WasPassed()) {
500 endTime =
501 ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
502 aOptions->mEnd.Value(), aRv, aReturnUnclamped);
503 } else if (aOptions && aOptions->mStart.WasPassed() &&
504 aOptions->mDuration.WasPassed()) {
505 const DOMHighResTimeStamp start =
506 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
507 aOptions->mStart.Value(), aRv, aReturnUnclamped);
508 if (aRv.Failed()) {
509 return 0;
512 const DOMHighResTimeStamp duration =
513 ConvertMarkToTimestampWithDOMHighResTimeStamp(
514 ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
515 aRv);
516 if (aRv.Failed()) {
517 return 0;
520 endTime = start + duration;
521 } else {
522 endTime = Now();
525 return endTime;
528 DOMHighResTimeStamp Performance::ResolveStartTimeForMeasure(
529 const Maybe<const nsAString&>& aStartMark,
530 const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv,
531 bool aReturnUnclamped) {
532 DOMHighResTimeStamp startTime;
533 if (aOptions && aOptions->mStart.WasPassed()) {
534 startTime =
535 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
536 aOptions->mStart.Value(), aRv, aReturnUnclamped);
537 } else if (aOptions && aOptions->mDuration.WasPassed() &&
538 aOptions->mEnd.WasPassed()) {
539 const DOMHighResTimeStamp duration =
540 ConvertMarkToTimestampWithDOMHighResTimeStamp(
541 ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
542 aRv);
543 if (aRv.Failed()) {
544 return 0;
547 const DOMHighResTimeStamp end =
548 ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
549 aOptions->mEnd.Value(), aRv, aReturnUnclamped);
550 if (aRv.Failed()) {
551 return 0;
554 startTime = end - duration;
555 } else if (aStartMark) {
556 startTime =
557 ConvertMarkToTimestampWithString(*aStartMark, aRv, aReturnUnclamped);
558 } else {
559 startTime = 0;
562 return startTime;
565 static std::string GetMarkerFilename() {
566 std::stringstream s;
567 if (char* markerDir = getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
568 s << markerDir << "/";
570 #ifdef XP_WIN
571 s << "marker-" << GetCurrentProcessId() << ".txt";
572 #else
573 s << "marker-" << getpid() << ".txt";
574 #endif
575 return s.str();
578 std::pair<TimeStamp, TimeStamp> Performance::GetTimeStampsForMarker(
579 const Maybe<const nsAString&>& aStartMark,
580 const Optional<nsAString>& aEndMark,
581 const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv) {
582 const DOMHighResTimeStamp unclampedStartTime = ResolveStartTimeForMeasure(
583 aStartMark, aOptions, aRv, /* aReturnUnclamped */ true);
584 const DOMHighResTimeStamp unclampedEndTime =
585 ResolveEndTimeForMeasure(aEndMark, aOptions, aRv, /* aReturnUnclamped */
586 true);
588 TimeStamp startTimeStamp =
589 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedStartTime);
590 TimeStamp endTimeStamp =
591 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedEndTime);
593 return std::make_pair(startTimeStamp, endTimeStamp);
596 // This emits markers to an external marker-[pid].txt file for use by an
597 // external profiler like samply or etw-gecko
598 void Performance::MaybeEmitExternalProfilerMarker(
599 const nsAString& aName, Maybe<const PerformanceMeasureOptions&> aOptions,
600 Maybe<const nsAString&> aStartMark, const Optional<nsAString>& aEndMark) {
601 static FILE* markerFile = getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")
602 ? fopen(GetMarkerFilename().c_str(), "w+")
603 : nullptr;
604 if (!markerFile) {
605 return;
608 ErrorResult rv;
609 auto [startTimeStamp, endTimeStamp] =
610 GetTimeStampsForMarker(aStartMark, aEndMark, aOptions, rv);
612 if (NS_WARN_IF(rv.Failed())) {
613 return;
616 #ifdef XP_LINUX
617 uint64_t rawStart = startTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
618 uint64_t rawEnd = endTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
619 #elif XP_WIN
620 uint64_t rawStart = startTimeStamp.RawQueryPerformanceCounterValue().value();
621 uint64_t rawEnd = endTimeStamp.RawQueryPerformanceCounterValue().value();
622 #elif XP_MACOSX
623 uint64_t rawStart = startTimeStamp.RawMachAbsoluteTimeNanoseconds();
624 uint64_t rawEnd = endTimeStamp.RawMachAbsoluteTimeNanoseconds();
625 #else
626 uint64_t rawStart = 0;
627 uint64_t rawEnd = 0;
628 MOZ_CRASH("no timestamp");
629 #endif
630 // Write a line for this measure to the marker file. The marker file uses a
631 // text-based format where every line is one marker, and each line has the
632 // format:
633 // `<raw_start_timestamp> <raw_end_timestamp> <measure_name>`
635 // The timestamp value is OS specific.
636 fprintf(markerFile, "%" PRIu64 " %" PRIu64 " %s\n", rawStart, rawEnd,
637 NS_ConvertUTF16toUTF8(aName).get());
638 fflush(markerFile);
641 already_AddRefed<PerformanceMeasure> Performance::Measure(
642 JSContext* aCx, const nsAString& aName,
643 const StringOrPerformanceMeasureOptions& aStartOrMeasureOptions,
644 const Optional<nsAString>& aEndMark, ErrorResult& aRv) {
645 if (!GetParentObject()) {
646 aRv.ThrowInvalidStateError("Global object is unavailable");
647 return nullptr;
650 // Maybe is more readable than using the union type directly.
651 Maybe<const PerformanceMeasureOptions&> options;
652 if (aStartOrMeasureOptions.IsPerformanceMeasureOptions()) {
653 options.emplace(aStartOrMeasureOptions.GetAsPerformanceMeasureOptions());
656 const bool isOptionsNotEmpty =
657 options.isSome() &&
658 (!options->mDetail.isUndefined() || options->mStart.WasPassed() ||
659 options->mEnd.WasPassed() || options->mDuration.WasPassed());
660 if (isOptionsNotEmpty) {
661 if (aEndMark.WasPassed()) {
662 aRv.ThrowTypeError(
663 "Cannot provide separate endMark argument if "
664 "PerformanceMeasureOptions argument is given");
665 return nullptr;
668 if (!options->mStart.WasPassed() && !options->mEnd.WasPassed()) {
669 aRv.ThrowTypeError(
670 "PerformanceMeasureOptions must have start and/or end member");
671 return nullptr;
674 if (options->mStart.WasPassed() && options->mDuration.WasPassed() &&
675 options->mEnd.WasPassed()) {
676 aRv.ThrowTypeError(
677 "PerformanceMeasureOptions cannot have all of the following members: "
678 "start, duration, and end");
679 return nullptr;
683 const DOMHighResTimeStamp endTime = ResolveEndTimeForMeasure(
684 aEndMark, options, aRv, /* aReturnUnclamped */ false);
685 if (NS_WARN_IF(aRv.Failed())) {
686 return nullptr;
689 // Convert to Maybe for consistency with options.
690 Maybe<const nsAString&> startMark;
691 if (aStartOrMeasureOptions.IsString()) {
692 startMark.emplace(aStartOrMeasureOptions.GetAsString());
694 const DOMHighResTimeStamp startTime = ResolveStartTimeForMeasure(
695 startMark, options, aRv, /* aReturnUnclamped */ false);
696 if (NS_WARN_IF(aRv.Failed())) {
697 return nullptr;
700 JS::Rooted<JS::Value> detail(aCx);
701 if (options && !options->mDetail.isNullOrUndefined()) {
702 StructuredSerializeOptions serializeOptions;
703 JS::Rooted<JS::Value> valueToClone(aCx, options->mDetail);
704 nsContentUtils::StructuredClone(aCx, GetParentObject(), valueToClone,
705 serializeOptions, &detail, aRv);
706 if (aRv.Failed()) {
707 return nullptr;
709 } else {
710 detail.setNull();
713 RefPtr<PerformanceMeasure> performanceMeasure = new PerformanceMeasure(
714 GetParentObject(), aName, startTime, endTime, detail);
715 InsertUserEntry(performanceMeasure);
717 MaybeEmitExternalProfilerMarker(aName, options, startMark, aEndMark);
719 if (profiler_is_collecting_markers()) {
720 auto [startTimeStamp, endTimeStamp] =
721 GetTimeStampsForMarker(startMark, aEndMark, options, aRv);
723 Maybe<nsString> endMark;
724 if (aEndMark.WasPassed()) {
725 endMark.emplace(aEndMark.Value());
728 Maybe<uint64_t> innerWindowId;
729 if (GetOwner()) {
730 innerWindowId = Some(GetOwner()->WindowID());
732 profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
733 {MarkerTiming::Interval(startTimeStamp, endTimeStamp),
734 MarkerInnerWindowId(innerWindowId)},
735 UserTimingMarker{}, aName, /* aIsMeasure */ true,
736 startMark, endMark);
739 return performanceMeasure.forget();
742 void Performance::ClearMeasures(const Optional<nsAString>& aName) {
743 ClearUserEntries(aName, u"measure"_ns);
746 void Performance::LogEntry(PerformanceEntry* aEntry,
747 const nsACString& aOwner) const {
748 PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n",
749 aOwner.BeginReading(),
750 NS_ConvertUTF16toUTF8(aEntry->GetEntryType()->GetUTF16String()).get(),
751 NS_ConvertUTF16toUTF8(aEntry->GetName()->GetUTF16String()).get(),
752 aEntry->StartTime(), aEntry->Duration(),
753 static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
756 void Performance::TimingNotification(PerformanceEntry* aEntry,
757 const nsACString& aOwner,
758 const double aEpoch) {
759 PerformanceEntryEventInit init;
760 init.mBubbles = false;
761 init.mCancelable = false;
762 aEntry->GetName(init.mName);
763 aEntry->GetEntryType(init.mEntryType);
764 init.mStartTime = aEntry->StartTime();
765 init.mDuration = aEntry->Duration();
766 init.mEpoch = aEpoch;
767 CopyUTF8toUTF16(aOwner, init.mOrigin);
769 RefPtr<PerformanceEntryEvent> perfEntryEvent =
770 PerformanceEntryEvent::Constructor(this, u"performanceentry"_ns, init);
772 nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner());
773 if (et) {
774 et->DispatchEvent(*perfEntryEvent);
778 void Performance::InsertUserEntry(PerformanceEntry* aEntry) {
779 mUserEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
781 QueueEntry(aEntry);
785 * Steps are labeled according to the description found at
786 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
788 * Buffer Full Event
790 void Performance::BufferEvent() {
792 * While resource timing secondary buffer is not empty,
793 * run the following substeps:
795 while (!mSecondaryResourceEntries.IsEmpty()) {
796 uint32_t secondaryResourceEntriesBeforeCount = 0;
797 uint32_t secondaryResourceEntriesAfterCount = 0;
800 * Let number of excess entries before be resource
801 * timing secondary buffer current size.
803 secondaryResourceEntriesBeforeCount = mSecondaryResourceEntries.Length();
806 * If can add resource timing entry returns false,
807 * then fire an event named resourcetimingbufferfull
808 * at the Performance object.
810 if (!CanAddResourceTimingEntry()) {
811 DispatchBufferFullEvent();
815 * Run copy secondary buffer.
817 * While resource timing secondary buffer is not
818 * empty and can add resource timing entry returns
819 * true ...
821 while (!mSecondaryResourceEntries.IsEmpty() &&
822 CanAddResourceTimingEntry()) {
824 * Let entry be the oldest PerformanceResourceTiming
825 * in resource timing secondary buffer. Add entry to
826 * the end of performance entry buffer. Increment
827 * resource timing buffer current size by 1.
829 mResourceEntries.InsertElementSorted(
830 mSecondaryResourceEntries.ElementAt(0), PerformanceEntryComparator());
832 * Remove entry from resource timing secondary buffer.
833 * Decrement resource timing secondary buffer current
834 * size by 1.
836 mSecondaryResourceEntries.RemoveElementAt(0);
840 * Let number of excess entries after be resource
841 * timing secondary buffer current size.
843 secondaryResourceEntriesAfterCount = mSecondaryResourceEntries.Length();
846 * If number of excess entries before is lower than
847 * or equals number of excess entries after, then
848 * remove all entries from resource timing secondary
849 * buffer, set resource timing secondary buffer current
850 * size to 0, and abort these steps.
852 if (secondaryResourceEntriesBeforeCount <=
853 secondaryResourceEntriesAfterCount) {
854 mSecondaryResourceEntries.Clear();
855 break;
859 * Set resource timing buffer full event pending flag
860 * to false.
862 mPendingResourceTimingBufferFullEvent = false;
865 void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize) {
866 mResourceTimingBufferSize = aMaxSize;
870 * Steps are labeled according to the description found at
871 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
873 * Can Add Resource Timing Entry
875 MOZ_ALWAYS_INLINE bool Performance::CanAddResourceTimingEntry() {
877 * If resource timing buffer current size is smaller than resource timing
878 * buffer size limit, return true. [Otherwise,] [r]eturn false.
880 return mResourceEntries.Length() < mResourceTimingBufferSize;
884 * Steps are labeled according to the description found at
885 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
887 * Add a PerformanceResourceTiming Entry
889 void Performance::InsertResourceEntry(PerformanceEntry* aEntry) {
890 MOZ_ASSERT(aEntry);
892 QueueEntry(aEntry);
895 * Let new entry be the input PerformanceEntry to be added.
897 * If can add resource timing entry returns true and resource
898 * timing buffer full event pending flag is false ...
900 if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent) {
902 * Add new entry to the performance entry buffer.
903 * Increase resource timing buffer current size by 1.
905 mResourceEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
906 return;
910 * If resource timing buffer full event pending flag is
911 * false ...
913 if (!mPendingResourceTimingBufferFullEvent) {
915 * Set resource timing buffer full event pending flag
916 * to true.
918 mPendingResourceTimingBufferFullEvent = true;
921 * Queue a task to run fire a buffer full event.
923 NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
924 "Performance::BufferEvent", this, &Performance::BufferEvent));
927 * Add new entry to the resource timing secondary buffer.
928 * Increase resource timing secondary buffer current size
929 * by 1.
931 mSecondaryResourceEntries.InsertElementSorted(aEntry,
932 PerformanceEntryComparator());
935 void Performance::AddObserver(PerformanceObserver* aObserver) {
936 mObservers.AppendElementUnlessExists(aObserver);
939 void Performance::RemoveObserver(PerformanceObserver* aObserver) {
940 mObservers.RemoveElement(aObserver);
943 void Performance::NotifyObservers() {
944 mPendingNotificationObserversTask = false;
945 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, Notify, ());
948 void Performance::CancelNotificationObservers() {
949 mPendingNotificationObserversTask = false;
952 class NotifyObserversTask final : public CancelableRunnable {
953 public:
954 explicit NotifyObserversTask(Performance* aPerformance)
955 : CancelableRunnable("dom::NotifyObserversTask"),
956 mPerformance(aPerformance) {
957 MOZ_ASSERT(mPerformance);
960 // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
961 // MOZ_CAN_RUN_SCRIPT.
962 MOZ_CAN_RUN_SCRIPT_BOUNDARY
963 NS_IMETHOD Run() override {
964 MOZ_ASSERT(mPerformance);
965 RefPtr<Performance> performance(mPerformance);
966 performance->NotifyObservers();
967 return NS_OK;
970 nsresult Cancel() override {
971 mPerformance->CancelNotificationObservers();
972 mPerformance = nullptr;
973 return NS_OK;
976 private:
977 ~NotifyObserversTask() = default;
979 RefPtr<Performance> mPerformance;
982 void Performance::QueueNotificationObserversTask() {
983 if (!mPendingNotificationObserversTask) {
984 RunNotificationObserversTask();
988 void Performance::RunNotificationObserversTask() {
989 mPendingNotificationObserversTask = true;
990 nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(this);
991 nsresult rv;
992 if (nsIGlobalObject* global = GetOwnerGlobal()) {
993 rv = global->Dispatch(task.forget());
994 } else {
995 rv = NS_DispatchToCurrentThread(task.forget());
997 if (NS_WARN_IF(NS_FAILED(rv))) {
998 mPendingNotificationObserversTask = false;
1002 void Performance::QueueEntry(PerformanceEntry* aEntry) {
1003 nsTObserverArray<PerformanceObserver*> interestedObservers;
1004 if (!mObservers.IsEmpty()) {
1005 const auto [begin, end] = mObservers.NonObservingRange();
1006 std::copy_if(begin, end, MakeBackInserter(interestedObservers),
1007 [aEntry](PerformanceObserver* observer) {
1008 return observer->ObservesTypeOfEntry(aEntry);
1012 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers, QueueEntry,
1013 (aEntry));
1015 aEntry->BufferEntryIfNeeded();
1017 if (!interestedObservers.IsEmpty()) {
1018 QueueNotificationObserversTask();
1022 // We could clear User entries here, but doing so could break sites that call
1023 // performance.measure() if the marks disappeared without warning. Chrome
1024 // allows "infinite" entries.
1025 void Performance::MemoryPressure() {}
1027 size_t Performance::SizeOfUserEntries(
1028 mozilla::MallocSizeOf aMallocSizeOf) const {
1029 size_t userEntries = 0;
1030 for (const PerformanceEntry* entry : mUserEntries) {
1031 userEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
1033 return userEntries;
1036 size_t Performance::SizeOfResourceEntries(
1037 mozilla::MallocSizeOf aMallocSizeOf) const {
1038 size_t resourceEntries = 0;
1039 for (const PerformanceEntry* entry : mResourceEntries) {
1040 resourceEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
1042 return resourceEntries;
1045 } // namespace mozilla::dom