Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/html/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 16 kB image not shown  

Quelle  Performance.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "Performance.h"

#include <sstream>

#if defined(XP_LINUX)
#  include <fcntl.h>
#  include <sys/mman.h>
#endif

#include "ETWTools.h"
#include "GeckoProfiler.h"
#include "nsRFPService.h"
#include "PerformanceEntry.h"
#include "PerformanceMainThread.h"
#include "PerformanceMark.h"
#include "PerformanceMeasure.h"
#include "PerformanceObserver.h"
#include "PerformanceResourceTiming.h"
#include "PerformanceService.h"
#include "PerformanceWorker.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/PerformanceBinding.h"
#include "mozilla/dom/PerformanceEntryEvent.h"
#include "mozilla/dom/PerformanceNavigationBinding.h"
#include "mozilla/dom/PerformanceObserverBinding.h"
#include "mozilla/dom/PerformanceNavigationTiming.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Perfetto.h"
#include "mozilla/Preferences.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"

#define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)

namespace mozilla::dom {

enum class Performance::ResolveTimestampAttribute {
  Start,
  End,
  Duration,
};

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance, DOMEventTargetHelper,
                                   mUserEntries, mResourceEntries,
                                   mSecondaryResourceEntries, mObservers);

NS_IMPL_ADDREF_INHERITED(Performance, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper)

// Used to dump performance timing information to a local file.
// Only defined when the env variable MOZ_USE_PERFORMANCE_MARKER_FILE
// is set and initialized by MaybeOpenMarkerFile().
static FILE* sMarkerFile = nullptr;

/* static */
already_AddRefed<Performance> Performance::CreateForMainThread(
    nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
    nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel) {
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(aWindow->AsGlobal());
  RefPtr<Performance> performance =
      new PerformanceMainThread(aWindow, aDOMTiming, aChannel);
  return performance.forget();
}

/* static */
already_AddRefed<Performance> Performance::CreateForWorker(
    WorkerGlobalScope* aGlobalScope) {
  MOZ_ASSERT(aGlobalScope);
  //  aWorkerPrivate->AssertIsOnWorkerThread();

  RefPtr<Performance> performance = new PerformanceWorker(aGlobalScope);
  return performance.forget();
}

/* static */
already_AddRefed<Performance> Performance::Get(JSContext* aCx,
                                               nsIGlobalObject* aGlobal) {
  RefPtr<Performance> performance;
  if (NS_IsMainThread()) {
    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
    if (!window) {
      return nullptr;
    }

    performance = window->GetPerformance();
    return performance.forget();
  }

  const WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
  if (!workerPrivate) {
    return nullptr;
  }

  WorkerGlobalScope* scope = workerPrivate->GlobalScope();
  MOZ_ASSERT(scope);
  performance = scope->GetPerformance();

  return performance.forget();
}

Performance::Performance(nsIGlobalObject* aGlobal)
    : DOMEventTargetHelper(aGlobal),
      mResourceTimingBufferSize(kDefaultResourceTimingBufferSize),
      mPendingNotificationObserversTask(false),
      mPendingResourceTimingBufferFullEvent(false),
      mRTPCallerType(aGlobal->GetRTPCallerType()),
      mCrossOriginIsolated(aGlobal->CrossOriginIsolated()),
      mShouldResistFingerprinting(aGlobal->ShouldResistFingerprinting(
          RFPTarget::ReduceTimerPrecision)) {}

Performance::~Performance() = default;

DOMHighResTimeStamp Performance::TimeStampToDOMHighResForRendering(
    TimeStamp aTimeStamp) const {
  DOMHighResTimeStamp stamp = GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp);
  // 0 is an inappropriate mixin for this this area; however CSS Animations
  // needs to have it's Time Reduction Logic refactored, so it's currently
  // only clamping for RFP mode. RFP mode gives a much lower time precision,
  // so we accept the security leak here for now.
  return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp, 0,
                                                         mRTPCallerType);
}

DOMHighResTimeStamp Performance::Now() {
  DOMHighResTimeStamp rawTime = NowUnclamped();

  // XXX: Removing this caused functions in pkcs11f.h to fail.
  // Bug 1628021 investigates the root cause - it involves initializing
  // the RNG service (part of GetRandomTimelineSeed()) off-main-thread
  // but the underlying cause hasn't been identified yet.
  if (mRTPCallerType == RTPCallerType::SystemPrincipal) {
    return rawTime;
  }

  return nsRFPService::ReduceTimePrecisionAsMSecs(
      rawTime, GetRandomTimelineSeed(), mRTPCallerType);
}

DOMHighResTimeStamp Performance::NowUnclamped() const {
  TimeDuration duration = TimeStamp::Now() - CreationTimeStamp();
  return duration.ToMilliseconds();
}

