Magnum::DebugTools::FrameProfiler class new in 2020.06

Frame profiler.

A generic implementation of a frame profiler supporting a moving average over a set of frames as well as delayed measurements to avoid stalls when querying the results. This class alone doesn't provide any pre-defined measurements, see for example FrameProfilerGL that provides common measurements like CPU and GPU time.

Basic usage

Measurements are performed by calling beginFrame() and endFrame() at designated points in the frame:

void MyApp::drawEvent() {
    _profiler.beginFrame();

    // actual drawing code …

    _profiler.endFrame();

    // possibly other code (such as UI) you don't want to have included in the
    // measurements …

    swapBuffers();
    redraw();
}

In order to have stable profiling results, the application needs to redraw constantly. However for applications that otherwise redraw only on change it might be wasteful — to account for this, it's possible to toggle the profiler operation using enable() / disable() and then redraw() can be called only if isEnabled() returns true.

Data for all measurements is then available through measurementName(), measurementUnits() and measurementMean(). For a convenient overview of all measured values you can call statistics() and feed its output to a UI library or something that can render text. Alternatively, if you don't want to bother with text rendering, call printStatistics() to have the output periodically printed to the console. If an interactive terminal is detected, the output will be colored and refreshing itself in place. Together with the on-demand profiling, it could look like this, refreshing the output every 10 frames:

    _profiler.endFrame();
    _profiler.printStatistics(10);

    swapBuffers();
    if(_profiler.isEnabled()) redraw();
}

And here's a sample output on the terminal — using a fully configured FrameProfilerGL:

Last 50 frames:
  Frame time: 16.65 ms
  CPU duration: 14.72 ms
  GPU duration: 10.89 ms
  Vertex fetch ratio: 0.24
  Primitives clipped: 59.67 %

Setting up measurements

Unless you're using this class through FrameProfilerGL, measurements have to be set up by passing Measurement instances to the setup() function or to the constructor, together with specifying count of frames for the moving average. A CPU duration measurements using the std::chrono APIs over last 50 frames can be done like this:

using std::chrono::high_resolution_clock;

high_resolution_clock::time_point frameBeginTime;
DebugTools::FrameProfiler profiler{{
    DebugTools::FrameProfiler::Measurement{"CPU time",
        DebugTools::FrameProfiler::Units::Nanoseconds,
        [](void* state) {
            *static_cast<high_resolution_clock::time_point*>(state)
                = high_resolution_clock::now();
        },
        [](void* state) {
            return UnsignedLong(
                std::chrono::duration_cast<std::chrono::nanoseconds>(
                    *static_cast<high_resolution_clock::time_point*>(state)
                    - high_resolution_clock::now()).count());
        }, &frameBeginTime}
}, 50};

In the above case, the measurement result is available immediately on frame end. That's not always the case, and for example GPU queries need a few frames delay to avoid stalls from CPU/GPU synchronization. The following snippet sets up sample count measurement with a delay, using three separate GL::SampleQuery instances that are cycled through each frame and retrieved two frames later. The profiler automatically takes care of choosing one of the three instances for each measurement via additional current / previous parameters passed to each callback:

GL::SampleQuery queries[3]{
    GL::SampleQuery{GL::SampleQuery::Target::SamplesPassed},
    GL::SampleQuery{GL::SampleQuery::Target::SamplesPassed},
    GL::SampleQuery{GL::SampleQuery::Target::SamplesPassed}
};
DebugTools::FrameProfiler _profiler{{
    DebugTools::FrameProfiler::Measurement{"Samples",
        DebugTools::FrameProfiler::Units::Count,
        UnsignedInt(Containers::arraySize(queries)),
        [](void* state, UnsignedInt current) {
            static_cast<GL::SampleQuery*>(state)[current].begin();
        },
        [](void* state, UnsignedInt current) {
            static_cast<GL::SampleQuery*>(state)[current].end();
        },
        [](void* state, UnsignedInt previous, UnsignedInt) {
            return static_cast<GL::SampleQuery*>(state)[previous]
                .result<UnsignedLong>();
        }, queries}
}, 50};

Derived classes

class FrameProfilerGL new in 2020.06
OpenGL frame profiler.

Public types

class Measurement
Measurement.
enum class Units: UnsignedByte { Nanoseconds, Bytes, Count, RatioThousandths, PercentageThousandths }
Measurement units.

Constructors, destructors, conversion operators

