Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / performance / Performance.cpp
blobdbc50a185df571fd3f4ea77408cacca6a4da4b3f
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 // 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;
68 /* static */
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();
80 /* static */
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();
90 /* static */
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);
96 if (!window) {
97 return nullptr;
100 performance = window->GetPerformance();
101 return performance.forget();
104 const WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
105 if (!workerPrivate) {
106 return nullptr;
109 WorkerGlobalScope* scope = workerPrivate->GlobalScope();
110 MOZ_ASSERT(scope);
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,
136 mRTPCallerType);
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) {
147 return rawTime;
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,
169 mRTPCallerType);
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();
187 return;
190 aRetval.Clear();
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) {
205 aRetval.Clear();
207 RefPtr<nsAtom> name = NS_Atomize(aName);
208 RefPtr<nsAtom> entryType =
209 aEntryType.WasPassed() ? NS_Atomize(aEntryType.Value()) : nullptr;
211 if (entryType) {
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);
218 return;
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);
227 return;
229 // Invalid entryType
230 return;
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;
262 } else {
263 aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
264 ++userEntriesIdx;
268 while (resourceEntriesIdx < qualifiedResourceEntries.Length()) {
269 aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
270 ++resourceEntriesIdx;
273 while (userEntriesIdx < qualifiedUserEntries.Length()) {
274 aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
275 ++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(
323 aWriter, aName,
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");
335 return nullptr;
338 GlobalObject global(aCx, parent->GetGlobalJSObject());
339 if (global.Failed()) {
340 aRv.ThrowInvalidStateError("Global object is unavailable");
341 return nullptr;
344 RefPtr<PerformanceMark> performanceMark =
345 PerformanceMark::Constructor(global, aName, aMarkOptions, aRv);
346 if (aRv.Failed()) {
347 return nullptr;
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",
378 "unloadEventStart",
379 "unloadEventEnd",
380 "redirectStart",
381 "redirectEnd",
382 "fetchStart",
383 "domainLookupStart",
384 "domainLookupEnd",
385 "connectStart",
386 "secureConnectionStart",
387 "connectEnd",
388 "requestStart",
389 "responseStart",
390 "responseEnd",
391 "domLoading",
392 "domInteractive",
393 "domContentLoadedEventStart",
394 "domContentLoadedEventEnd",
395 "domComplete",
396 "loadEventStart",
397 "loadEventEnd",
398 nullptr};
400 for (uint32_t i = 0; attributes[i]; ++i) {
401 if (aName.EqualsASCII(attributes[i])) {
402 return true;
406 return false;
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);
429 return 0;
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";
440 break;
441 case ResolveTimestampAttribute::End:
442 attributeName = "end";
443 break;
444 case ResolveTimestampAttribute::Duration:
445 attributeName = "duration";
446 break;
449 nsPrintfCString errorMsg("Given attribute %s cannot be negative",
450 attributeName.get());
451 aRv.ThrowTypeError(errorMsg);
453 return aTimestamp;
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,
470 ErrorResult& aRv) {
471 if (!IsGlobalObjectWindow()) {
472 nsPrintfCString errorMsg(
473 "Cannot get PerformanceTiming attribute values for non-Window global "
474 "object. Given: %s",
475 NS_ConvertUTF16toUTF8(aName).get());
476 aRv.ThrowTypeError(errorMsg);
477 return 0;
480 if (aName.EqualsASCII("navigationStart")) {
481 return 0;
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);
491 if (endTime == 0) {
492 nsPrintfCString errorMsg(
493 "Given PerformanceTiming attribute, %s, isn't available yet",
494 NS_ConvertUTF16toUTF8(aName).get());
495 aRv.ThrowInvalidAccessError(errorMsg);
496 return 0;
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,
509 aReturnUnclamped);
510 } else if (aOptions && aOptions->mEnd.WasPassed()) {
511 endTime =
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);
519 if (aRv.Failed()) {
520 return 0;
523 const DOMHighResTimeStamp duration =
524 ConvertMarkToTimestampWithDOMHighResTimeStamp(
525 ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
526 aRv);
527 if (aRv.Failed()) {
528 return 0;
531 endTime = start + duration;
532 } else if (aReturnUnclamped) {
533 MOZ_DIAGNOSTIC_ASSERT(sMarkerFile ||
534 profiler_thread_is_being_profiled_for_markers());
535 endTime = NowUnclamped();
536 } else {
537 endTime = Now();
540 return endTime;
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()) {
549 startTime =
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(),
557 aRv);
558 if (aRv.Failed()) {
559 return 0;
562 const DOMHighResTimeStamp end =
563 ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
564 aOptions->mEnd.Value(), aRv, aReturnUnclamped);
565 if (aRv.Failed()) {
566 return 0;
569 startTime = end - duration;
570 } else if (aStartMark) {
571 startTime =
572 ConvertMarkToTimestampWithString(*aStartMark, aRv, aReturnUnclamped);
573 } else {
574 startTime = 0;
577 return startTime;
580 static std::string GetMarkerFilename() {
581 std::stringstream s;
582 if (char* markerDir = getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
583 s << markerDir << "/";
585 #ifdef XP_WIN
586 s << "marker-" << GetCurrentProcessId() << ".txt";
587 #else
588 s << "marker-" << getpid() << ".txt";
589 #endif
590 return s.str();
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 */
601 true);
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
614 // is NULL.
615 static bool MaybeOpenMarkerFile() {
616 if (!getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")) {
617 return false;
620 // Check if it's already open.
621 if (sMarkerFile) {
622 return true;
625 #ifdef XP_LINUX
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+");
630 if (!sMarkerFile) {
631 return false;
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;
644 # ifndef ANDROID
645 protection |= PROT_EXEC;
646 # endif
648 // Mmap just the first page - that's enough to ensure the path makes it into
649 // the recording.
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) {
653 fclose(sMarkerFile);
654 sMarkerFile = nullptr;
655 return false;
657 #else
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
663 // open+fdopen.
664 sMarkerFile = fopen(GetMarkerFilename().c_str(), "w+");
665 if (!sMarkerFile) {
666 return false;
668 #endif
669 return true;
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()) {
678 return;
681 #if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
682 ErrorResult rv;
683 auto [startTimeStamp, endTimeStamp] =
684 GetTimeStampsForMarker(aStartMark, aEndMark, aOptions, rv);
686 if (NS_WARN_IF(rv.Failed())) {
687 return;
689 #endif
691 #ifdef XP_LINUX
692 uint64_t rawStart = startTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
693 uint64_t rawEnd = endTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
694 #elif XP_WIN
695 uint64_t rawStart = startTimeStamp.RawQueryPerformanceCounterValue().value();
696 uint64_t rawEnd = endTimeStamp.RawQueryPerformanceCounterValue().value();
697 #elif XP_MACOSX
698 uint64_t rawStart = startTimeStamp.RawMachAbsoluteTimeNanoseconds();
699 uint64_t rawEnd = endTimeStamp.RawMachAbsoluteTimeNanoseconds();
700 #else
701 uint64_t rawStart = 0;
702 uint64_t rawEnd = 0;
703 MOZ_CRASH("no timestamp");
704 #endif
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
707 // format:
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());
713 fflush(sMarkerFile);
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");
722 return nullptr;
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 =
732 options.isSome() &&
733 (!options->mDetail.isUndefined() || options->mStart.WasPassed() ||
734 options->mEnd.WasPassed() || options->mDuration.WasPassed());
735 if (isOptionsNotEmpty) {
736 if (aEndMark.WasPassed()) {
737 aRv.ThrowTypeError(
738 "Cannot provide separate endMark argument if "
739 "PerformanceMeasureOptions argument is given");
740 return nullptr;
743 if (!options->mStart.WasPassed() && !options->mEnd.WasPassed()) {
744 aRv.ThrowTypeError(
745 "PerformanceMeasureOptions must have start and/or end member");
746 return nullptr;
749 if (options->mStart.WasPassed() && options->mDuration.WasPassed() &&
750 options->mEnd.WasPassed()) {
751 aRv.ThrowTypeError(
752 "PerformanceMeasureOptions cannot have all of the following members: "
753 "start, duration, and end");
754 return nullptr;
758 const DOMHighResTimeStamp endTime = ResolveEndTimeForMeasure(
759 aEndMark, options, aRv, /* aReturnUnclamped */ false);
760 if (NS_WARN_IF(aRv.Failed())) {
761 return nullptr;
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())) {
772 return nullptr;
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);
781 if (aRv.Failed()) {
782 return nullptr;
784 } else {
785 detail.setNull();
788 #ifdef MOZ_PERFETTO
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()},
802 startTimeStamp);
803 PERFETTO_TRACE_EVENT_END(category, endTimeStamp);
805 #endif
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,
830 startMark, endMark);
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());
873 QueueEntry(aEntry);
877 * Steps are labeled according to the description found at
878 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
880 * Buffer Full Event
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
911 * true ...
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
926 * size by 1.
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();
947 break;
951 * Set resource timing buffer full event pending flag
952 * to false.
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) {
982 MOZ_ASSERT(aEntry);
984 QueueEntry(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());
998 return;
1002 * If resource timing buffer full event pending flag is
1003 * false ...
1005 if (!mPendingResourceTimingBufferFullEvent) {
1007 * Set resource timing buffer full event pending flag
1008 * to true.
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
1021 * by 1.
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 {
1045 public:
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();
1059 return NS_OK;
1062 nsresult Cancel() override {
1063 mPerformance->CancelNotificationObservers();
1064 mPerformance = nullptr;
1065 return NS_OK;
1068 private:
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);
1083 nsresult rv;
1084 if (nsIGlobalObject* global = GetOwnerGlobal()) {
1085 rv = global->Dispatch(task.forget());
1086 } else {
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,
1105 (aEntry));
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);
1125 return userEntries;
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