DOMHighResTimeStamp Performance::TimeOrigin() {
  if (!mPerformanceService) {
    mPerformanceService = PerformanceService::GetOrCreate();
  }

  MOZ_ASSERT(mPerformanceService);
  DOMHighResTimeStamp rawTimeOrigin =
      mPerformanceService->TimeOrigin(CreationTimeStamp());
  // Time Origin is an absolute timestamp, so we supply a 0 context mix-in
  return nsRFPService::ReduceTimePrecisionAsMSecs(rawTimeOrigin, 0,
                                                  mRTPCallerType);
}

JSObject* Performance::WrapObject(JSContext* aCx,
                                  JS::Handle<JSObject*> aGivenProto) {
  return Performance_Binding::Wrap(aCx, this, aGivenProto);
}

void Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
  aRetval = mResourceEntries.Clone();
  aRetval.AppendElements(mUserEntries);
  aRetval.Sort(PerformanceEntryComparator());
}

void Performance::GetEntriesByType(
    const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
  if (aEntryType.EqualsLiteral("resource")) {
    aRetval = mResourceEntries.Clone();
    return;
  }

  aRetval.Clear();

  if (aEntryType.EqualsLiteral("mark") || aEntryType.EqualsLiteral("measure")) {
    RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
    for (PerformanceEntry* entry : mUserEntries) {
      if (entry->GetEntryType() == entryType) {
        aRetval.AppendElement(entry);
      }
    }
  }
}

void Performance::GetEntriesByName(
    const nsAString& aName, const Optional<nsAString>& aEntryType,
    nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
  aRetval.Clear();

  RefPtr<nsAtom> name = NS_Atomize(aName);
  RefPtr<nsAtom> entryType =
      aEntryType.WasPassed() ? NS_Atomize(aEntryType.Value()) : nullptr;

  if (entryType) {
    if (entryType == nsGkAtoms::mark || entryType == nsGkAtoms::measure) {
      for (PerformanceEntry* entry : mUserEntries) {
        if (entry->GetName() == name && entry->GetEntryType() == entryType) {
          aRetval.AppendElement(entry);
        }
      }
      return;
    }
    if (entryType == nsGkAtoms::resource) {
      for (PerformanceEntry* entry : mResourceEntries) {
        MOZ_ASSERT(entry->GetEntryType() == entryType);
        if (entry->GetName() == name) {
          aRetval.AppendElement(entry);
        }
      }
      return;
    }
    // Invalid entryType
    return;
  }

  nsTArray<PerformanceEntry*> qualifiedResourceEntries;
  nsTArray<PerformanceEntry*> qualifiedUserEntries;
  // ::Measure expects that results from this function are already
  // passed through ReduceTimePrecision. mResourceEntries and mUserEntries
  // are, so the invariant holds.
  for (PerformanceEntry* entry : mResourceEntries) {
    if (entry->GetName() == name) {
      qualifiedResourceEntries.AppendElement(entry);
    }
  }

  for (PerformanceEntry* entry : mUserEntries) {
    if (entry->GetName() == name) {
      qualifiedUserEntries.AppendElement(entry);
    }
  }

  size_t resourceEntriesIdx = 0, userEntriesIdx = 0;
  aRetval.SetCapacity(qualifiedResourceEntries.Length() +
                      qualifiedUserEntries.Length());

  PerformanceEntryComparator comparator;

  while (resourceEntriesIdx < qualifiedResourceEntries.Length() &&
         userEntriesIdx < qualifiedUserEntries.Length()) {
    if (comparator.LessThan(qualifiedResourceEntries[resourceEntriesIdx],
                            qualifiedUserEntries[userEntriesIdx])) {
      aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
      ++resourceEntriesIdx;
    } else {
      aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
      ++userEntriesIdx;
    }
  }

  while (resourceEntriesIdx < qualifiedResourceEntries.Length()) {
    aRetval.AppendElement(qualifiedResourceEntries[resourceEntriesIdx]);
    ++resourceEntriesIdx;
  }

  while (userEntriesIdx < qualifiedUserEntries.Length()) {
    aRetval.AppendElement(qualifiedUserEntries[userEntriesIdx]);
    ++userEntriesIdx;
  }
}

void Performance::GetEntriesByTypeForObserver(
    const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
  GetEntriesByType(aEntryType, aRetval);
}

void Performance::ClearUserEntries(const Optional<nsAString>& aEntryName,
                                   const nsAString& aEntryType) {
  MOZ_ASSERT(!aEntryType.IsEmpty());
  RefPtr<nsAtom> name =
      aEntryName.WasPassed() ? NS_Atomize(aEntryName.Value()) : nullptr;
  RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
  mUserEntries.RemoveElementsBy([name, entryType](const auto& entry) {
    return (!name || entry->GetName() == name) &&
           (entry->GetEntryType() == entryType);
  });
}

void Performance::ClearResourceTimings() { mResourceEntries.Clear(); }

