仍有bug
This commit is contained in:
@@ -411,6 +411,26 @@ public:
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
advance_unlocked();
|
||||
}
|
||||
template <detail::State_Tag_In<States> Tag>
|
||||
void publish_state() {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
data().state.advance();
|
||||
data().state_callbacks.template notify<Tag>(*data().state.current);
|
||||
}
|
||||
template <detail::State_Tag_In<States> Tag, auto... Members, typename Callback>
|
||||
requires (sizeof...(Members) > 0) &&
|
||||
(detail::State_Member<Members, State> && ...) &&
|
||||
std::invocable<Callback, State_Access<State>>
|
||||
void publish_state(Callback&& callback) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
(before_state_set(Members), ...);
|
||||
std::invoke(std::forward<Callback>(callback),
|
||||
State_Access{*data().state.pending});
|
||||
(after_state_set(Members), ...);
|
||||
(emit_state_dependencies<Members>(), ...);
|
||||
data().state.advance();
|
||||
data().state_callbacks.template notify<Tag>(*data().state.current);
|
||||
}
|
||||
template <std::invocable<const State&> Callback>
|
||||
void advance(Callback&& callback) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
|
||||
+150
-10
@@ -1,9 +1,11 @@
|
||||
#include "frame.hpp"
|
||||
#include "frame_statistics.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
namespace aethera {
|
||||
namespace {
|
||||
constexpr std::size_t marker_count = static_cast<std::size_t>(Frame_Trace_Marker::count);
|
||||
@@ -49,16 +51,154 @@ void Render_Frame::record(Frame_Trace_Measurement measurement, std::uint64_t val
|
||||
std::uint64_t expected{};
|
||||
d->measurements[index].compare_exchange_strong(expected, encode_present_value(value_ns), std::memory_order_release, std::memory_order_relaxed);
|
||||
}
|
||||
std::vector<Frame_Trace_Point> Render_Frame::trace_points() const {
|
||||
std::vector<Frame_Trace_Point> result;
|
||||
result.reserve(marker_count);
|
||||
for (std::size_t index = 0; index < marker_count; ++index) if (const auto encoded = d->markers[index].load(std::memory_order_acquire)) result.push_back({static_cast<Frame_Trace_Marker>(index), decode_present_value(encoded)});
|
||||
return result;
|
||||
}
|
||||
std::vector<Frame_Trace_Value> Render_Frame::trace_values() const {
|
||||
std::vector<Frame_Trace_Value> result;
|
||||
result.reserve(measurement_count);
|
||||
for (std::size_t index = 0; index < measurement_count; ++index) if (const auto encoded = d->measurements[index].load(std::memory_order_acquire)) result.push_back({static_cast<Frame_Trace_Measurement>(index), decode_present_value(encoded)});
|
||||
Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) const {
|
||||
std::array<std::optional<double>, marker_count> markers{};
|
||||
for (std::size_t index = 0; index < marker_count; ++index) {
|
||||
const auto encoded = d->markers[index].load(std::memory_order_acquire);
|
||||
if (encoded)
|
||||
markers[index] = static_cast<double>(decode_present_value(encoded)) /
|
||||
1'000'000.0;
|
||||
}
|
||||
std::array<double, measurement_count> measurements{};
|
||||
for (std::size_t index = 0; index < measurement_count; ++index) {
|
||||
const auto encoded = d->measurements[index].load(std::memory_order_acquire);
|
||||
if (encoded)
|
||||
measurements[index] = static_cast<double>(decode_present_value(encoded)) /
|
||||
1'000'000.0;
|
||||
}
|
||||
const auto marker = [&](Frame_Trace_Marker value) {
|
||||
return markers[static_cast<std::size_t>(value)].value_or(0.0);
|
||||
};
|
||||
const auto interval = [&](Frame_Trace_Marker first, Frame_Trace_Marker last) {
|
||||
const auto start = markers[static_cast<std::size_t>(first)];
|
||||
const auto finish = markers[static_cast<std::size_t>(last)];
|
||||
return start && finish ? std::max(0.0, *finish - *start) : 0.0;
|
||||
};
|
||||
const auto measurement = [&](Frame_Trace_Measurement value) {
|
||||
return measurements[static_cast<std::size_t>(value)];
|
||||
};
|
||||
|
||||
Frame_Statistics_Sample result{};
|
||||
result.set(Frame_Statistic::server_completion_ms,
|
||||
marker(Frame_Trace_Marker::frame_ready));
|
||||
result.set(Frame_Statistic::scene_render_ms, interval(
|
||||
Frame_Trace_Marker::scene_render_started,
|
||||
Frame_Trace_Marker::scene_render_finished));
|
||||
result.set(Frame_Statistic::event_dispatch_ms, interval(
|
||||
Frame_Trace_Marker::event_dispatch_started,
|
||||
Frame_Trace_Marker::event_dispatch_finished));
|
||||
result.set(Frame_Statistic::prepare_ms, interval(
|
||||
Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished));
|
||||
result.set(Frame_Statistic::paint_ms, interval(
|
||||
Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished));
|
||||
result.set(Frame_Statistic::backend_queue_ms, interval(
|
||||
Frame_Trace_Marker::backend_queue_entered, Frame_Trace_Marker::backend_queue_left));
|
||||
result.set(Frame_Statistic::gpu_submission_ms, interval(
|
||||
Frame_Trace_Marker::gpu_submitted, Frame_Trace_Marker::gpu_completed));
|
||||
result.set(Frame_Statistic::readback_stage_ms, interval(
|
||||
Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished));
|
||||
result.set(Frame_Statistic::callback_ms, interval(
|
||||
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished));
|
||||
|
||||
constexpr std::array measurement_statistics{
|
||||
Frame_Statistic::backend_apply_ms,
|
||||
Frame_Statistic::backend_plan_ms,
|
||||
Frame_Statistic::backend_execute_ms,
|
||||
Frame_Statistic::backend_submit_ms,
|
||||
Frame_Statistic::gpu_fence_wait_ms,
|
||||
Frame_Statistic::gpu_render_ms,
|
||||
Frame_Statistic::gpu_transition_ms,
|
||||
Frame_Statistic::gpu_copy_ms,
|
||||
Frame_Statistic::gpu_total_ms,
|
||||
Frame_Statistic::readback_ms};
|
||||
for (std::size_t index = 0; index < measurement_statistics.size(); ++index)
|
||||
result.set(measurement_statistics[index], measurements[index]);
|
||||
|
||||
double remaining = marker(Frame_Trace_Marker::frame_ready);
|
||||
const auto take = [&](double requested) {
|
||||
const auto value = std::min(remaining, std::max(0.0, requested));
|
||||
remaining -= value;
|
||||
return value;
|
||||
};
|
||||
const double scene_time = interval(Frame_Trace_Marker::scene_render_started,
|
||||
Frame_Trace_Marker::scene_render_finished);
|
||||
const double event_time = std::min(scene_time, interval(
|
||||
Frame_Trace_Marker::event_dispatch_started,
|
||||
Frame_Trace_Marker::event_dispatch_finished));
|
||||
const double prepare_time = std::min(std::max(0.0, scene_time - event_time), interval(
|
||||
Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished));
|
||||
const double paint_time = std::min(
|
||||
std::max(0.0, scene_time - event_time - prepare_time), interval(
|
||||
Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished));
|
||||
|
||||
if (dimension == Frame_Dimension::two_dimensional) {
|
||||
result.set(Frame_Statistic::pipeline_2d_event_ms, take(event_time));
|
||||
result.set(Frame_Statistic::pipeline_2d_prepare_ms, take(prepare_time));
|
||||
result.set(Frame_Statistic::pipeline_2d_paint_ms, take(paint_time));
|
||||
result.set(Frame_Statistic::pipeline_2d_scene_coordination_ms,
|
||||
take(std::max(0.0, scene_time - event_time - prepare_time - paint_time)));
|
||||
result.set(Frame_Statistic::pipeline_2d_callback_ms, take(interval(
|
||||
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished)));
|
||||
result.set(Frame_Statistic::pipeline_2d_frame_handoff_ms, remaining);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.set(Frame_Statistic::pipeline_3d_event_ms, take(event_time));
|
||||
result.set(Frame_Statistic::pipeline_3d_prepare_ms, take(prepare_time));
|
||||
result.set(Frame_Statistic::pipeline_3d_submit_graph_ms, take(paint_time));
|
||||
result.set(Frame_Statistic::pipeline_3d_scene_coordination_ms,
|
||||
take(std::max(0.0, scene_time - event_time - prepare_time - paint_time)));
|
||||
const double scene_finished = marker(Frame_Trace_Marker::scene_render_finished);
|
||||
const double queue_entered = marker(Frame_Trace_Marker::backend_queue_entered);
|
||||
const double backend_prepare_started = marker(Frame_Trace_Marker::backend_prepare_started);
|
||||
const double backend_prepare_finished = marker(Frame_Trace_Marker::backend_prepare_finished);
|
||||
const double submit_queued = marker(Frame_Trace_Marker::backend_submit_queued);
|
||||
const double queue_left = marker(Frame_Trace_Marker::backend_queue_left);
|
||||
result.set(Frame_Statistic::pipeline_3d_prepare_queue_ms, take(std::max(
|
||||
0.0, backend_prepare_started - std::max(scene_finished, queue_entered))));
|
||||
double preparation_window = std::max(0.0,
|
||||
backend_prepare_finished - backend_prepare_started);
|
||||
const auto take_preparation = [&](Frame_Trace_Measurement key) {
|
||||
const double value = std::min(preparation_window,
|
||||
std::max(0.0, measurement(key)));
|
||||
preparation_window -= value;
|
||||
return take(value);
|
||||
};
|
||||
result.set(Frame_Statistic::pipeline_3d_backend_apply_ms,
|
||||
take_preparation(Frame_Trace_Measurement::backend_apply_ns));
|
||||
result.set(Frame_Statistic::pipeline_3d_backend_plan_ms,
|
||||
take_preparation(Frame_Trace_Measurement::backend_plan_ns));
|
||||
result.set(Frame_Statistic::pipeline_3d_backend_execute_ms,
|
||||
take_preparation(Frame_Trace_Measurement::backend_execute_ns));
|
||||
result.set(Frame_Statistic::pipeline_3d_backend_commands_ms, take(preparation_window));
|
||||
result.set(Frame_Statistic::pipeline_3d_backend_queue_ms,
|
||||
take(std::max(0.0, queue_left - submit_queued)));
|
||||
const double gpu_submitted = marker(Frame_Trace_Marker::gpu_submitted);
|
||||
double submit_window = std::max(0.0, gpu_submitted - queue_left);
|
||||
const double measured_submit = std::min(submit_window, std::max(
|
||||
0.0, measurement(Frame_Trace_Measurement::backend_submit_ns)));
|
||||
result.set(Frame_Statistic::pipeline_3d_backend_submit_ms, take(measured_submit));
|
||||
submit_window -= measured_submit;
|
||||
result.set(Frame_Statistic::pipeline_3d_submit_handoff_ms, take(submit_window));
|
||||
double gpu_window = interval(Frame_Trace_Marker::gpu_submitted,
|
||||
Frame_Trace_Marker::gpu_completed);
|
||||
const auto take_gpu = [&](Frame_Trace_Measurement key) {
|
||||
const double value = std::min(gpu_window, std::max(0.0, measurement(key)));
|
||||
gpu_window -= value;
|
||||
return take(value);
|
||||
};
|
||||
result.set(Frame_Statistic::pipeline_3d_gpu_render_ms,
|
||||
take_gpu(Frame_Trace_Measurement::gpu_render_ns));
|
||||
result.set(Frame_Statistic::pipeline_3d_gpu_transition_ms,
|
||||
take_gpu(Frame_Trace_Measurement::gpu_transition_ns));
|
||||
result.set(Frame_Statistic::pipeline_3d_gpu_copy_ms,
|
||||
take_gpu(Frame_Trace_Measurement::gpu_copy_ns));
|
||||
result.set(Frame_Statistic::pipeline_3d_gpu_sync_ms, take(gpu_window));
|
||||
result.set(Frame_Statistic::pipeline_3d_readback_ms, take(interval(
|
||||
Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished)));
|
||||
result.set(Frame_Statistic::pipeline_3d_callback_ms, take(interval(
|
||||
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished)));
|
||||
result.set(Frame_Statistic::pipeline_3d_completion_handoff_ms, remaining);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
namespace aethera {
|
||||
enum class Frame_Dimension : std::uint8_t;
|
||||
struct Frame_Statistics_Sample;
|
||||
enum class Frame_Trace_Marker : std::uint8_t {
|
||||
created,
|
||||
scene_render_requested,
|
||||
@@ -43,6 +45,7 @@ enum class Frame_Trace_Measurement : std::uint8_t {
|
||||
count
|
||||
};
|
||||
struct Frame_Identity {
|
||||
friend bool operator==(const Frame_Identity&, const Frame_Identity&) = default;
|
||||
std::uint64_t sequence{}; /* 外部帧管理器分配的单调帧序号。 */
|
||||
std::uint64_t correlation_id{}; /* 与调用方请求关联的标识;零值表示未关联。 */
|
||||
};
|
||||
@@ -62,8 +65,7 @@ public:
|
||||
[[nodiscard]] std::uint64_t created_time_unix_ns() const noexcept;
|
||||
void mark(Frame_Trace_Marker marker) noexcept;
|
||||
void record(Frame_Trace_Measurement measurement, std::uint64_t value_ns) noexcept;
|
||||
[[nodiscard]] std::vector<Frame_Trace_Point> trace_points() const;
|
||||
[[nodiscard]] std::vector<Frame_Trace_Value> trace_values() const;
|
||||
[[nodiscard]] Frame_Statistics_Sample statistics(Frame_Dimension dimension) const;
|
||||
protected:
|
||||
/* 物理帧槽再次承载新逻辑帧时,重建其唯一身份和诊断时间原点。 */
|
||||
void begin(Frame_Identity identity) noexcept;
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "frame_statistics.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace aethera {
|
||||
void Frame_Statistics_Sample::set(Frame_Statistic statistic, double value) noexcept {
|
||||
const auto index = static_cast<std::size_t>(statistic);
|
||||
if (index >= values.size() || !std::isfinite(value)) return;
|
||||
values[index] = value;
|
||||
present.set(index);
|
||||
}
|
||||
|
||||
Sliding_Statistics::Sliding_Statistics() : Sliding_Statistics(600) {}
|
||||
|
||||
Sliding_Statistics::Sliding_Statistics(std::size_t capacity)
|
||||
: values_(capacity), trimmed_values_(capacity),
|
||||
minimum_(capacity, true), maximum_(capacity, false) {
|
||||
if (capacity == 0)
|
||||
throw std::invalid_argument("statistics capacity must be positive");
|
||||
}
|
||||
|
||||
Sliding_Statistics::Extremum_Queue::Extremum_Queue(
|
||||
std::size_t capacity, bool minimum)
|
||||
: nodes_(capacity + 1U), minimum_(minimum) {}
|
||||
|
||||
void Sliding_Statistics::Extremum_Queue::submit(
|
||||
double sample, std::uint64_t sequence, std::size_t window) noexcept {
|
||||
const auto count = nodes_.size();
|
||||
while (head_ != tail_ &&
|
||||
nodes_[head_].sequence + window <= sequence)
|
||||
head_ = (head_ + 1U) % count;
|
||||
while (head_ != tail_) {
|
||||
const auto previous = (tail_ + count - 1U) % count;
|
||||
const bool superseded = minimum_ ? nodes_[previous].value >= sample
|
||||
: nodes_[previous].value <= sample;
|
||||
if (!superseded) break;
|
||||
tail_ = previous;
|
||||
}
|
||||
nodes_[tail_] = Node{sample, sequence};
|
||||
tail_ = (tail_ + 1U) % count;
|
||||
}
|
||||
|
||||
double Sliding_Statistics::Extremum_Queue::value() const noexcept {
|
||||
return head_ == tail_ ? 0.0 : nodes_[head_].value;
|
||||
}
|
||||
|
||||
void Sliding_Statistics::Extremum_Queue::reset() noexcept {
|
||||
head_ = 0;
|
||||
tail_ = 0;
|
||||
}
|
||||
|
||||
Sliding_Statistics::Quantile_Estimator::Quantile_Estimator(
|
||||
double probability) noexcept : probability_(probability) {}
|
||||
|
||||
void Sliding_Statistics::Quantile_Estimator::submit(double sample) noexcept {
|
||||
if (count_ < initial_.size()) {
|
||||
initial_[count_++] = sample;
|
||||
if (count_ != initial_.size()) return;
|
||||
std::ranges::sort(initial_);
|
||||
heights_ = initial_;
|
||||
positions_ = {1.0, 2.0, 3.0, 4.0, 5.0};
|
||||
desired_ = {1.0, 1.0 + 2.0 * probability_,
|
||||
1.0 + 4.0 * probability_,
|
||||
3.0 + 2.0 * probability_, 5.0};
|
||||
increments_ = {0.0, probability_ / 2.0, probability_,
|
||||
(1.0 + probability_) / 2.0, 1.0};
|
||||
return;
|
||||
}
|
||||
++count_;
|
||||
std::size_t bucket{};
|
||||
if (sample < heights_[0]) {
|
||||
heights_[0] = sample;
|
||||
} else if (sample >= heights_[4]) {
|
||||
heights_[4] = sample;
|
||||
bucket = 3;
|
||||
} else {
|
||||
while (bucket < 3 && sample >= heights_[bucket + 1]) ++bucket;
|
||||
}
|
||||
for (std::size_t index = bucket + 1; index < positions_.size(); ++index)
|
||||
positions_[index] += 1.0;
|
||||
for (std::size_t index = 0; index < desired_.size(); ++index)
|
||||
desired_[index] += increments_[index];
|
||||
for (std::size_t index = 1; index < 4; ++index) {
|
||||
const double distance = desired_[index] - positions_[index];
|
||||
const double direction = distance >= 1.0 ? 1.0 : distance <= -1.0 ? -1.0 : 0.0;
|
||||
if (direction == 0.0 ||
|
||||
(direction > 0.0 && positions_[index + 1] - positions_[index] <= 1.0) ||
|
||||
(direction < 0.0 && positions_[index - 1] - positions_[index] >= -1.0))
|
||||
continue;
|
||||
const double left = positions_[index] - positions_[index - 1];
|
||||
const double right = positions_[index + 1] - positions_[index];
|
||||
const double estimate = heights_[index] + direction / (left + right) *
|
||||
((left + direction) * (heights_[index + 1] - heights_[index]) / right +
|
||||
(right - direction) * (heights_[index] - heights_[index - 1]) / left);
|
||||
if (heights_[index - 1] < estimate && estimate < heights_[index + 1])
|
||||
heights_[index] = estimate;
|
||||
else {
|
||||
const auto neighbor = static_cast<std::size_t>(
|
||||
static_cast<std::ptrdiff_t>(index) +
|
||||
static_cast<std::ptrdiff_t>(direction));
|
||||
heights_[index] += direction *
|
||||
(heights_[neighbor] - heights_[index]) /
|
||||
(positions_[neighbor] - positions_[index]);
|
||||
}
|
||||
positions_[index] += direction;
|
||||
}
|
||||
}
|
||||
|
||||
double Sliding_Statistics::Quantile_Estimator::value() const noexcept {
|
||||
if (count_ == 0) return 0.0;
|
||||
if (count_ >= initial_.size()) return heights_[2];
|
||||
auto ordered = initial_;
|
||||
std::sort(ordered.begin(), ordered.begin() + static_cast<std::ptrdiff_t>(count_));
|
||||
const auto index = std::min(count_ - 1U, static_cast<std::size_t>(
|
||||
std::ceil(probability_ * static_cast<double>(count_))) - 1U);
|
||||
return ordered[index];
|
||||
}
|
||||
|
||||
void Sliding_Statistics::Quantile_Estimator::reset() noexcept {
|
||||
initial_ = {};
|
||||
heights_ = {};
|
||||
positions_ = {};
|
||||
desired_ = {};
|
||||
increments_ = {};
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
Statistic_State Sliding_Statistics::submit(double value) {
|
||||
if (!std::isfinite(value)) return state_;
|
||||
if (size_ == values_.size()) {
|
||||
if (!trimmed_window_initialized_) {
|
||||
const double lower = p05_.value();
|
||||
const double upper = p95_.value();
|
||||
trimmed_sum_ = 0.0;
|
||||
for (std::size_t index = 0; index < size_; ++index) {
|
||||
trimmed_values_[index] = std::clamp(values_[index], lower, upper);
|
||||
trimmed_sum_ += trimmed_values_[index];
|
||||
}
|
||||
trimmed_window_initialized_ = true;
|
||||
}
|
||||
const double discarded = values_[next_];
|
||||
sum_ -= discarded;
|
||||
squared_sum_ -= discarded * discarded;
|
||||
trimmed_sum_ -= trimmed_values_[next_];
|
||||
}
|
||||
p05_.submit(value);
|
||||
p50_.submit(value);
|
||||
p95_.submit(value);
|
||||
p99_.submit(value);
|
||||
minimum_.submit(value, sequence_, values_.size());
|
||||
maximum_.submit(value, sequence_, values_.size());
|
||||
++sequence_;
|
||||
const double trimmed = trimmed_window_initialized_
|
||||
? std::clamp(value, p05_.value(), p95_.value()) : value;
|
||||
values_[next_] = value;
|
||||
trimmed_values_[next_] = trimmed;
|
||||
sum_ += value;
|
||||
squared_sum_ += value * value;
|
||||
trimmed_sum_ += trimmed;
|
||||
next_ = (next_ + 1U) % values_.size();
|
||||
size_ = std::min(size_ + 1U, values_.size());
|
||||
const double mean = sum_ / static_cast<double>(size_);
|
||||
state_ = Statistic_State{
|
||||
size_, value, minimum_.value(), maximum_.value(), mean,
|
||||
trimmed_sum_ / static_cast<double>(size_),
|
||||
std::sqrt(std::max(0.0,
|
||||
squared_sum_ / static_cast<double>(size_) - mean * mean)),
|
||||
p50_.value(), p95_.value(), p99_.value()};
|
||||
return state_;
|
||||
}
|
||||
|
||||
const Statistic_State& Sliding_Statistics::state() const noexcept { return state_; }
|
||||
|
||||
void Sliding_Statistics::reset() noexcept {
|
||||
size_ = 0;
|
||||
next_ = 0;
|
||||
sum_ = 0.0;
|
||||
squared_sum_ = 0.0;
|
||||
trimmed_sum_ = 0.0;
|
||||
trimmed_window_initialized_ = false;
|
||||
sequence_ = 0;
|
||||
minimum_.reset();
|
||||
maximum_.reset();
|
||||
p05_.reset();
|
||||
p50_.reset();
|
||||
p95_.reset();
|
||||
p99_.reset();
|
||||
state_ = {};
|
||||
}
|
||||
|
||||
Frame_Statistics_Accumulator::Frame_Statistics_Accumulator(std::size_t capacity) {
|
||||
values_.reserve(frame_statistic_count);
|
||||
for (std::size_t index = 0; index < frame_statistic_count; ++index)
|
||||
values_.emplace_back(capacity);
|
||||
}
|
||||
|
||||
const Frame_Statistics_State& Frame_Statistics_Accumulator::submit(
|
||||
const Render_Frame& frame, Frame_Dimension dimension) {
|
||||
auto sample = frame.statistics(dimension);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto frame_identity = frame.identity();
|
||||
if (previous_completion_ != std::chrono::steady_clock::time_point{}) {
|
||||
sample.set(Frame_Statistic::frame_interval_ms,
|
||||
std::chrono::duration<double, std::milli>(
|
||||
now - previous_completion_).count());
|
||||
if (frame_identity.sequence > previous_sequence_ + 1U)
|
||||
state_.dropped_sequences += frame_identity.sequence - previous_sequence_ - 1U;
|
||||
}
|
||||
previous_completion_ = now;
|
||||
previous_sequence_ = frame_identity.sequence;
|
||||
state_.identity = frame_identity;
|
||||
state_.created_time_unix_ns = frame.created_time_unix_ns();
|
||||
for (std::size_t index = 0; index < sample.values.size(); ++index) {
|
||||
if (!sample.present.test(index)) continue;
|
||||
state_.values[index] = values_[index].submit(sample.values[index]);
|
||||
}
|
||||
return state_;
|
||||
}
|
||||
|
||||
void Frame_Statistics_Accumulator::reset() noexcept {
|
||||
for (auto& value : values_) value.reset();
|
||||
state_ = {};
|
||||
previous_completion_ = {};
|
||||
previous_sequence_ = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
#include "frame.hpp"
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace aethera {
|
||||
enum class Frame_Dimension : std::uint8_t { two_dimensional, three_dimensional };
|
||||
|
||||
enum class Frame_Statistic : std::uint8_t {
|
||||
server_completion_ms,
|
||||
scene_render_ms,
|
||||
event_dispatch_ms,
|
||||
prepare_ms,
|
||||
paint_ms,
|
||||
backend_queue_ms,
|
||||
gpu_submission_ms,
|
||||
readback_stage_ms,
|
||||
callback_ms,
|
||||
backend_apply_ms,
|
||||
backend_plan_ms,
|
||||
backend_execute_ms,
|
||||
backend_submit_ms,
|
||||
gpu_fence_wait_ms,
|
||||
gpu_render_ms,
|
||||
gpu_transition_ms,
|
||||
gpu_copy_ms,
|
||||
gpu_total_ms,
|
||||
readback_ms,
|
||||
pipeline_2d_event_ms,
|
||||
pipeline_2d_prepare_ms,
|
||||
pipeline_2d_paint_ms,
|
||||
pipeline_2d_scene_coordination_ms,
|
||||
pipeline_2d_callback_ms,
|
||||
pipeline_2d_frame_handoff_ms,
|
||||
pipeline_3d_event_ms,
|
||||
pipeline_3d_prepare_ms,
|
||||
pipeline_3d_submit_graph_ms,
|
||||
pipeline_3d_scene_coordination_ms,
|
||||
pipeline_3d_prepare_queue_ms,
|
||||
pipeline_3d_backend_apply_ms,
|
||||
pipeline_3d_backend_plan_ms,
|
||||
pipeline_3d_backend_execute_ms,
|
||||
pipeline_3d_backend_commands_ms,
|
||||
pipeline_3d_backend_queue_ms,
|
||||
pipeline_3d_backend_submit_ms,
|
||||
pipeline_3d_submit_handoff_ms,
|
||||
pipeline_3d_gpu_render_ms,
|
||||
pipeline_3d_gpu_transition_ms,
|
||||
pipeline_3d_gpu_copy_ms,
|
||||
pipeline_3d_gpu_sync_ms,
|
||||
pipeline_3d_readback_ms,
|
||||
pipeline_3d_callback_ms,
|
||||
pipeline_3d_completion_handoff_ms,
|
||||
frame_interval_ms,
|
||||
count
|
||||
};
|
||||
|
||||
inline constexpr std::size_t frame_statistic_count =
|
||||
static_cast<std::size_t>(Frame_Statistic::count);
|
||||
|
||||
struct Frame_Statistics_Sample {
|
||||
std::array<double, frame_statistic_count> values{};
|
||||
std::bitset<frame_statistic_count> present{};
|
||||
|
||||
void set(Frame_Statistic statistic, double value) noexcept;
|
||||
};
|
||||
|
||||
struct Statistic_State {
|
||||
std::size_t count{};
|
||||
double latest{};
|
||||
double minimum{};
|
||||
double maximum{};
|
||||
double average{};
|
||||
double trimmed_average{};
|
||||
double variability{};
|
||||
double p50{};
|
||||
double p95{};
|
||||
double p99{};
|
||||
bool operator==(const Statistic_State&) const = default;
|
||||
};
|
||||
|
||||
class Sliding_Statistics final {
|
||||
public:
|
||||
Sliding_Statistics();
|
||||
explicit Sliding_Statistics(std::size_t capacity);
|
||||
[[nodiscard]] Statistic_State submit(double value);
|
||||
[[nodiscard]] const Statistic_State& state() const noexcept;
|
||||
void reset() noexcept;
|
||||
|
||||
private:
|
||||
/* P² 只维护五个标记点,分位数是从 reset 起的在线估计,不保存样本。 */
|
||||
class Quantile_Estimator final {
|
||||
public:
|
||||
explicit Quantile_Estimator(double probability) noexcept;
|
||||
void submit(double value) noexcept;
|
||||
[[nodiscard]] double value() const noexcept;
|
||||
void reset() noexcept;
|
||||
private:
|
||||
double probability_{};
|
||||
std::array<double, 5> initial_{};
|
||||
std::array<double, 5> heights_{};
|
||||
std::array<double, 5> positions_{};
|
||||
std::array<double, 5> desired_{};
|
||||
std::array<double, 5> increments_{};
|
||||
std::size_t count_{};
|
||||
};
|
||||
|
||||
/* 预分配单调队列,O(1) 摊还维护精确滑动窗口极值。 */
|
||||
class Extremum_Queue final {
|
||||
public:
|
||||
Extremum_Queue(std::size_t capacity, bool minimum);
|
||||
void submit(double value, std::uint64_t sequence,
|
||||
std::size_t window) noexcept;
|
||||
[[nodiscard]] double value() const noexcept;
|
||||
void reset() noexcept;
|
||||
private:
|
||||
struct Node { double value{}; std::uint64_t sequence{}; };
|
||||
std::vector<Node> nodes_;
|
||||
std::size_t head_{};
|
||||
std::size_t tail_{};
|
||||
bool minimum_{};
|
||||
};
|
||||
|
||||
/* 原始值和截尾值只为精确滑动均值/方差服务,构造后不再分配。 */
|
||||
std::vector<double> values_;
|
||||
std::vector<double> trimmed_values_;
|
||||
std::size_t size_{};
|
||||
std::size_t next_{};
|
||||
double sum_{};
|
||||
double squared_sum_{};
|
||||
double trimmed_sum_{};
|
||||
bool trimmed_window_initialized_{};
|
||||
std::uint64_t sequence_{};
|
||||
Extremum_Queue minimum_;
|
||||
Extremum_Queue maximum_;
|
||||
Quantile_Estimator p05_{0.05};
|
||||
Quantile_Estimator p50_{0.50};
|
||||
Quantile_Estimator p95_{0.95};
|
||||
Quantile_Estimator p99_{0.99};
|
||||
Statistic_State state_{};
|
||||
};
|
||||
|
||||
struct Frame_Statistics_State {
|
||||
/* 可直接通过 Scene State 双缓冲发布的定长结果;不含统计器内部样本。 */
|
||||
std::array<Statistic_State, frame_statistic_count> values{};
|
||||
Frame_Identity identity{};
|
||||
std::uint64_t created_time_unix_ns{};
|
||||
std::uint64_t dropped_sequences{};
|
||||
bool operator==(const Frame_Statistics_State&) const = default;
|
||||
};
|
||||
|
||||
class Frame_Statistics_Accumulator final {
|
||||
public:
|
||||
explicit Frame_Statistics_Accumulator(std::size_t capacity = 600);
|
||||
[[nodiscard]] const Frame_Statistics_State& submit(
|
||||
const Render_Frame& frame, Frame_Dimension dimension);
|
||||
void reset() noexcept;
|
||||
private:
|
||||
std::vector<Sliding_Statistics> values_;
|
||||
Frame_Statistics_State state_{};
|
||||
std::chrono::steady_clock::time_point previous_completion_{};
|
||||
std::uint64_t previous_sequence_{};
|
||||
};
|
||||
}
|
||||
@@ -202,14 +202,13 @@ private:
|
||||
std::atomic_bool state_callback_enabled{};
|
||||
void create_executor(std::size_t workers, std::shared_ptr<tf::WorkerInterface> worker_interface) {
|
||||
executor = std::make_unique<tf::Executor>(workers, std::move(worker_interface));
|
||||
observer = executor->make_observer<Task_Observer>();
|
||||
observer.reset();
|
||||
state = {};
|
||||
}
|
||||
void ensure_executor() {
|
||||
std::lock_guard guard(state_mutex);
|
||||
if (executor) return;
|
||||
executor = std::make_unique<tf::Executor>();
|
||||
observer = executor->make_observer<Task_Observer>();
|
||||
}
|
||||
void publish_state() {
|
||||
if (!state_callback_enabled.load(std::memory_order_acquire)) return;
|
||||
@@ -283,7 +282,9 @@ public:
|
||||
return memory;
|
||||
}
|
||||
void set_state_callback(std::function<void(const Task_Runtime_State&)> callback) {
|
||||
ensure_executor();
|
||||
std::lock_guard guard(state_mutex);
|
||||
if (!observer) observer = executor->make_observer<Task_Observer>();
|
||||
state_callbacks.template set<Task_Runtime_State_Tag>(std::move(callback));
|
||||
state_callback_enabled.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user