FrameProfiler() explicit noexcept
Default constructor.
FrameProfiler(Containers::Array<Measurement>&& measurements, UnsignedInt maxFrameCount) explicit noexcept
Constructor.
FrameProfiler(std::initializer_list<Measurement> measurements, UnsignedInt maxFrameCount) explicit
FrameProfiler(const FrameProfiler&) deleted
Copying is not allowed.
FrameProfiler(FrameProfiler&&) noexcept
Move constructor.

Public functions

auto operator=(const FrameProfiler&) -> FrameProfiler& deleted
Copying is not allowed.
auto operator=(FrameProfiler&&) -> FrameProfiler& noexcept
Move assignment.
void setup(Containers::Array<Measurement>&& measurements, UnsignedInt maxFrameCount)
Setup measurements.
void setup(std::initializer_list<Measurement> measurements, UnsignedInt maxFrameCount)
auto isEnabled() const -> bool
Whether the profiling is enabled.
void enable()
Enable the profiler.
void disable()
Disable the profiler.
void beginFrame()
Begin a frame.
void endFrame()
End a frame.
auto maxFrameCount() const -> UnsignedInt
Max count of measured frames.
auto measuredFrameCount() const -> UnsignedInt
Count of measured frames.
auto measurementCount() const -> UnsignedInt
Measurement count.
auto measurementName(UnsignedInt id) const -> Containers::StringView
Measurement name.
auto measurementUnits(UnsignedInt id) const -> Units
Measurement units.
auto measurementDelay(UnsignedInt id) const -> UnsignedInt
Measurement delay.
auto isMeasurementAvailable(UnsignedInt id) const -> bool
Whether given measurement is available.
auto measurementData(UnsignedInt id, UnsignedInt frame) const -> UnsignedLong
Measurement data at given frame.
auto measurementMean(UnsignedInt id) const -> Double
Measurement mean.
auto statistics() const -> Containers::String
Overview of all measurements.
void printStatistics(UnsignedInt frequency) const
Print an overview of all measurements to a console at given rate.
void printStatistics(Debug& out, UnsignedInt frequency) const
Print an overview of all measurements to given output at given rate.
void printStatistics(Debug&& out, UnsignedInt frequency) const

Enum documentation

enum class Magnum::DebugTools::FrameProfiler::Units: UnsignedByte

Measurement units.

Enumerators
Nanoseconds

Nanoseconds, measuring for example elapsed time. Depending on the magnitude, statistics() can show them as microseconds, milliseconds or seconds.

Bytes

Bytes, measuring for example memory usage, bandwidth. Depending on the magnitude, statistics() can show them as kB, MB, GB (with a multiplier of 1024).

Count

Generic count. For discrete values that don't fit any of the above. Depending on the magnitude, statistics() can show the value as k, M or G (with a multiplier of 1000).

RatioThousandths

Ratio expressed in 1/1000s. statistics() divides the value by 1000 and depending on the magnitude it can show it also as k, M or G (with a multiplier of 1000).

PercentageThousandths

Percentage expressed in 1/1000s. statistics() divides the value by 1000 and appends a % sign.

Function documentation

Magnum::DebugTools::FrameProfiler::FrameProfiler() explicit noexcept

Default constructor.

Call setup() to populate the profiler with measurements.

Magnum::DebugTools::FrameProfiler::FrameProfiler(Containers::Array<Measurement>&& measurements, UnsignedInt maxFrameCount) explicit noexcept

Constructor.

Equivalent to default-constructing an instance and calling setup() afterwards.

Magnum::DebugTools::FrameProfiler::FrameProfiler(std::initializer_list<Measurement> measurements, UnsignedInt maxFrameCount) explicit

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void Magnum::DebugTools::FrameProfiler::setup(Containers::Array<Measurement>&& measurements, UnsignedInt maxFrameCount)

Setup measurements.

Parameters
measurements List of measurements
maxFrameCount Max frame count over which to calculate a moving average. Expected to be at least 1.

Calling setup() on an already set up profiler will replace existing measurements with measurements and reset measuredFrameCount() back to 0.

void Magnum::DebugTools::FrameProfiler::setup(std::initializer_list<Measurement> measurements, UnsignedInt maxFrameCount)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void Magnum::DebugTools::FrameProfiler::enable()

Enable the profiler.

The profiler is enabled implicitly after construction. When this function is called, it discards all measured data, effectively making measuredFrameCount() zero. If you want to reset the profiler to measure different values as well, call setup().

void Magnum::DebugTools::FrameProfiler::disable()

Disable the profiler.

Disabling the profiler will make beginFrame() and endFrame() a no-op, effectively freezing all reported measurements until the profiler is enabled again.

void Magnum::DebugTools::FrameProfiler::beginFrame()