struct UserTimingMarker : public BaseMarkerType<UserTimingMarker> {
  static constexpr const char* Name = "UserTiming";
  static constexpr const char* Description =
      "UserTimingMeasure is created using the DOM API performance.measure().";

  using MS = MarkerSchema;
  static constexpr MS::PayloadField PayloadFields[] = {
      {"name", MS::InputType::String, "User Marker Name", MS::Format::String,
       MS::PayloadFlags::Searchable},
      {"entryType", MS::InputType::Boolean, "Entry Type"},
      {"startMark", MS::InputType::String, "Start Mark"},
      {"endMark", MS::InputType::String, "End Mark"}};

  static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
                                               MS::Location::MarkerTable};
  static constexpr const char* AllLabels = "{marker.data.name}";

  static constexpr MS::ETWMarkerGroup Group = MS::ETWMarkerGroup::UserMarkers;

  static void StreamJSONMarkerData(
      baseprofiler::SpliceableJSONWriter& aWriter,
      const ProfilerString16View& aName, bool aIsMeasure,
      const Maybe<ProfilerString16View>& aStartMark,
      const Maybe<ProfilerString16View>& aEndMark) {
    StreamJSONMarkerDataImpl(
        aWriter, aName,
        aIsMeasure ? MakeStringSpan("measure") : MakeStringSpan("mark"),
        aStartMark, aEndMark);
  }
};

already_AddRefed<PerformanceMark> Performance::Mark(
    JSContext* aCx, const nsAString& aName,
    const PerformanceMarkOptions& aMarkOptions, ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> parent = GetParentObject();
  if (!parent || parent->IsDying() || !parent->HasJSGlobal()) {
    aRv.ThrowInvalidStateError("Global object is unavailable");
    return nullptr;
  }

  GlobalObject global(aCx, parent->GetGlobalJSObject());
  if (global.Failed()) {
    aRv.ThrowInvalidStateError("Global object is unavailable");
    return nullptr;
  }

  RefPtr<PerformanceMark> performanceMark =
      PerformanceMark::Constructor(global, aName, aMarkOptions, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  InsertUserEntry(performanceMark);

  if (profiler_thread_is_being_profiled_for_markers()) {
    Maybe<uint64_t> innerWindowId;
    if (nsGlobalWindowInner* owner = GetOwnerWindow()) {
      innerWindowId = Some(owner->WindowID());
    }
    TimeStamp startTimeStamp =
        CreationTimeStamp() +
        TimeDuration::FromMilliseconds(performanceMark->UnclampedStartTime());
    profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
                        MarkerOptions(MarkerTiming::InstantAt(startTimeStamp),
                                      MarkerInnerWindowId(innerWindowId)),
                        UserTimingMarker{}, aName, /* aIsMeasure */ false,
                        Nothing{}, Nothing{});
  }

  return performanceMark.forget();
}

void Performance::ClearMarks(const Optional<nsAString>& aName) {
  ClearUserEntries(aName, u"mark"_ns);
}

// To be removed once bug 1124165 lands
bool Performance::IsPerformanceTimingAttribute(const nsAString& aName) const {
  // Note that toJSON is added to this list due to bug 1047848
  static const char* attributes[] = {"navigationStart",
                                     "unloadEventStart",
                                     "unloadEventEnd",
                                     "redirectStart",
                                     "redirectEnd",
                                     "fetchStart",
                                     "domainLookupStart",
                                     "domainLookupEnd",
                                     "connectStart",
                                     "secureConnectionStart",
                                     "connectEnd",
                                     "requestStart",
                                     "responseStart",
                                     "responseEnd",
                                     "domLoading",
                                     "domInteractive",
                                     "domContentLoadedEventStart",
                                     "domContentLoadedEventEnd",
                                     "domComplete",
                                     "loadEventStart",
                                     "loadEventEnd",
                                     nullptr};

  for (uint32_t i = 0; attributes[i]; ++i) {
    if (aName.EqualsASCII(attributes[i])) {
      return true;
    }
  }

  return false;
}

DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithString(
    const nsAString& aName, ErrorResult& aRv, bool aReturnUnclamped) {
  if (IsPerformanceTimingAttribute(aName)) {
    return ConvertNameToTimestamp(aName, aRv);
  }

  RefPtr<nsAtom> name = NS_Atomize(aName);
  // Just loop over the user entries
  for (const PerformanceEntry* entry : Reversed(mUserEntries)) {
    if (entry->GetName() == name && entry->GetEntryType() == nsGkAtoms::mark) {
      if (aReturnUnclamped) {
        return entry->UnclampedStartTime();
      }
      return entry->StartTime();
    }
  }

  nsPrintfCString errorMsg("Given mark name, %s, is unknown",
                           NS_ConvertUTF16toUTF8(aName).get());
  aRv.ThrowSyntaxError(errorMsg);
  return 0;
}

DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
    const ResolveTimestampAttribute aAttribute,
    const DOMHighResTimeStamp aTimestamp, ErrorResult& aRv) {
  if (aTimestamp < 0) {
    nsAutoCString attributeName;
    switch (aAttribute) {
      case ResolveTimestampAttribute::Start:
        attributeName = "start";
        break;
      case ResolveTimestampAttribute::End:
        attributeName = "end";
        break;
      case ResolveTimestampAttribute::Duration:
        attributeName = "duration";
        break;
    }

    nsPrintfCString errorMsg("Given attribute %s cannot be negative",
                             attributeName.get());
    aRv.ThrowTypeError(errorMsg);
  }
  return aTimestamp;
}

DOMHighResTimeStamp Performance::ConvertMarkToTimestamp(
    const ResolveTimestampAttribute aAttribute,
    const OwningStringOrDouble& aMarkNameOrTimestamp, ErrorResult& aRv,
    bool aReturnUnclamped) {
  if (aMarkNameOrTimestamp.IsString()) {
    return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp.GetAsString(),
                                            aRv, aReturnUnclamped);
  }

  return ConvertMarkToTimestampWithDOMHighResTimeStamp(
      aAttribute, aMarkNameOrTimestamp.GetAsDouble(), aRv);
}

DOMHighResTimeStamp Performance::ConvertNameToTimestamp(const nsAString& aName,
                                                        ErrorResult& aRv) {
  if (!IsGlobalObjectWindow()) {
    nsPrintfCString errorMsg(
        "Cannot get PerformanceTiming attribute values for non-Window global "
        "object. Given: %s",
        NS_ConvertUTF16toUTF8(aName).get());
    aRv.ThrowTypeError(errorMsg);
    return 0;
  }

  if (aName.EqualsASCII("navigationStart")) {
    return 0;
  }

  // We use GetPerformanceTimingFromString, rather than calling the
  // navigationStart method timing function directly, because the former handles
  // reducing precision against timing attacks.
  const DOMHighResTimeStamp startTime =
      GetPerformanceTimingFromString(u"navigationStart"_ns);
  const DOMHighResTimeStamp endTime = GetPerformanceTimingFromString(aName);
  MOZ_ASSERT(endTime >= 0);
  if (endTime == 0) {
    nsPrintfCString errorMsg(
        "Given PerformanceTiming attribute, %s, isn't available yet",
        NS_ConvertUTF16toUTF8(aName).get());
    aRv.ThrowInvalidAccessError(errorMsg);
    return 0;
  }

  return endTime - startTime;
}

DOMHighResTimeStamp Performance::ResolveEndTimeForMeasure(
    const Optional<nsAString>& aEndMark,
    const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv,
    bool aReturnUnclamped) {
  DOMHighResTimeStamp endTime;
  if (aEndMark.WasPassed()) {
    endTime = ConvertMarkToTimestampWithString(aEndMark.Value(), aRv,
                                               aReturnUnclamped);
  } else if (aOptions && aOptions->mEnd.WasPassed()) {
    endTime =
        ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
                               aOptions->mEnd.Value(), aRv, aReturnUnclamped);
  } else if (aOptions && aOptions->mStart.WasPassed() &&
             aOptions->mDuration.WasPassed()) {
    const DOMHighResTimeStamp start =
        ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
                               aOptions->mStart.Value(), aRv, aReturnUnclamped);
    if (aRv.Failed()) {
      return 0;
    }

    const DOMHighResTimeStamp duration =
        ConvertMarkToTimestampWithDOMHighResTimeStamp(
            ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
            aRv);
    if (aRv.Failed()) {
      return 0;
    }

    endTime = start + duration;
  } else if (aReturnUnclamped) {
    MOZ_DIAGNOSTIC_ASSERT(sMarkerFile ||
                          profiler_thread_is_being_profiled_for_markers());
    endTime = NowUnclamped();
  } else {
    endTime = Now();
  }

  return endTime;
}

DOMHighResTimeStamp Performance::ResolveStartTimeForMeasure(
    const Maybe<const nsAString&>& aStartMark,
    const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv,
    bool aReturnUnclamped) {
  DOMHighResTimeStamp startTime;
  if (aOptions && aOptions->mStart.WasPassed()) {
    startTime =
        ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
                               aOptions->mStart.Value(), aRv, aReturnUnclamped);
  } else if (aOptions && aOptions->mDuration.WasPassed() &&
             aOptions->mEnd.WasPassed()) {
    const DOMHighResTimeStamp duration =
        ConvertMarkToTimestampWithDOMHighResTimeStamp(
            ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
            aRv);
    if (aRv.Failed()) {
      return 0;
    }

    const DOMHighResTimeStamp end =
        ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
                               aOptions->mEnd.Value(), aRv, aReturnUnclamped);
    if (aRv.Failed()) {
      return 0;
    }

    startTime = end - duration;
  } else if (aStartMark) {
    startTime =
        ConvertMarkToTimestampWithString(*aStartMark, aRv, aReturnUnclamped);
  } else {
    startTime = 0;
  }

  return startTime;
}

