diff options
Diffstat (limited to '')
-rw-r--r-- | mozglue/baseprofiler/public/ProgressLogger.h | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/mozglue/baseprofiler/public/ProgressLogger.h b/mozglue/baseprofiler/public/ProgressLogger.h new file mode 100644 index 0000000000..e15095faaa --- /dev/null +++ b/mozglue/baseprofiler/public/ProgressLogger.h @@ -0,0 +1,500 @@ +/* -*- 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/. */ + +#ifndef ProgressLogger_h +#define ProgressLogger_h + +#include "mozilla/Assertions.h" +#include "mozilla/ProportionValue.h" +#include "mozilla/RefCounted.h" +#include "mozilla/RefPtr.h" + +#include <atomic> + +// Uncomment to printf ProcessLogger updates. +// #define DEBUG_PROCESSLOGGER + +#ifdef DEBUG_PROCESSLOGGER +# include "mozilla/BaseProfilerUtils.h" +# include <cstdio> +#endif // DEBUG_PROCESSLOGGER + +namespace mozilla { + +// A `ProgressLogger` is used to update a referenced atomic `ProportionValue`, +// and can recursively create a sub-logger corresponding to a subset of their +// own range, but that sub-logger's updates are done in its local 0%-100% range. +// The typical usage is for multi-level tasks, where each level can estimate its +// own work and the work delegated to a next-level function, without knowing how +// this local work relates to the higher-level total work. See +// `CreateSubLoggerFromTo` for details. +// Note that this implementation is single-threaded, it does not support logging +// progress from multiple threads at the same time. +class ProgressLogger { + public: + // An RefPtr'd object of this class is used as the target of all + // ProgressLogger updates, and it may be shared to make these updates visible + // from other code in any thread. + class SharedProgress : public external::AtomicRefCounted<SharedProgress> { + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(SharedProgress) + + SharedProgress() = default; + + SharedProgress(const SharedProgress&) = delete; + SharedProgress& operator=(const SharedProgress&) = delete; + + // This constant is used to indicate that an update may change the progress + // value, but should not modify the previously-recorded location. + static constexpr const char* NO_LOCATION_UPDATE = nullptr; + + // Set the current progress and location, but the previous location is not + // overwritten if the new one is null or empty. + // The location and then the progress are atomically "released", so that all + // preceding writes on this thread will be visible to other threads reading + // these values; most importantly when reaching 100% progress, the reader + // can be confident that the location is final and the operation being + // watched has completed. + void SetProgress( + ProportionValue aProgress, + const char* aLocationOrNullEmptyToIgnore = NO_LOCATION_UPDATE) { + if (aLocationOrNullEmptyToIgnore && + *aLocationOrNullEmptyToIgnore != '\0') { + mLastLocation.store(aLocationOrNullEmptyToIgnore, + std::memory_order_release); + } + mProgress.store(aProgress, std::memory_order_release); + } + + // Read the current progress value. Atomically "acquired", so that writes + // from the thread that stored this value are all visible to the reader + // here; most importantly when reaching 100%, we can be confident that the + // location is final and the operation being watched has completed. + [[nodiscard]] ProportionValue Progress() const { + return mProgress.load(std::memory_order_acquire); + } + + // Read the current progress value. Atomically "acquired". + [[nodiscard]] const char* LastLocation() const { + return mLastLocation.load(std::memory_order_acquire); + } + + private: + friend mozilla::detail::RefCounted<SharedProgress, + mozilla::detail::AtomicRefCount>; + ~SharedProgress() = default; + + // Progress and last-known location. + // Beware that these two values are not strongly tied: Reading one then the + // other may give mismatched information; but it should be fine for + // informational usage. + // They are stored using atomic acquire-release ordering, to guarantee that + // when read, all writes preceding these values are visible. + std::atomic<ProportionValue> mProgress = ProportionValue{0.0}; + std::atomic<const char*> mLastLocation = nullptr; + }; + + static constexpr const char* NO_LOCATION_UPDATE = + SharedProgress::NO_LOCATION_UPDATE; + + ProgressLogger() = default; + + // Construct a top-level logger, starting at 0% and expected to end at 100%. + explicit ProgressLogger( + RefPtr<SharedProgress> aGlobalProgressOrNull, + const char* aLocationOrNullEmptyToIgnoreAtStart = NO_LOCATION_UPDATE, + const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) + : ProgressLogger{std::move(aGlobalProgressOrNull), + /* Start */ ProportionValue{0.0}, + /* Multiplier */ ProportionValue{1.0}, + aLocationOrNullEmptyToIgnoreAtStart, + aLocationOrNullEmptyToIgnoreAtEnd} {} + + // Don't make copies, it would be confusing! + // TODO: Copies could one day be allowed to track multi-threaded work, but it + // is outside the scope of this implementation; Please update if needed. + ProgressLogger(const ProgressLogger&) = delete; + ProgressLogger& operator&(const ProgressLogger&) = delete; + + // Move-construct is allowed, to return from CreateSubLoggerFromTo, and + // forward straight into a function. Note that moved-from ProgressLoggers must + // not be used anymore! Use `CreateSubLoggerFromTo` to pass a sub-logger to + // functions. + ProgressLogger(ProgressLogger&& aOther) + : mGlobalProgressOrNull(std::move(aOther.mGlobalProgressOrNull)), + mLocalStartInGlobalSpace(aOther.mLocalStartInGlobalSpace), + mLocalToGlobalMultiplier(aOther.mLocalToGlobalMultiplier), + mLocationAtDestruction(aOther.mLocationAtDestruction) { + aOther.MarkMovedFrom(); +#ifdef DEBUG_PROCESSLOGGER + if (mGlobalProgressOrNull) { + printf("[%d] Moved (staying globally at %.2f in [%.2f, %.2f])\n", + int(baseprofiler::profiler_current_process_id().ToNumber()), + GetGlobalProgress().ToDouble() * 100.0, + mLocalStartInGlobalSpace.ToDouble() * 100.0, + (mLocalStartInGlobalSpace + mLocalToGlobalMultiplier).ToDouble() * + 100.0); + } +#endif // DEBUG_PROCESSLOGGER + } + + // Move-assign. This may be useful when starting with a default (empty) logger + // and later assigning it a progress value to start updating. + ProgressLogger& operator=(ProgressLogger&& aOther) { + mGlobalProgressOrNull = std::move(aOther.mGlobalProgressOrNull); + mLocalStartInGlobalSpace = aOther.mLocalStartInGlobalSpace; + mLocalToGlobalMultiplier = aOther.mLocalToGlobalMultiplier; + mLocationAtDestruction = aOther.mLocationAtDestruction; + aOther.MarkMovedFrom(); +#ifdef DEBUG_PROCESSLOGGER + if (mGlobalProgressOrNull) { + printf("[%d] Re-assigned (globally at %.2f in [%.2f, %.2f])\n", + int(baseprofiler::profiler_current_process_id().ToNumber()), + GetGlobalProgress().ToDouble() * 100.0, + mLocalStartInGlobalSpace.ToDouble() * 100.0, + (mLocalStartInGlobalSpace + mLocalToGlobalMultiplier).ToDouble() * + 100.0); + } +#endif // DEBUG_PROCESSLOGGER + return *this; + } + + // Destruction sets the local update value to 100% unless empty or moved-from. + ~ProgressLogger() { + if (!IsMovedFrom()) { +#ifdef DEBUG_PROCESSLOGGER + if (mGlobalProgressOrNull) { + printf("[%d] Destruction:\n", + int(baseprofiler::profiler_current_process_id().ToNumber())); + } +#endif // DEBUG_PROCESSLOGGER + SetLocalProgress(ProportionValue{1.0}, mLocationAtDestruction); + } + } + + // Retrieve the current progress in the global space. May be invalid. + [[nodiscard]] ProportionValue GetGlobalProgress() const { + return mGlobalProgressOrNull ? mGlobalProgressOrNull->Progress() + : ProportionValue::MakeInvalid(); + } + + // Retrieve the last known global location. May be null. + [[nodiscard]] const char* GetLastGlobalLocation() const { + return mGlobalProgressOrNull ? mGlobalProgressOrNull->LastLocation() + : nullptr; + } + + // Set the current progress in the local space. + void SetLocalProgress(ProportionValue aLocalProgress, + const char* aLocationOrNullEmptyToIgnore) { + MOZ_ASSERT(!IsMovedFrom()); + if (mGlobalProgressOrNull && !mLocalToGlobalMultiplier.IsExactlyZero()) { + mGlobalProgressOrNull->SetProgress(LocalToGlobal(aLocalProgress), + aLocationOrNullEmptyToIgnore); +#ifdef DEBUG_PROCESSLOGGER + printf("[%d] - local %.0f%% ~ global %.2f%% \"%s\"\n", + int(baseprofiler::profiler_current_process_id().ToNumber()), + aLocalProgress.ToDouble() * 100.0, + LocalToGlobal(aLocalProgress).ToDouble() * 100.0, + aLocationOrNullEmptyToIgnore ? aLocationOrNullEmptyToIgnore + : "<null>"); +#endif // DEBUG_PROCESSLOGGER + } + } + + // Create a sub-logger that will record progress in the given local range. + // E.g.: `f(pl.CreateSubLoggerFromTo(0.2, "f...", 0.4, "f done"));` expects + // that `f` will produce work in the local range 0.2 (when starting) to 0.4 + // (when returning); `f` itself will update this provided logger from 0.0 + // to 1.0 (local to that `f` function), which will effectively be converted to + // 0.2-0.4 (local to the calling function). + // This can cascade multiple levels, each deeper level affecting a smaller and + // smaller range in the global output. + [[nodiscard]] ProgressLogger CreateSubLoggerFromTo( + ProportionValue aSubStartInLocalSpace, + const char* aLocationOrNullEmptyToIgnoreAtStart, + ProportionValue aSubEndInLocalSpace, + const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { + MOZ_ASSERT(!IsMovedFrom()); + if (!mGlobalProgressOrNull) { + return ProgressLogger{}; + } + const ProportionValue subStartInGlobalSpace = + LocalToGlobal(aSubStartInLocalSpace); + const ProportionValue subEndInGlobalSpace = + LocalToGlobal(aSubEndInLocalSpace); + if (subStartInGlobalSpace.IsInvalid() || subEndInGlobalSpace.IsInvalid()) { + return ProgressLogger{mGlobalProgressOrNull, + /* Start */ ProportionValue::MakeInvalid(), + /* Multiplier */ ProportionValue{0.0}, + aLocationOrNullEmptyToIgnoreAtStart, + aLocationOrNullEmptyToIgnoreAtEnd}; + } +#ifdef DEBUG_PROCESSLOGGER + if (mGlobalProgressOrNull) { + printf("[%d] * Sub: local [%.0f%%, %.0f%%] ~ global [%.2f%%, %.2f%%]\n", + int(baseprofiler::profiler_current_process_id().ToNumber()), + aSubStartInLocalSpace.ToDouble() * 100.0, + aSubEndInLocalSpace.ToDouble() * 100.0, + subStartInGlobalSpace.ToDouble() * 100.0, + subEndInGlobalSpace.ToDouble() * 100.0); + } +#endif // DEBUG_PROCESSLOGGER + return ProgressLogger{ + mGlobalProgressOrNull, + /* Start */ subStartInGlobalSpace, + /* Multipler */ subEndInGlobalSpace - subStartInGlobalSpace, + aLocationOrNullEmptyToIgnoreAtStart, aLocationOrNullEmptyToIgnoreAtEnd}; + } + + // Helper with no start location. + [[nodiscard]] ProgressLogger CreateSubLoggerFromTo( + ProportionValue aSubStartInLocalSpace, + ProportionValue aSubEndInLocalSpace, + const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { + return CreateSubLoggerFromTo(aSubStartInLocalSpace, NO_LOCATION_UPDATE, + aSubEndInLocalSpace, + aLocationOrNullEmptyToIgnoreAtEnd); + } + + // Helper using the current progress as start. + [[nodiscard]] ProgressLogger CreateSubLoggerTo( + const char* aLocationOrNullEmptyToIgnoreAtStart, + ProportionValue aSubEndInLocalSpace, + const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { + MOZ_ASSERT(!IsMovedFrom()); + if (!mGlobalProgressOrNull) { + return ProgressLogger{}; + } + const ProportionValue subStartInGlobalSpace = GetGlobalProgress(); + const ProportionValue subEndInGlobalSpace = + LocalToGlobal(aSubEndInLocalSpace); + if (subStartInGlobalSpace.IsInvalid() || subEndInGlobalSpace.IsInvalid()) { + return ProgressLogger{mGlobalProgressOrNull, + /* Start */ ProportionValue::MakeInvalid(), + /* Multiplier */ ProportionValue{0.0}, + aLocationOrNullEmptyToIgnoreAtStart, + aLocationOrNullEmptyToIgnoreAtEnd}; + } +#ifdef DEBUG_PROCESSLOGGER + if (mGlobalProgressOrNull) { + printf("[%d] * Sub: local [(here), %.0f%%] ~ global [%.2f%%, %.2f%%]\n", + int(baseprofiler::profiler_current_process_id().ToNumber()), + aSubEndInLocalSpace.ToDouble() * 100.0, + subStartInGlobalSpace.ToDouble() * 100.0, + subEndInGlobalSpace.ToDouble() * 100.0); + } +#endif // DEBUG_PROCESSLOGGER + return ProgressLogger{ + mGlobalProgressOrNull, + /* Start */ subStartInGlobalSpace, + /* Multiplier */ subEndInGlobalSpace - subStartInGlobalSpace, + aLocationOrNullEmptyToIgnoreAtStart, aLocationOrNullEmptyToIgnoreAtEnd}; + } + + // Helper using the current progress as start, no start location. + [[nodiscard]] ProgressLogger CreateSubLoggerTo( + ProportionValue aSubEndInLocalSpace, + const char* aLocationOrNullEmptyToIgnoreAtEnd = NO_LOCATION_UPDATE) { + return CreateSubLoggerTo(NO_LOCATION_UPDATE, aSubEndInLocalSpace, + aLocationOrNullEmptyToIgnoreAtEnd); + } + + class IndexAndProgressLoggerRange; + + [[nodiscard]] inline IndexAndProgressLoggerRange CreateLoopSubLoggersFromTo( + ProportionValue aLoopStartInLocalSpace, + ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, + const char* aLocationOrNullEmptyToIgnoreAtEdges = + ProgressLogger::NO_LOCATION_UPDATE); + [[nodiscard]] inline IndexAndProgressLoggerRange CreateLoopSubLoggersTo( + ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, + const char* aLocationOrNullEmptyToIgnoreAtEdges = + ProgressLogger::NO_LOCATION_UPDATE); + + private: + // All constructions start at the local 0%. + ProgressLogger(RefPtr<SharedProgress> aGlobalProgressOrNull, + ProportionValue aLocalStartInGlobalSpace, + ProportionValue aLocalToGlobalMultiplier, + const char* aLocationOrNullEmptyToIgnoreAtConstruction, + const char* aLocationOrNullEmptyToIgnoreAtDestruction) + : mGlobalProgressOrNull(std::move(aGlobalProgressOrNull)), + mLocalStartInGlobalSpace(aLocalStartInGlobalSpace), + mLocalToGlobalMultiplier(aLocalToGlobalMultiplier), + mLocationAtDestruction(aLocationOrNullEmptyToIgnoreAtDestruction) { + MOZ_ASSERT(!IsMovedFrom(), "Don't construct a moved-from object!"); + SetLocalProgress(ProportionValue{0.0}, + aLocationOrNullEmptyToIgnoreAtConstruction); + } + + void MarkMovedFrom() { + mLocalToGlobalMultiplier = ProportionValue::MakeInvalid(); + } + [[nodiscard]] bool IsMovedFrom() const { + return mLocalToGlobalMultiplier.IsInvalid(); + } + + [[nodiscard]] ProportionValue LocalToGlobal( + ProportionValue aLocalProgress) const { + return aLocalProgress * mLocalToGlobalMultiplier + mLocalStartInGlobalSpace; + } + + // Global progress value to update from local changes. + RefPtr<SharedProgress> mGlobalProgressOrNull; + + // How much to multiply and add to a local [0, 100%] value, to get the + // corresponding value in the global space. + // If mLocalToGlobalMultiplier is invalid, this ProgressLogger is moved-from, + // functions should not be used, and destructor won't update progress. + ProportionValue mLocalStartInGlobalSpace; + ProportionValue mLocalToGlobalMultiplier; + + const char* mLocationAtDestruction = nullptr; +}; + +// Helper class for range-for loop, e.g., with `aProgressLogger`: +// for (auto [index, loopProgressLogger] : +// IndexAndProgressLoggerRange{aProgressLogger, 30_pc, 50_pc, 10, +// "looping..."}) { +// // This will loop 10 times. +// // `index` is the loop index, from 0 to 9. +// // The overall loop will start at 30% and end at 50% of aProgressLogger. +// // `loopProgressLogger` is the progress logger for each iteration, +// // covering 1/10th of the range, therefore: [30%,32%], then [32%,34%], +// // etc. until [48%,50%]. +// // Progress is automatically updated before/after each loop. +// } +// Note that this implementation is single-threaded, it does not support logging +// progress from parallel loops. +class ProgressLogger::IndexAndProgressLoggerRange { + public: + struct IndexAndProgressLogger { + uint32_t index; + ProgressLogger progressLogger; + }; + + class IndexAndProgressLoggerEndIterator { + public: + explicit IndexAndProgressLoggerEndIterator(uint32_t aIndex) + : mIndex(aIndex) {} + + [[nodiscard]] uint32_t Index() const { return mIndex; } + + private: + uint32_t mIndex; + }; + + class IndexAndProgressLoggerIterator { + public: + IndexAndProgressLoggerIterator( + RefPtr<ProgressLogger::SharedProgress> aGlobalProgressOrNull, + ProportionValue aLoopStartInGlobalSpace, + ProportionValue aLoopIncrementInGlobalSpace, + const char* aLocationOrNullEmptyToIgnoreAtEdges) + : mGlobalProgressOrNull(aGlobalProgressOrNull), + mLoopStartInGlobalSpace(aLoopStartInGlobalSpace), + mLoopIncrementInGlobalSpace(aLoopIncrementInGlobalSpace), + mIndex(0u), + mLocationOrNullEmptyToIgnoreAtEdges( + aLocationOrNullEmptyToIgnoreAtEdges) { + if (mGlobalProgressOrNull) { + mGlobalProgressOrNull->SetProgress(mLoopStartInGlobalSpace, + mLocationOrNullEmptyToIgnoreAtEdges); + } + } + + [[nodiscard]] IndexAndProgressLogger operator*() { + return IndexAndProgressLogger{ + mIndex, + mGlobalProgressOrNull + ? ProgressLogger{mGlobalProgressOrNull, mLoopStartInGlobalSpace, + mLoopIncrementInGlobalSpace, + ProgressLogger::NO_LOCATION_UPDATE, + ProgressLogger::NO_LOCATION_UPDATE} + : ProgressLogger{}}; + } + + [[nodiscard]] bool operator!=( + const IndexAndProgressLoggerEndIterator& aEnd) const { + return mIndex != aEnd.Index(); + } + + IndexAndProgressLoggerIterator& operator++() { + ++mIndex; + mLoopStartInGlobalSpace = + mLoopStartInGlobalSpace + mLoopIncrementInGlobalSpace; + if (mGlobalProgressOrNull) { + mGlobalProgressOrNull->SetProgress(mLoopStartInGlobalSpace, + mLocationOrNullEmptyToIgnoreAtEdges); + } + return *this; + } + + private: + RefPtr<ProgressLogger::SharedProgress> mGlobalProgressOrNull; + ProportionValue mLoopStartInGlobalSpace; + ProportionValue mLoopIncrementInGlobalSpace; + uint32_t mIndex; + const char* mLocationOrNullEmptyToIgnoreAtEdges; + }; + + [[nodiscard]] IndexAndProgressLoggerIterator begin() { + return IndexAndProgressLoggerIterator{ + mGlobalProgressOrNull, mLoopStartInGlobalSpace, + mLoopIncrementInGlobalSpace, mLocationOrNullEmptyToIgnoreAtEdges}; + } + + [[nodiscard]] IndexAndProgressLoggerEndIterator end() { + return IndexAndProgressLoggerEndIterator{mLoopCount}; + } + + private: + friend class ProgressLogger; + IndexAndProgressLoggerRange(ProgressLogger& aProgressLogger, + ProportionValue aLoopStartInGlobalSpace, + ProportionValue aLoopEndInGlobalSpace, + uint32_t aLoopCount, + const char* aLocationOrNullEmptyToIgnoreAtEdges = + ProgressLogger::NO_LOCATION_UPDATE) + : mGlobalProgressOrNull(aProgressLogger.mGlobalProgressOrNull), + mLoopStartInGlobalSpace(aLoopStartInGlobalSpace), + mLoopIncrementInGlobalSpace( + (aLoopEndInGlobalSpace - aLoopStartInGlobalSpace) / aLoopCount), + mLoopCount(aLoopCount), + mLocationOrNullEmptyToIgnoreAtEdges( + aLocationOrNullEmptyToIgnoreAtEdges) {} + + RefPtr<ProgressLogger::SharedProgress> mGlobalProgressOrNull; + ProportionValue mLoopStartInGlobalSpace; + ProportionValue mLoopIncrementInGlobalSpace; + uint32_t mLoopCount; + const char* mLocationOrNullEmptyToIgnoreAtEdges; +}; + +[[nodiscard]] ProgressLogger::IndexAndProgressLoggerRange +ProgressLogger::CreateLoopSubLoggersFromTo( + ProportionValue aLoopStartInLocalSpace, + ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, + const char* aLocationOrNullEmptyToIgnoreAtEdges) { + return IndexAndProgressLoggerRange{ + *this, LocalToGlobal(aLoopStartInLocalSpace), + LocalToGlobal(aLoopEndInLocalSpace), aLoopCount, + aLocationOrNullEmptyToIgnoreAtEdges}; +} + +[[nodiscard]] ProgressLogger::IndexAndProgressLoggerRange +ProgressLogger::CreateLoopSubLoggersTo( + ProportionValue aLoopEndInLocalSpace, uint32_t aLoopCount, + const char* aLocationOrNullEmptyToIgnoreAtEdges) { + return IndexAndProgressLoggerRange{ + *this, GetGlobalProgress(), LocalToGlobal(aLoopEndInLocalSpace), + aLoopCount, aLocationOrNullEmptyToIgnoreAtEdges}; +} + +} // namespace mozilla + +#endif // ProgressLogger_h |