This commit is contained in:
2026-09-01 09:31:24 +08:00
parent f15402251d
commit 9de411f623
4 changed files with 263 additions and 0 deletions
@@ -0,0 +1,75 @@
#pragma once
#include "concurrent/base/Concurrent_Struct.hpp"
#include "model/Model.hpp"
#include "statistics/Sliding_Statistics.hpp"
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <optional>
namespace aethera {
/* 所有帧策略共享的统计配置、公开状态和统计实现,不拥有具体调度状态。 */
template <typename Self>
struct Frame_Policy : Model_Layer<
Self,
Root<Self>,
Storage_Registration<Prop_Tag, Import_Struct_With_Dirty>> {
using Layer = Model_Layer<
Self,
Root<Self>,
Storage_Registration<Prop_Tag, Import_Struct_With_Dirty>>;
using Prev_Private = typename Layer::Prev_Private;
using Prev_Builder = typename Layer::Prev_Builder;
struct Prop : Layer::template Prev<Prop_Tag> {
bool statistics_enabled{}; /* true 时采集并发布滑动统计。 */
std::size_t statistics_window_size{120}; /* 启用统计时的样本窗口容量。 */
};
struct State {
std::uint64_t timer_ticks{}; /* 已处理的周期到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 到期时三帧均忙而丢弃的次数。 */
std::uint64_t completed_frames{}; /* 已完成媒体发送的帧数。 */
Statistics_Summary render_time_ns; /* 渲染耗时滑动统计,单位纳秒。 */
Statistics_Summary send_time_ns; /* 发送耗时滑动统计,单位纳秒。 */
Statistics_Summary end_to_end_time_ns; /* 渲染开始至发送完成耗时统计,单位纳秒。 */
};
struct Private : Prev_Private {
struct Active_Statistics {
explicit Active_Statistics(std::size_t window_size);
Sliding_Statistics render; /* 渲染耗时窗口。 */
Sliding_Statistics send; /* 发送耗时窗口。 */
Sliding_Statistics end_to_end; /* 渲染开始至发送结束耗时窗口。 */
};
bool prepare_statistics(
const Prop& properties,
Export_Struct<State>& published_state);
void record_timer_tick(
bool dropped,
Export_Struct<State>& published_state);
void record_render(
std::chrono::nanoseconds duration,
Export_Struct<State>& published_state);
void record_send(
std::chrono::nanoseconds send_duration,
std::chrono::nanoseconds end_to_end_duration,
Export_Struct<State>& published_state);
private:
void publish(Export_Struct<State>& published_state);
std::optional<Active_Statistics> statistics; /* 存在即表示本次运行启用了统计。 */
std::uint64_t timer_ticks{}; /* 本次运行累计的到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 本次运行累计的忙碌丢弃次数。 */
std::uint64_t completed_frames{}; /* 本次运行累计的完成帧数。 */
};
struct Builder : Prev_Builder {};
Export_Struct<State> state; /* 由策略互斥串行发布的帧策略运行统计。 */
};
}
#include "Frame_Policy.ipp"
@@ -0,0 +1,88 @@
#pragma once
namespace aethera {
template <typename Self>
Frame_Policy<Self>::Private::Active_Statistics::Active_Statistics(
std::size_t window_size) :
render(window_size),
send(window_size),
end_to_end(window_size) {}
template <typename Self>
bool Frame_Policy<Self>::Private::prepare_statistics(
const Prop& properties,
Export_Struct<State>& published_state) {
if (properties.statistics_enabled && properties.statistics_window_size == 0) {
return false;
}
statistics.reset();
timer_ticks = 0;
dropped_timer_ticks = 0;
completed_frames = 0;
if (properties.statistics_enabled) {
statistics.emplace(properties.statistics_window_size);
}
publish(published_state);
return true;
}
template <typename Self>
void Frame_Policy<Self>::Private::record_timer_tick(
bool dropped,
Export_Struct<State>& published_state) {
if (!statistics) {
return;
}
++timer_ticks;
if (dropped) {
++dropped_timer_ticks;
}
publish(published_state);
}
template <typename Self>
void Frame_Policy<Self>::Private::record_render(
std::chrono::nanoseconds duration,
Export_Struct<State>& published_state) {
if (!statistics) {
return;
}
statistics->render.add(static_cast<double>(duration.count()));
publish(published_state);
}
template <typename Self>
void Frame_Policy<Self>::Private::record_send(
std::chrono::nanoseconds send_duration,
std::chrono::nanoseconds end_to_end_duration,
Export_Struct<State>& published_state) {
if (!statistics) {
return;
}
statistics->send.add(static_cast<double>(send_duration.count()));
statistics->end_to_end.add(
static_cast<double>(end_to_end_duration.count()));
++completed_frames;
publish(published_state);
}
template <typename Self>
void Frame_Policy<Self>::Private::publish(
Export_Struct<State>& published_state) {
State& output = *published_state.internal.use();
output.timer_ticks = timer_ticks;
output.dropped_timer_ticks = dropped_timer_ticks;
output.completed_frames = completed_frames;
output.render_time_ns = statistics
? statistics->render.summary()
: Statistics_Summary{};
output.send_time_ns = statistics
? statistics->send.summary()
: Statistics_Summary{};
output.end_to_end_time_ns = statistics
? statistics->end_to_end.summary()
: Statistics_Summary{};
published_state.internal.advance();
}
}
@@ -0,0 +1,66 @@
#include "Sliding_Statistics.hpp"
#include <algorithm>
#include <cmath>
#include <iterator>
#include <exception>
namespace aethera {
Sliding_Statistics::Sliding_Statistics(std::size_t window_size) :
window_size(window_size) {
if (this->window_size == 0) {
std::terminate();
}
samples.reserve(this->window_size);
}
void Sliding_Statistics::add(double sample) {
if (samples.size() < window_size) {
samples.push_back(sample);
} else {
const double retired = samples[next_index];
const auto retired_position = ordered.find(retired);
if (retired_position == ordered.end()) {
std::terminate();
}
ordered.erase(retired_position);
sum -= retired;
squared_sum -= retired * retired;
samples[next_index] = sample;
next_index = (next_index + 1) % samples.size();
}
ordered.insert(sample);
sum += sample;
squared_sum += sample * sample;
}
void Sliding_Statistics::clear() noexcept {
samples.clear();
ordered.clear();
next_index = 0;
sum = 0.0;
squared_sum = 0.0;
}
Statistics_Summary Sliding_Statistics::summary() const noexcept {
if (samples.empty()) {
return {};
}
const auto count = static_cast<std::uint64_t>(samples.size());
const double average = sum / static_cast<double>(count);
const double variance = std::max(
0.0,
squared_sum / static_cast<double>(count) - average * average);
const std::size_t p95_rank =
(static_cast<std::size_t>(count) * 95 + 99) / 100;
auto p95_position = ordered.begin();
std::advance(p95_position, p95_rank - 1);
return {
count,
average,
*p95_position,
std::sqrt(variance)};
}
}
@@ -0,0 +1,34 @@
#pragma once
#include "global.hpp"
#include <cstddef>
#include <cstdint>
#include <set>
#include <vector>
namespace aethera {
struct Statistics_Summary {
std::uint64_t sample_count{}; /* 当前滑动窗口内的有效样本数。 */
double average{}; /* 当前窗口样本的算术平均值。 */
double p95{}; /* 当前窗口按最近秩定义的第 95 百分位。 */
double standard_deviation{}; /* 当前窗口总体标准差,用于表达波动。 */
};
/* 固定容量滑动统计;有序样本直接维护 P95,不复制窗口或生成快照。 */
struct Sliding_Statistics : Immovable {
explicit Sliding_Statistics(std::size_t window_size);
void add(double sample);
void clear() noexcept;
[[nodiscard]] Statistics_Summary summary() const noexcept;
private:
std::size_t window_size; /* 构造时确定且非零的滑动窗口容量。 */
std::vector<double> samples; /* 按写入顺序保存当前窗口,用于淘汰最旧样本。 */
std::multiset<double> ordered; /* 与 samples 中有效窗口等价的有序样本集合。 */
std::size_t next_index{}; /* 窗口满后下一次被替换的样本位置。 */
double sum{}; /* 当前窗口样本和。 */
double squared_sum{}; /* 当前窗口样本平方和。 */
};
}