Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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