static std::string GetMarkerFilename() {
  std::stringstream s;
  if (char* markerDir = getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
    s << markerDir << "/";
  }
#ifdef XP_WIN
  s << "marker-" << GetCurrentProcessId() << ".txt";
#else
  s << "marker-" << getpid() << ".txt";
#endif
  return s.str();
}

std::pair<TimeStamp, TimeStamp> Performance::GetTimeStampsForMarker(
    const Maybe<const nsAString&>& aStartMark,
    const Optional<nsAString>& aEndMark,
    const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv) {
  const DOMHighResTimeStamp unclampedStartTime = ResolveStartTimeForMeasure(
      aStartMark, aOptions, aRv, /* aReturnUnclamped */ true);
  const DOMHighResTimeStamp unclampedEndTime =
      ResolveEndTimeForMeasure(aEndMark, aOptions, aRv, /* aReturnUnclamped */
                               true);

  TimeStamp startTimeStamp =
      CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedStartTime);
  TimeStamp endTimeStamp =
      CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedEndTime);

  return std::make_pair(startTimeStamp, endTimeStamp);
}

// Try to open the marker file for writing performance markers.
// If successful, returns true and sMarkerFile will be defined
// to the file handle.  Otherwise, return false and sMarkerFile
// is NULL.
static bool MaybeOpenMarkerFile() {
  if (!getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")) {
    return false;
  }

  // Check if it's already open.
  if (sMarkerFile) {
    return true;
  }

#ifdef XP_LINUX
  // We treat marker files similar to Jitdump files (see PerfSpewer.cpp) and
  // mmap them if needed.
  int fd = open(GetMarkerFilename().c_str(), O_CREAT | O_TRUNC | O_RDWR, 0666);
  sMarkerFile = fdopen(fd, "w+");
  if (!sMarkerFile) {
    return false;
  }

  // On Linux and Android, we need to mmap the file so that the path makes it
  // into the perf.data file or into samply.
  // On non-Android, make the mapping executable, otherwise the MMAP event may
  // not be recorded by perf (see perf_event_open mmap_data).
  // But on Android, don't make the mapping executable, because doing so can
  // make the mmap call fail on some Android devices. It's also not required on
  // Android because simpleperf sets mmap_data = 1 for unrelated reasons (it
  // wants to know about vdex files for Java JIT profiling, see
  // SetRecordNotExecutableMaps).
  int protection = PROT_READ;
#  ifndef ANDROID
  protection |= PROT_EXEC;
#  endif

  // Mmap just the first page - that's enough to ensure the path makes it into
  // the recording.
  long page_size = sysconf(_SC_PAGESIZE);
  void* mmap_address = mmap(nullptr, page_size, protection, MAP_PRIVATE, fd, 0);
  if (mmap_address == MAP_FAILED) {
    fclose(sMarkerFile);
    sMarkerFile = nullptr;
    return false;
  }
#else
  // On macOS, we just need to `open` or `fopen` the marker file, and samply
  // will know its path because it hooks those functions - no mmap needed.
  // On Windows, there's no need to use MOZ_USE_PERFORMANCE_MARKER_FILE because
  // we have ETW trace events for UserTiming measures. Still, we want this code
  // to compile successfully on Windows, so we use fopen rather than
  // open+fdopen.
  sMarkerFile = fopen(GetMarkerFilename().c_str(), "w+");
  if (!sMarkerFile) {
    return false;
  }
#endif
  return true;
}

// This emits markers to an external marker-[pid].txt file for use by an
// external profiler like samply or etw-gecko
void Performance::MaybeEmitExternalProfilerMarker(
    const nsAString& aName, Maybe<const PerformanceMeasureOptions&> aOptions,
    Maybe<const nsAString&> aStartMark, const Optional<nsAString>& aEndMark) {
  if (!MaybeOpenMarkerFile()) {
    return;
  }

#if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
  ErrorResult rv;
  auto [startTimeStamp, endTimeStamp] =
      GetTimeStampsForMarker(aStartMark, aEndMark, aOptions, rv);

  if (NS_WARN_IF(rv.Failed())) {
    return;
  }
#endif

#ifdef XP_LINUX
  uint64_t rawStart = startTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
  uint64_t rawEnd = endTimeStamp.RawClockMonotonicNanosecondsSinceBoot();
#elif XP_WIN
  uint64_t rawStart = startTimeStamp.RawQueryPerformanceCounterValue().value();
  uint64_t rawEnd = endTimeStamp.RawQueryPerformanceCounterValue().value();
#elif XP_MACOSX
  uint64_t rawStart = startTimeStamp.RawMachAbsoluteTimeNanoseconds();
  uint64_t rawEnd = endTimeStamp.RawMachAbsoluteTimeNanoseconds();
#else
  uint64_t rawStart = 0;
  uint64_t rawEnd = 0;
  MOZ_CRASH("no timestamp");
#endif
  // Write a line for this measure to the marker file. The marker file uses a
  // text-based format where every line is one marker, and each line has the
  // format:
  // `<raw_start_timestamp> <raw_end_timestamp> <measure_name>`
  //
  // The timestamp value is OS specific.
  fprintf(sMarkerFile, "%" PRIu64 " %" PRIu64 " %s\n", rawStart, rawEnd,
          NS_ConvertUTF16toUTF8(aName).get());
  fflush(sMarkerFile);
}

