16#include <mach/mach_time.h>
72 std::uint64_t xrun_count,
74 const float watch_load =
75 std::isfinite(policy.watch_load) && policy.watch_load > 0.0f
78 const float overload_load =
79 std::isfinite(policy.overload_load) && policy.overload_load > watch_load
80 ? policy.overload_load
81 : std::max(watch_load, 1.0f);
82 const float critical_load =
83 std::isfinite(policy.critical_load) && policy.critical_load > overload_load
84 ? policy.critical_load
85 : std::max(overload_load, 1.25f);
86 const float observed_load =
89 AudioRuntimeOverloadReport report;
90 if (observed_load >= critical_load
91 || (policy.critical_xruns > 0 && xrun_count >= policy.critical_xruns)
92 || (policy.critical_overloads > 0
95 report.should_shed_optional_work =
true;
96 report.should_bypass_optional_work =
true;
97 report.validation_failure =
true;
98 report.action =
"bypass-optional-work";
102 if (observed_load >= overload_load
103 || (policy.watch_xruns > 0 && xrun_count >= policy.watch_xruns)
104 || (policy.watch_overloads > 0
107 report.should_shed_optional_work =
true;
108 report.action =
"shed-optional-work";
112 if (observed_load >= watch_load) {
114 report.action =
"monitor";
136 const std::uint64_t divisor = std::gcd(
numer,
denom);
142#if defined(__APPLE__)
143 mach_timebase_info_data_t
timebase{};
152 constexpr std::int64_t
to_ns(std::uint64_t ticks)
const noexcept {
153 constexpr auto kMax =
static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max());
156 if (ticks > kMax /
numer)
157 return std::numeric_limits<std::int64_t>::max();
158 return static_cast<std::int64_t
>(std::min(ticks *
numer /
denom, kMax));
201#if defined(__APPLE__)
202 return mach_absolute_time();
204 return static_cast<std::uint64_t
>(std::chrono::duration_cast<std::chrono::nanoseconds>(
205 std::chrono::steady_clock::now().time_since_epoch())
213 void begin(
int num_frames,
float sample_rate)
noexcept {
214#if defined(PULP_AUDIOWORKLET_DISABLE_LOAD_TIMING)
223 if (num_frames <= 0 || !(sample_rate > 0.0f)) {
228 const double available_ns =
229 static_cast<double>(num_frames) /
static_cast<double>(sample_rate) * 1e9;
231 if (!std::isfinite(available_ns) || available_ns <= 0.0 ||
232 available_ns >
static_cast<double>(std::numeric_limits<int64_t>::max())) {
237 available_ns_ =
static_cast<int64_t
>(available_ns);
243#if defined(PULP_AUDIOWORKLET_DISABLE_LOAD_TIMING)
247 const int64_t elapsed_ns =
248 ratio_.to_ns(end_ticks >= start_ticks_ ? end_ticks - start_ticks_ : 0);
250 if (available_ns_ > 0) {
251 float raw =
static_cast<float>(elapsed_ns) /
static_cast<float>(available_ns_);
253 const float previous_load = load_.load(std::memory_order_relaxed);
254 const float next_load = previous_load + smoothing_ * (raw - previous_load);
255 const float previous_peak = peak_load_.load(std::memory_order_relaxed);
256 load_.store(next_load, std::memory_order_relaxed);
257 peak_load_.store(std::max(previous_peak, raw), std::memory_order_relaxed);
258 last_load_.store(raw, std::memory_order_relaxed);
259 last_elapsed_ns_.store(elapsed_ns, std::memory_order_relaxed);
260 last_available_ns_.store(available_ns_, std::memory_order_relaxed);
261 callback_count_.fetch_add(1, std::memory_order_relaxed);
262 if (raw >= overload_threshold_) {
263 overload_count_.fetch_add(1, std::memory_order_relaxed);
270 float load() const noexcept {
return load_.load(std::memory_order_relaxed); }
273 float peak_load() const noexcept {
return peak_load_.load(std::memory_order_relaxed); }
276 float last_load() const noexcept {
return last_load_.load(std::memory_order_relaxed); }
281 return last_elapsed_ns_.load(std::memory_order_relaxed);
286 return callback_count_.load(std::memory_order_relaxed);
291 return overload_count_.load(std::memory_order_relaxed);
297 .load = load_.load(std::memory_order_relaxed),
298 .peak_load = peak_load_.load(std::memory_order_relaxed),
299 .last_load = last_load_.load(std::memory_order_relaxed),
300 .elapsed_ns = last_elapsed_ns_.load(std::memory_order_relaxed),
301 .available_ns = last_available_ns_.load(std::memory_order_relaxed),
302 .callback_count = callback_count_.load(std::memory_order_relaxed),
303 .overload_count = overload_count_.load(std::memory_order_relaxed),
308 void reset_peak() noexcept { peak_load_.store(0.0f, std::memory_order_relaxed); }
312 load_.store(0.0f, std::memory_order_relaxed);
313 peak_load_.store(0.0f, std::memory_order_relaxed);
314 last_load_.store(0.0f, std::memory_order_relaxed);
315 last_elapsed_ns_.store(0, std::memory_order_relaxed);
316 last_available_ns_.store(0, std::memory_order_relaxed);
317 callback_count_.store(0, std::memory_order_relaxed);
318 overload_count_.store(0, std::memory_order_relaxed);
326 smoothing_ = std::clamp(alpha, 0.0f, 1.0f);
332 overload_threshold_ = (std::isfinite(
load) &&
load > 0.0f) ?
load : 1.0f;
351 std::uint64_t start_ticks_ = 0;
352 int64_t available_ns_ = 0;
353 std::atomic<float> load_{0.0f};
354 std::atomic<float> peak_load_{0.0f};
355 std::atomic<float> last_load_{0.0f};
356 std::atomic<std::int64_t> last_elapsed_ns_{0};
357 std::atomic<std::int64_t> last_available_ns_{0};
358 std::atomic<std::uint64_t> callback_count_{0};
359 std::atomic<std::uint64_t> overload_count_{0};
360 float smoothing_ = 0.1f;
361 float overload_threshold_ = 1.0f;
float last_load() const noexcept
Last raw load sample.
Definition load_measurer.hpp:276
void set_overload_threshold(float load) noexcept
Set the raw-load threshold that increments overload_count().
Definition load_measurer.hpp:331
AudioProcessLoadMeasurer(LoadClockRatio ratio) noexcept
Uses ratio to convert clock ticks to nanoseconds.
Definition load_measurer.hpp:190
float peak_load() const noexcept
Peak load since last reset.
Definition load_measurer.hpp:273
void begin(int num_frames, float sample_rate) noexcept
Call at the start of the audio callback.
Definition load_measurer.hpp:213
AudioProcessLoadMeasurer() noexcept
Reads the platform timebase once, here, so begin() and end() never query it.
Definition load_measurer.hpp:186
void set_smoothing(float alpha) noexcept
Set smoothing factor (0 = no smoothing, 1 = no averaging).
Definition load_measurer.hpp:325
std::uint64_t callback_count() const noexcept
Number of valid callback windows measured since the last reset.
Definition load_measurer.hpp:285
std::uint64_t overload_count() const noexcept
Number of valid callback windows whose raw load reached the overload threshold.
Definition load_measurer.hpp:290
std::int64_t last_elapsed_ns() const noexcept
Most recent measured callback duration, in nanoseconds (0 before the first valid window).
Definition load_measurer.hpp:280
AudioProcessLoadSnapshot snapshot() const noexcept
Latest telemetry snapshot. Safe to poll from UI/diagnostic threads.
Definition load_measurer.hpp:295
float load() const noexcept
Current smoothed CPU load (0.0 = idle, 1.0 = full buffer, >1.0 = overrun).
Definition load_measurer.hpp:270
void reset() noexcept
Reset all state.
Definition load_measurer.hpp:311
void reset_peak() noexcept
Reset peak load tracker.
Definition load_measurer.hpp:308
LoadClockRatio clock_ratio() const noexcept
The tick-to-nanosecond ratio captured at construction.
Definition load_measurer.hpp:193
void end() noexcept
Call at the end of the audio callback.
Definition load_measurer.hpp:242
static std::uint64_t read_ticks() noexcept
One raw read of the measurer's monotonic clock, in its own ticks (convert spans with clock_ratio()).
Definition load_measurer.hpp:200
Definition analyzer_provider.hpp:12
AudioRuntimeOverloadReport evaluate_audio_runtime_overload(const AudioProcessLoadSnapshot &load, std::uint64_t xrun_count, const AudioRuntimeOverloadPolicy &policy={}) noexcept
Definition load_measurer.hpp:70
AudioRuntimeOverloadSeverity
Definition load_measurer.hpp:35
@ Critical
Definition load_measurer.hpp:39
@ Overloaded
Definition load_measurer.hpp:38
@ Nominal
Definition load_measurer.hpp:36
@ Watch
Definition load_measurer.hpp:37
const char * to_string(LiveDspNodeKind kind) noexcept
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
Definition beat_division.hpp:11
Snapshot published by AudioProcessLoadMeasurer.
Definition load_measurer.hpp:25
float peak_load
Highest raw load since the last reset.
Definition load_measurer.hpp:27
float load
Smoothed CPU load. 1.0 == full buffer.
Definition load_measurer.hpp:26
std::int64_t available_ns
Definition load_measurer.hpp:30
std::int64_t elapsed_ns
Definition load_measurer.hpp:29
std::uint64_t callback_count
Definition load_measurer.hpp:31
float last_load
Most recent raw load sample.
Definition load_measurer.hpp:28
std::uint64_t overload_count
Definition load_measurer.hpp:32
Definition load_measurer.hpp:42
std::uint64_t critical_xruns
Definition load_measurer.hpp:47
float overload_load
Definition load_measurer.hpp:44
float watch_load
Definition load_measurer.hpp:43
float critical_load
Definition load_measurer.hpp:45
std::uint64_t watch_overloads
Definition load_measurer.hpp:48
std::uint64_t watch_xruns
Definition load_measurer.hpp:46
std::uint64_t critical_overloads
Definition load_measurer.hpp:49
Definition load_measurer.hpp:52
bool should_shed_optional_work
Definition load_measurer.hpp:54
bool validation_failure
Definition load_measurer.hpp:56
const char * action
Definition load_measurer.hpp:57
AudioRuntimeOverloadSeverity severity
Definition load_measurer.hpp:53
bool should_bypass_optional_work
Definition load_measurer.hpp:55
Converts a span of monotonic clock ticks to nanoseconds.
Definition load_measurer.hpp:127
std::uint64_t denom
Definition load_measurer.hpp:129
constexpr std::int64_t to_ns(std::uint64_t ticks) const noexcept
Nanoseconds in a span of ticks, saturating at INT64_MAX.
Definition load_measurer.hpp:152
friend constexpr bool operator==(const LoadClockRatio &, const LoadClockRatio &)=default
std::uint64_t numer
Definition load_measurer.hpp:128
static constexpr LoadClockRatio reduced(std::uint64_t numer, std::uint64_t denom) noexcept
numer/denom reduced by their GCD.
Definition load_measurer.hpp:133
static LoadClockRatio host() noexcept
The running platform's timebase. Not for the audio thread.
Definition load_measurer.hpp:141