/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et ft=cpp : */ /* 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/. */ #ifndef mozilla_PerformanceRecorder_h #define mozilla_PerformanceRecorder_h #include #include "mozilla/Attributes.h" #include "mozilla/BaseProfilerMarkersPrerequisites.h" #include "mozilla/Maybe.h" #include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" #include "mozilla/TypedEnumBits.h" #include "nsStringFwd.h" #include "nsTPriorityQueue.h" #include "mozilla/ProfilerMarkers.h" namespace mozilla { namespace gfx { enum class YUVColorSpace : uint8_t; enum class ColorDepth : uint8_t; enum class ColorRange : uint8_t; } // namespace gfx struct TrackingId { enum class Source : uint8_t { Unimplemented, AudioDestinationNode, Camera, Canvas, ChannelDecoder, HLSDecoder, MediaCapabilities, MediaElementDecoder, MediaElementStream, MSEDecoder, RTCRtpReceiver, Screen, Tab, Window, LAST }; enum class TrackAcrossProcesses : uint8_t { Yes, No, }; TrackingId(); TrackingId(Source aSource, uint32_t aUniqueInProcId, TrackAcrossProcesses aTrack = TrackAcrossProcesses::No); nsCString ToString() const; Source mSource; uint32_t mUniqueInProcId; Maybe mProcId; }; enum class MediaInfoFlag : uint16_t { None = (0 << 0), NonKeyFrame = (1 << 0), KeyFrame = (1 << 1), SoftwareDecoding = (1 << 2), HardwareDecoding = (1 << 3), VIDEO_AV1 = (1 << 4), VIDEO_H264 = (1 << 5), VIDEO_VP8 = (1 << 6), VIDEO_VP9 = (1 << 7), VIDEO_THEORA = (1 << 8), }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MediaInfoFlag) /** * This represents the different stages that a media data will go through * within the playback journey. * * |---| |---| |------| * Copy Demuxed Copy Demuxed Copy Decoded * Data Data Video * |------------- | |-----------------------------------| * Request Demux Request Decode * |-----------------------------------------------------------| * Request Data * * RequestData : Record the time where MediaDecoderStateMachine(MDSM) starts * asking for a decoded data to MDSM receives a decoded data. * * RequestDemux : Record the time where MediaFormatReader(MFR) starts asking * a demuxed sample to MFR received a demuxed sample. This stage is a sub- * stage of RequestData. * * CopyDemuxedData : On some situations, we will need to copy the demuxed * data, which is still not decoded yet so its size is still small. This * records the time which we spend on copying data. This stage could happen * multiple times, either being a sub-stage of RequestDemux (in MSE case), * or being a sub-stage of RequestDecode (when sending data via IPC). * * RequestDecode : Record the time where MFR starts asking decoder to return * a decoded data to MFR receives a decoded data. As the decoder might be * remote, this stage might include the time spending on IPC trips. This * stage is a sub-stage of RequestData. * * CopyDecodedVideo : If we can't reuse same decoder texture to the * compositor, then we have to copy video data to to another sharable * texture. This records the time which we spend on copying data. This stage * is a sub- stage of RequestDecode. */ enum class MediaStage : uint8_t { Invalid, RequestData, RequestDemux, CopyDemuxedData, RequestDecode, CopyDecodedVideo, }; class PlaybackStage { public: explicit PlaybackStage(MediaStage aStage, int32_t aHeight = 0, MediaInfoFlag aFlag = MediaInfoFlag::None) : mStage(aStage), mHeight(aHeight), mFlag(aFlag) { MOZ_ASSERT(aStage != MediaStage::Invalid); } ProfilerString8View Name() const; const MarkerCategory& Category() const { return baseprofiler::category::MEDIA_PLAYBACK; } MediaStage mStage; int32_t mHeight; MediaInfoFlag mFlag; private: mutable Maybe mName; }; class CaptureStage { public: enum class ImageType : uint8_t { Unknown, I420, YUY2, YV12, UYVY, NV12, NV21, MJPEG, }; CaptureStage(nsCString aSource, TrackingId aTrackingId, int32_t aWidth, int32_t aHeight, ImageType aImageType) : mSource(std::move(aSource)), mTrackingId(std::move(aTrackingId)), mWidth(aWidth), mHeight(aHeight), mImageType(aImageType) {} ProfilerString8View Name() const; const MarkerCategory& Category() const { return baseprofiler::category::MEDIA_RT; } nsCString mSource; TrackingId mTrackingId; int32_t mWidth; int32_t mHeight; ImageType mImageType; private: mutable Maybe mName; }; class CopyVideoStage { public: CopyVideoStage(nsCString aSource, TrackingId aTrackingId, int32_t aWidth, int32_t aHeight) : mSource(std::move(aSource)), mTrackingId(std::move(aTrackingId)), mWidth(aWidth), mHeight(aHeight) {} ProfilerString8View Name() const; const MarkerCategory& Category() const { return baseprofiler::category::MEDIA_RT; } // The name of the source that performs this stage. nsCString mSource; // A unique id identifying the source of the video frame this stage is // performed for. TrackingId mTrackingId; int32_t mWidth; int32_t mHeight; private: mutable Maybe mName; }; class DecodeStage { public: enum ImageFormat : uint8_t { YUV420P, YUV422P, YUV444P, NV12, YV12, NV21, P010, P016, RGBA32, RGB24, GBRP, ANDROID_SURFACE, }; DecodeStage(nsCString aSource, TrackingId aTrackingId, MediaInfoFlag aFlag) : mSource(std::move(aSource)), mTrackingId(std::move(aTrackingId)), mFlag(aFlag) {} ProfilerString8View Name() const; const MarkerCategory& Category() const { return baseprofiler::category::MEDIA_PLAYBACK; } void SetResolution(int aWidth, int aHeight) { mWidth = Some(aWidth); mHeight = Some(aHeight); } void SetImageFormat(ImageFormat aFormat) { mImageFormat = Some(aFormat); } void SetYUVColorSpace(gfx::YUVColorSpace aColorSpace) { mYUVColorSpace = Some(aColorSpace); } void SetColorRange(gfx::ColorRange aColorRange) { mColorRange = Some(aColorRange); } void SetColorDepth(gfx::ColorDepth aColorDepth) { mColorDepth = Some(aColorDepth); } // The name of the source that performs this stage. nsCString mSource; // A unique id identifying the source of the video frame this stage is // performed for. TrackingId mTrackingId; MediaInfoFlag mFlag; Maybe mWidth; Maybe mHeight; Maybe mImageFormat; Maybe mYUVColorSpace; Maybe mColorRange; Maybe mColorDepth; mutable Maybe mName; }; class PerformanceRecorderBase { public: static bool IsMeasurementEnabled(); static TimeStamp GetCurrentTimeForMeasurement(); // Return the resolution range for the given height. Eg. V:1080 class PerformanceRecorderImpl : public PerformanceRecorderBase { public: ~PerformanceRecorderImpl() = default; PerformanceRecorderImpl(PerformanceRecorderImpl&& aRhs) noexcept : mStages(std::move(aRhs.mStages)) {} PerformanceRecorderImpl& operator=(PerformanceRecorderImpl&&) = delete; PerformanceRecorderImpl(const PerformanceRecorderImpl&) = delete; PerformanceRecorderImpl& operator=(const PerformanceRecorderImpl&) = delete; protected: PerformanceRecorderImpl() = default; // Stores the stage with the current time as its start time, associated with // aId. template void Start(int64_t aId, Args... aArgs) { if (IsMeasurementEnabled()) { MutexAutoLock lock(mMutex); mStages.Push(std::make_tuple(aId, GetCurrentTimeForMeasurement(), StageType(std::move(aArgs)...))); } } // Return the passed time since creation of the aId stage in microseconds if // it has not yet been recorded. Other stages with lower ids will be // discarded. Otherwise, return 0. template float Record(int64_t aId, F&& aStageMutator) { Maybe entry; { MutexAutoLock lock(mMutex); while (!mStages.IsEmpty() && std::get<0>(mStages.Top()) < aId) { mStages.Pop(); } if (mStages.IsEmpty()) { return 0.0; } if (std::get<0>(mStages.Top()) != aId) { return 0.0; } entry = Some(mStages.Pop()); } const auto& startTime = std::get<1>(*entry); auto& stage = std::get<2>(*entry); MOZ_ASSERT(std::get<0>(*entry) == aId); double elapsedTimeUs = 0.0; if (!startTime.IsNull() && IsMeasurementEnabled()) { const auto now = TimeStamp::Now(); elapsedTimeUs = (now - startTime).ToMicroseconds(); MOZ_ASSERT(elapsedTimeUs >= 0, "Elapsed time can't be less than 0!"); aStageMutator(stage); AUTO_PROFILER_STATS(PROFILER_MARKER_UNTYPED); ::profiler_add_marker( stage.Name(), stage.Category(), MarkerOptions(MarkerTiming::Interval(startTime, now))); } return static_cast(elapsedTimeUs); } float Record(int64_t aId) { return Record(aId, [](auto&) {}); } protected: using Entry = std::tuple; struct IdComparator { bool LessThan(const Entry& aTupleA, const Entry& aTupleB) { return std::get<0>(aTupleA) < std::get<0>(aTupleB); } }; Mutex mMutex{"PerformanceRecorder::mMutex"}; nsTPriorityQueue mStages MOZ_GUARDED_BY(mMutex); }; /** * This class is used to record the time spent on different stages in the media * pipeline. `Record()` needs to be called explicitly to record a profiler * marker registering the time passed since creation. A stage may be mutated in * `Record()` in case data has become available since the recorder started. * * This variant is intended to be created on the stack when a stage starts, then * recorded with `Record()` when the stage is finished. */ template class PerformanceRecorder : public PerformanceRecorderImpl { using Super = PerformanceRecorderImpl; public: template explicit PerformanceRecorder(Args... aArgs) { Start(std::move(aArgs)...); }; private: template void Start(Args... aArgs) { Super::Start(0, std::move(aArgs)...); } public: template float Record(F&& aStageMutator) { return Super::Record(0, std::forward(aStageMutator)); } float Record() { return Super::Record(0); } }; /** * This class is used to record the time spent on different stages in the media * pipeline. `Start()` and `Record()` needs to be called explicitly to record a * profiler marker registering the time passed since creation. A stage may be * mutated in `Record()` in case data has become available since the recorder * started. * * This variant is intended to be kept as a member in a class and supports async * stages. The async stages may overlap each other. To distinguish different * stages from each other, an int64_t is used as identifier. This is often a * timestamp in microseconds, see TimeUnit::ToMicroseconds. */ template class PerformanceRecorderMulti : public PerformanceRecorderImpl { using Super = PerformanceRecorderImpl; public: PerformanceRecorderMulti() = default; using Super::Record; using Super::Start; }; } // namespace mozilla #endif // mozilla_PerformanceRecorder_h