already_AddRefed<PerformanceMeasure> Performance::Measure(
    JSContext* aCx, const nsAString& aName,
    const StringOrPerformanceMeasureOptions& aStartOrMeasureOptions,
    const Optional<nsAString>& aEndMark, ErrorResult& aRv) {
  if (!GetParentObject()) {
    aRv.ThrowInvalidStateError("Global object is unavailable");
    return nullptr;
  }

  // Maybe is more readable than using the union type directly.
  Maybe<const PerformanceMeasureOptions&> options;
  if (aStartOrMeasureOptions.IsPerformanceMeasureOptions()) {
    options.emplace(aStartOrMeasureOptions.GetAsPerformanceMeasureOptions());
  }

  const bool isOptionsNotEmpty =
      options.isSome() &&
      (!options->mDetail.isUndefined() || options->mStart.WasPassed() ||
       options->mEnd.WasPassed() || options->mDuration.WasPassed());
  if (isOptionsNotEmpty) {
    if (aEndMark.WasPassed()) {
      aRv.ThrowTypeError(
          "Cannot provide separate endMark argument if "
          "PerformanceMeasureOptions argument is given");
      return nullptr;
    }

    if (!options->mStart.WasPassed() && !options->mEnd.WasPassed()) {
      aRv.ThrowTypeError(
          "PerformanceMeasureOptions must have start and/or end member");
      return nullptr;
    }

    if (options->mStart.WasPassed() && options->mDuration.WasPassed() &&
        options->mEnd.WasPassed()) {
      aRv.ThrowTypeError(
          "PerformanceMeasureOptions cannot have all of the following members: "
          "start, duration, and end");
      return nullptr;
    }
  }

  const DOMHighResTimeStamp endTime = ResolveEndTimeForMeasure(
      aEndMark, options, aRv, /* aReturnUnclamped */ false);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  // Convert to Maybe for consistency with options.
  Maybe<const nsAString&> startMark;
  if (aStartOrMeasureOptions.IsString()) {
    startMark.emplace(aStartOrMeasureOptions.GetAsString());
  }
  const DOMHighResTimeStamp startTime = ResolveStartTimeForMeasure(
      startMark, options, aRv, /* aReturnUnclamped */ false);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  JS::Rooted<JS::Value> detail(aCx);
  if (options && !options->mDetail.isNullOrUndefined()) {
    StructuredSerializeOptions serializeOptions;
    JS::Rooted<JS::Value> valueToClone(aCx, options->mDetail);
    nsContentUtils::StructuredClone(aCx, GetParentObject(), valueToClone,
                                    serializeOptions, &detail, aRv);
    if (aRv.Failed()) {
      return nullptr;
    }
  } else {
    detail.setNull();
  }

#ifdef MOZ_PERFETTO
  // Perfetto requires that events are properly nested within each category.
  // Since this is not a guarantee here, we need to define a dynamic category
  // for each measurement so it's not prematurely ended by another measurement
  // that overlaps.  We also use the usertiming category to guard these markers
  // so it's easy to toggle.
  if (TRACE_EVENT_CATEGORY_ENABLED("usertiming")) {
    NS_ConvertUTF16toUTF8 str(aName);
    perfetto::DynamicCategory category{str.get()};
    TimeStamp startTimeStamp =
        CreationTimeStamp() + TimeDuration::FromMilliseconds(startTime);
    TimeStamp endTimeStamp =
        CreationTimeStamp() + TimeDuration::FromMilliseconds(endTime);
    PERFETTO_TRACE_EVENT_BEGIN(category, perfetto::DynamicString{str.get()},
                               startTimeStamp);
    PERFETTO_TRACE_EVENT_END(category, endTimeStamp);
  }
#endif

  RefPtr<PerformanceMeasure> performanceMeasure = new PerformanceMeasure(
      GetParentObject(), aName, startTime, endTime, detail);
  InsertUserEntry(performanceMeasure);

  MaybeEmitExternalProfilerMarker(aName, options, startMark, aEndMark);

  if (profiler_thread_is_being_profiled_for_markers()) {
    auto [startTimeStamp, endTimeStamp] =
        GetTimeStampsForMarker(startMark, aEndMark, options, aRv);

    Maybe<nsString> endMark;
    if (aEndMark.WasPassed()) {
      endMark.emplace(aEndMark.Value());
    }

    Maybe<uint64_t> innerWindowId;
    if (nsGlobalWindowInner* owner = GetOwnerWindow()) {
      innerWindowId = Some(owner->WindowID());
    }
    profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
                        {MarkerTiming::Interval(startTimeStamp, endTimeStamp),
                         MarkerInnerWindowId(innerWindowId)},
                        UserTimingMarker{}, aName, /* aIsMeasure */ true,
                        startMark, endMark);
  }

  return performanceMeasure.forget();
}