Begin a frame.

Has to be called at the beginning of a frame and be paired with a corresponding endFrame(). Calls begin functions in all Measurement instances passed to setup(). If the profiler is disabled, the function is a no-op.

void Magnum::DebugTools::FrameProfiler::endFrame()

End a frame.

Has to be called at the end of frame, before buffer swap, and be paired with a corresponding beginFrame(). Calls end functions in all Measurement instances passed to setup() and query functions on all measurements that are sufficiently delayed, saving their output. If the profiler is disabled, the function is a no-op.

UnsignedInt Magnum::DebugTools::FrameProfiler::maxFrameCount() const

Max count of measured frames.

How many frames to calculate a moving average from. At the beginning of a measurement when there's not enough frames, the average is calculated only from measuredFrameCount(). Always at least 1.

UnsignedInt Magnum::DebugTools::FrameProfiler::measuredFrameCount() const

Count of measured frames.

Count of times endFrame() was called, but at most maxFrameCount(), after which the profiler calculates a moving average over last maxFrameCount() frames only. Actual data availability depends on measurementDelay().

UnsignedInt Magnum::DebugTools::FrameProfiler::measurementCount() const

Measurement count.

Count of Measurement instances passed to setup(). If setup() was not called yet, returns 0.

Containers::StringView Magnum::DebugTools::FrameProfiler::measurementName(UnsignedInt id) const

Measurement name.

The id corresponds to the index of the measurement in the list passed to setup(). Expects that id is less than measurementCount(). The returned view is always Containers::StringViewFlag::NullTerminated; Containers::StringViewFlag::Global is set if the corresponding Measurement was created with a global null-terminated string view.

Units Magnum::DebugTools::FrameProfiler::measurementUnits(UnsignedInt id) const

Measurement units.

The id corresponds to the index of the measurement in the list passed to setup(). Expects that id is less than measurementCount().

UnsignedInt Magnum::DebugTools::FrameProfiler::measurementDelay(UnsignedInt id) const

Measurement delay.

How many beginFrame() / endFrame() call pairs needs to be performed before a value for given measurement is available. Always at least 1. The id corresponds to the index of the measurement in the list passed to setup(). Expects that id is less than measurementCount().

bool Magnum::DebugTools::FrameProfiler::isMeasurementAvailable(UnsignedInt id) const

Whether given measurement is available.

Returns true if measuredFrameCount() is at least measurementDelay() for given id, false otherwise. The id corresponds to the index of the measurement in the list passed to setup(). Expects that id is less than measurementCount().

UnsignedLong Magnum::DebugTools::FrameProfiler::measurementData(UnsignedInt id, UnsignedInt frame) const

Measurement data at given frame.

A frame value of 0 is the oldest recorded frame and can go up to measurementCount() lessened by measurementDelay() or maxFrameCount() minus one, whichever is smaller. Expects that id is less than measurementCount() and at least one measurement is available.

Double Magnum::DebugTools::FrameProfiler::measurementMean(UnsignedInt id) const

Measurement mean.

Returns a moving average of $ n $ previous measurements out of the total $ M $ values:

\[ \bar{x}_\text{SM} = \dfrac{1}{n} \sum\limits_{i=0}^{n-1} x_{M -i} \]

The id corresponds to the index of the measurement in the list passed to setup(). Expects that id is less than measurementCount() and that the measurement is available.

Containers::String Magnum::DebugTools::FrameProfiler::statistics() const

Overview of all measurements.

Returns a formatted string with names, means and units of all measurements in the order they were added. If some measurement data is not available yet, prints placeholder values for these.

void Magnum::DebugTools::FrameProfiler::printStatistics(UnsignedInt frequency) const

Print an overview of all measurements to a console at given rate.

Expected to be called every frame. If profiling is enabled, on every frequencyth frame prints the same information as statistics(), but in addition, if the output is a TTY, it's colored and overwrites itself instead of filling up the terminal history. If profiling is disabled, does nothing.

void Magnum::DebugTools::FrameProfiler::printStatistics(Debug& out, UnsignedInt frequency) const

Print an overview of all measurements to given output at given rate.

Compared to printStatistics(UnsignedInt) const prints to given out (which can be also Corrade::Utility::Warning or Corrade::Utility::Error) and uses it to decide whether the output is a TTY and whether to print colors.

void Magnum::DebugTools::FrameProfiler::printStatistics(Debug&& out, UnsignedInt frequency) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

Debug& operator<<(Debug& debug, FrameProfiler::Units value) new in 2020.06

Debug output operator.