void Performance::ClearMeasures(const Optional<nsAString>& aName) {
  ClearUserEntries(aName, u"measure"_ns);
}

void Performance::LogEntry(PerformanceEntry* aEntry,
                           const nsACString& aOwner) const {
  PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n",
          aOwner.BeginReading(),
          NS_ConvertUTF16toUTF8(aEntry->GetEntryType()->GetUTF16String()).get(),
          NS_ConvertUTF16toUTF8(aEntry->GetName()->GetUTF16String()).get(),
          aEntry->StartTime(), aEntry->Duration(),
          static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
}

void Performance::TimingNotification(PerformanceEntry* aEntry,
                                     const nsACString& aOwner,
                                     const double aEpoch) {
  PerformanceEntryEventInit init;
  init.mBubbles = false;
  init.mCancelable = false;
  aEntry->GetName(init.mName);
  aEntry->GetEntryType(init.mEntryType);
  init.mStartTime = aEntry->StartTime();
  init.mDuration = aEntry->Duration();
  init.mEpoch = aEpoch;
  CopyUTF8toUTF16(aOwner, init.mOrigin);

  RefPtr<PerformanceEntryEvent> perfEntryEvent =
      PerformanceEntryEvent::Constructor(this, u"performanceentry"_ns, init);
  if (RefPtr<nsGlobalWindowInner> owner = GetOwnerWindow()) {
    owner->DispatchEvent(*perfEntryEvent);
  }
}

void Performance::InsertUserEntry(PerformanceEntry* aEntry) {
  mUserEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());

  QueueEntry(aEntry);
}

/*
 * Steps are labeled according to the description found at
 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
 *
 * Buffer Full Event
 */

void Performance::BufferEvent() {
  /*
   * While resource timing secondary buffer is not empty,
   * run the following substeps:
   */

  while (!mSecondaryResourceEntries.IsEmpty()) {
    uint32_t secondaryResourceEntriesBeforeCount = 0;
    uint32_t secondaryResourceEntriesAfterCount = 0;

    /*
     * Let number of excess entries before be resource
     * timing secondary buffer current size.
     */

    secondaryResourceEntriesBeforeCount = mSecondaryResourceEntries.Length();

    /*
     * If can add resource timing entry returns false,
     * then fire an event named resourcetimingbufferfull
     * at the Performance object.
     */

    if (!CanAddResourceTimingEntry()) {
      DispatchBufferFullEvent();
    }

    /*
     * Run copy secondary buffer.
     *
     * While resource timing secondary buffer is not
     * empty and can add resource timing entry returns
     * true ...
     */

    while (!mSecondaryResourceEntries.IsEmpty() &&
           CanAddResourceTimingEntry()) {
      /*
       * Let entry be the oldest PerformanceResourceTiming
       * in resource timing secondary buffer. Add entry to
       * the end of performance entry buffer. Increment
       * resource timing buffer current size by 1.
       */

      mResourceEntries.InsertElementSorted(
          mSecondaryResourceEntries.ElementAt(0), PerformanceEntryComparator());
      /*
       * Remove entry from resource timing secondary buffer.
       * Decrement resource timing secondary buffer current
       * size by 1.
       */

      mSecondaryResourceEntries.RemoveElementAt(0);
    }

    /*
     * Let number of excess entries after be resource
     * timing secondary buffer current size.
     */

    secondaryResourceEntriesAfterCount = mSecondaryResourceEntries.Length();

    /*
     * If number of excess entries before is lower than
     * or equals number of excess entries after, then
     * remove all entries from resource timing secondary
     * buffer, set resource timing secondary buffer current
     * size to 0, and abort these steps.
     */

    if (secondaryResourceEntriesBeforeCount <=
        secondaryResourceEntriesAfterCount) {
      mSecondaryResourceEntries.Clear();
      break;
    }
  }
  /*
   * Set resource timing buffer full event pending flag
   * to false.
   */

  mPendingResourceTimingBufferFullEvent = false;
}

void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize) {
  mResourceTimingBufferSize = aMaxSize;
}

/*
 * Steps are labeled according to the description found at
 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
 *
 * Can Add Resource Timing Entry
 */

MOZ_ALWAYS_INLINE bool Performance::CanAddResourceTimingEntry() {
  /*
   * If resource timing buffer current size is smaller than resource timing
   * buffer size limit, return true. [Otherwise,] [r]eturn false.
   */

  return mResourceEntries.Length() < mResourceTimingBufferSize;
}

/*
 * Steps are labeled according to the description found at
 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
 *
 * Add a PerformanceResourceTiming Entry
 */

void Performance::InsertResourceEntry(PerformanceEntry* aEntry) {
  MOZ_ASSERT(aEntry);

  QueueEntry(aEntry);

  /*
   * Let new entry be the input PerformanceEntry to be added.
   *
   * If can add resource timing entry returns true and resource
   * timing buffer full event pending flag is false ...
   */

  if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent) {
    /*
     * Add new entry to the performance entry buffer.
     * Increase resource timing buffer current size by 1.
     */

    mResourceEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
    return;
  }

  /*
   * If resource timing buffer full event pending flag is
   * false ...
   */

  if (!mPendingResourceTimingBufferFullEvent) {
    /*
     * Set resource timing buffer full event pending flag
     * to true.
     */

    mPendingResourceTimingBufferFullEvent = true;

    /*
     * Queue a task to run fire a buffer full event.
     */

    NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
        "Performance::BufferEvent"this, &Performance::BufferEvent));
  }
  /*
   * Add new entry to the resource timing secondary buffer.
   * Increase resource timing secondary buffer current size
   * by 1.
   */

  mSecondaryResourceEntries.InsertElementSorted(aEntry,
                                                PerformanceEntryComparator());
}

void Performance::AddObserver(PerformanceObserver* aObserver) {
  mObservers.AppendElementUnlessExists(aObserver);
}

void Performance::RemoveObserver(PerformanceObserver* aObserver) {
  mObservers.RemoveElement(aObserver);
}

void Performance::NotifyObservers() {
  mPendingNotificationObserversTask = false;
  NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, Notify, ());
}

void Performance::CancelNotificationObservers() {
  mPendingNotificationObserversTask = false;
}

class NotifyObserversTask final : public CancelableRunnable {
 public:
  explicit NotifyObserversTask(Performance* aPerformance)
      : CancelableRunnable("dom::NotifyObserversTask"),
        mPerformance(aPerformance) {
    MOZ_ASSERT(mPerformance);
  }

  // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
  // MOZ_CAN_RUN_SCRIPT.
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  NS_IMETHOD Run() override {
    MOZ_ASSERT(mPerformance);
    RefPtr<Performance> performance(mPerformance);
    performance->NotifyObservers();
    return NS_OK;
  }

  nsresult Cancel() override {
    mPerformance->CancelNotificationObservers();
    mPerformance = nullptr;
    return NS_OK;
  }

 private:
  ~NotifyObserversTask() = default;

  RefPtr<Performance> mPerformance;
};

void Performance::QueueNotificationObserversTask() {
  if (!mPendingNotificationObserversTask) {
    RunNotificationObserversTask();
  }
}

void Performance::RunNotificationObserversTask() {
  mPendingNotificationObserversTask = true;
  nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(this);
  nsresult rv;
  if (nsIGlobalObject* global = GetOwnerGlobal()) {
    rv = global->Dispatch(task.forget());
  } else {
    rv = NS_DispatchToCurrentThread(task.forget());
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    mPendingNotificationObserversTask = false;
  }
}

void Performance::QueueEntry(PerformanceEntry* aEntry) {
  nsTObserverArray<PerformanceObserver*> interestedObservers;
  if (!mObservers.IsEmpty()) {
    const auto [begin, end] = mObservers.NonObservingRange();
    std::copy_if(begin, end, MakeBackInserter(interestedObservers),
                 [aEntry](PerformanceObserver* observer) {
                   return observer->ObservesTypeOfEntry(aEntry);
                 });
  }

  NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers, QueueEntry,
                                           (aEntry));

  aEntry->BufferEntryIfNeeded();

  if (!interestedObservers.IsEmpty()) {
    QueueNotificationObserversTask();
  }
}

// We could clear User entries here, but doing so could break sites that call
// performance.measure() if the marks disappeared without warning.   Chrome
// allows "infinite" entries.
void Performance::MemoryPressure() {}

size_t Performance::SizeOfUserEntries(
    mozilla::MallocSizeOf aMallocSizeOf) const {
  size_t userEntries = 0;
  for (const PerformanceEntry* entry : mUserEntries) {
    userEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
  }
  return userEntries;
}

size_t Performance::SizeOfResourceEntries(
    mozilla::MallocSizeOf aMallocSizeOf) const {
  size_t resourceEntries = 0;
  for (const PerformanceEntry* entry : mResourceEntries) {
    resourceEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
  }
  return resourceEntries;
}

}  // namespace mozilla::dom

92%


¤ Dauer der Verarbeitung: 0.6 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.