展示优化

This commit is contained in:
2026-08-03 11:22:22 +08:00
parent 44cf49c0e2
commit 86c01e4cc5
15 changed files with 384 additions and 285 deletions
+5 -3
View File
@@ -79,7 +79,8 @@ struct Refresh_Control_Snapshot {
std::uint64_t target_period_ns{};
std::uint64_t effective_min_render_interval_ns{};
std::uint64_t next_render_time_ns{};
std::uint64_t admission_deadline_wait_ns{};
std::uint64_t admission_wait_elapsed_ns{};
std::uint64_t admission_wait_remaining_ns{};
std::uint64_t executor_queue_wait_ns{};
std::uint64_t present_wait_ns{};
std::uint64_t consumer_sample_accepted_count{};
@@ -102,7 +103,7 @@ struct Refresh_Control_Snapshot {
std::uint64_t mode_enter_count{};
std::uint64_t mode_exit_count{};
std::uint64_t update_posted_count{};
std::uint64_t update_coalesced_count{};
std::uint64_t duplicate_present_request_suppressed_count{};
std::uint64_t update_ack_count{};
std::uint64_t update_repost_count{};
std::uint64_t paint_without_posted_update_count{};
@@ -173,7 +174,8 @@ struct Frame_Lifecycle_Record {
std::uint64_t paint_end_ns{};
std::uint64_t presented_frame_id{};
std::uint64_t transition_32_ns{};
std::uint64_t render_queue_wait_ns{};
std::uint64_t render_slot_idle_wait_ns{};
std::uint64_t executor_queue_wait_ns{};
std::uint64_t ready_queue_wait_ns{};
std::uint64_t present_queue_wait_ns{};
std::uint64_t paint_return_wait_ns{};
+15 -13
View File
@@ -25,16 +25,18 @@ struct Render_Executor_Snapshot {
std::size_t idle_workers{};
std::size_t active_frame_jobs{};
std::size_t queued_frame_jobs{};
std::uint64_t submitted_jobs{};
std::uint64_t submitted_topologies{};
std::uint64_t started_topologies{};
std::uint64_t completed_topologies{};
std::uint64_t started_task_nodes{};
std::uint64_t completed_task_nodes{};
std::uint64_t rejected_frame_jobs{};
std::uint64_t dropped_frame_attempts{};
std::uint64_t peak_concurrency{};
std::uint64_t average_queue_wait_ns{};
std::uint64_t max_queue_wait_ns{};
std::uint64_t average_task_duration_ns{};
std::uint64_t max_task_duration_ns{};
std::uint64_t average_topology_queue_wait_ns{};
std::uint64_t max_topology_queue_wait_ns{};
std::uint64_t average_task_node_duration_ns{};
std::uint64_t max_task_node_duration_ns{};
std::uint64_t curve_tasks{};
std::uint64_t waterfall_tasks{};
std::uint64_t primitive_tasks{};
@@ -44,13 +46,13 @@ struct Render_Executor_Snapshot {
struct Frame_Worker_Stats {
Render_Executor_Snapshot executor_at_submit;
Render_Executor_Snapshot executor_at_finish;
std::uint32_t task_count{};
std::uint32_t peak_parallelism{};
std::uint64_t queue_wait_total_ns{};
std::uint64_t queue_wait_max_ns{};
std::uint64_t worker_run_total_ns{};
std::uint64_t parallel_stage_wall_ns{};
std::uint64_t rejected_task_count{};
std::uint32_t business_task_count{};
std::uint32_t business_task_peak_parallelism{};
std::uint64_t topology_queue_wait_total_ns{};
std::uint64_t topology_queue_wait_max_ns{};
std::uint64_t business_task_run_total_ns{};
std::uint64_t topology_run_total_ns{};
std::uint64_t rejected_topology_count{};
};
enum class Refresh_Limit_Source : std::uint8_t {
Unknown,
@@ -106,7 +108,7 @@ struct Consumer_Backlog_Epoch {
};
struct Low_Latency_Performance_Diagnostics {
std::uint64_t update_posted_count{};
std::uint64_t update_coalesced_count{};
std::uint64_t duplicate_present_request_suppressed_count{};
std::uint64_t update_ack_count{};
std::uint64_t update_repost_count{};
std::uint64_t consumer_sample_accepted_count{};
+67 -59
View File
@@ -11,24 +11,24 @@
namespace renderive {
struct Render_Executor_Metrics;
struct Frame_Task_Counters {
std::atomic_uint32_t task_count{0};
std::atomic_uint32_t active_count{0};
std::atomic_uint32_t peak_parallelism{0};
std::atomic_uint64_t task_duration_total_ns{0};
struct Frame_Business_Task_Counters {
std::atomic_uint32_t business_task_count{0};
std::atomic_uint32_t active_business_task_count{0};
std::atomic_uint32_t business_task_peak_parallelism{0};
std::atomic_uint64_t business_task_duration_total_ns{0};
};
struct Taskflow_Graph_Impl {
Taskflow_Graph_Impl(
tf::Taskflow& taskflow,
std::shared_ptr<Render_Executor_Metrics> metrics,
std::shared_ptr<Frame_Task_Counters> frame_counters)
std::shared_ptr<Frame_Business_Task_Counters> frame_counters)
: taskflow(taskflow),
metrics(std::move(metrics)),
frame_counters(std::move(frame_counters)) {}
tf::Taskflow& taskflow;
std::shared_ptr<Render_Executor_Metrics> metrics;
std::shared_ptr<Frame_Task_Counters> frame_counters;
std::shared_ptr<Frame_Business_Task_Counters> frame_counters;
std::vector<tf::Task> tasks;
};
@@ -36,13 +36,13 @@ struct Taskflow_Subflow_Impl {
Taskflow_Subflow_Impl(
tf::Subflow& subflow,
std::shared_ptr<Render_Executor_Metrics> metrics,
std::shared_ptr<Frame_Task_Counters> frame_counters)
std::shared_ptr<Frame_Business_Task_Counters> frame_counters)
: subflow(subflow),
metrics(std::move(metrics)),
frame_counters(std::move(frame_counters)) {}
tf::Subflow& subflow;
std::shared_ptr<Render_Executor_Metrics> metrics;
std::shared_ptr<Frame_Task_Counters> frame_counters;
std::shared_ptr<Frame_Business_Task_Counters> frame_counters;
std::vector<tf::Task> tasks;
};
@@ -51,16 +51,18 @@ struct Render_Executor_Metrics {
std::atomic<std::size_t> active_workers{0};
std::atomic<std::size_t> active_frame_jobs{0};
std::atomic<std::size_t> queued_frame_jobs{0};
std::atomic<std::uint64_t> submitted_tasks{0};
std::atomic<std::uint64_t> started_tasks{0};
std::atomic<std::uint64_t> completed_tasks{0};
std::atomic<std::uint64_t> submitted_topologies{0};
std::atomic<std::uint64_t> started_topologies{0};
std::atomic<std::uint64_t> completed_topologies{0};
std::atomic<std::uint64_t> started_task_nodes{0};
std::atomic<std::uint64_t> completed_task_nodes{0};
std::atomic<std::uint64_t> rejected_frame_jobs{0};
std::atomic<std::uint64_t> dropped_frame_attempts{0};
std::atomic<std::uint64_t> peak_concurrency{0};
std::atomic<std::uint64_t> queue_wait_total_ns{0};
std::atomic<std::uint64_t> queue_wait_max_ns{0};
std::atomic<std::uint64_t> task_duration_total_ns{0};
std::atomic<std::uint64_t> task_duration_max_ns{0};
std::atomic<std::uint64_t> topology_queue_wait_total_ns{0};
std::atomic<std::uint64_t> topology_queue_wait_max_ns{0};
std::atomic<std::uint64_t> task_node_duration_total_ns{0};
std::atomic<std::uint64_t> task_node_duration_max_ns{0};
std::atomic<std::uint64_t> curve_tasks{0};
std::atomic<std::uint64_t> waterfall_tasks{0};
std::atomic<std::uint64_t> primitive_tasks{0};
@@ -94,30 +96,30 @@ void add_kind_count(Render_Executor_Metrics& metrics, Render_Task_Kind kind) {
}
}
void begin_business_task(Render_Executor_Metrics* metrics, Frame_Task_Counters* counters, Render_Task_Kind kind) {
void begin_business_task(Render_Executor_Metrics* metrics, Frame_Business_Task_Counters* counters, Render_Task_Kind kind) {
if (metrics)
add_kind_count(*metrics, kind);
if (!counters)
return;
counters->task_count.fetch_add(1, std::memory_order_relaxed);
std::uint32_t active = counters->active_count.fetch_add(1, std::memory_order_acq_rel) + 1;
std::uint32_t peak = counters->peak_parallelism.load(std::memory_order_acquire);
while (peak < active && !counters->peak_parallelism.compare_exchange_weak(peak, active, std::memory_order_acq_rel, std::memory_order_acquire)) {}
counters->business_task_count.fetch_add(1, std::memory_order_relaxed);
std::uint32_t active = counters->active_business_task_count.fetch_add(1, std::memory_order_acq_rel) + 1;
std::uint32_t peak = counters->business_task_peak_parallelism.load(std::memory_order_acquire);
while (peak < active && !counters->business_task_peak_parallelism.compare_exchange_weak(peak, active, std::memory_order_acq_rel, std::memory_order_acquire)) {}
}
void end_business_task(Frame_Task_Counters* counters, std::uint64_t begin_ns) {
void end_business_task(Frame_Business_Task_Counters* counters, std::uint64_t begin_ns) {
if (!counters)
return;
std::uint64_t end_ns = steady_now_ns();
counters->task_duration_total_ns.fetch_add(end_ns > begin_ns ? end_ns - begin_ns : 0, std::memory_order_relaxed);
counters->active_count.fetch_sub(1, std::memory_order_acq_rel);
counters->business_task_duration_total_ns.fetch_add(end_ns > begin_ns ? end_ns - begin_ns : 0, std::memory_order_relaxed);
counters->active_business_task_count.fetch_sub(1, std::memory_order_acq_rel);
}
Task wrap_business_task(
Render_Task_Kind kind,
Task work,
std::shared_ptr<Render_Executor_Metrics> metrics,
std::shared_ptr<Frame_Task_Counters> counters) {
std::shared_ptr<Frame_Business_Task_Counters> counters) {
return Task([kind, work = std::move(work), metrics = std::move(metrics), counters = std::move(counters)]() mutable {
std::uint64_t begin_ns = steady_now_ns();
begin_business_task(metrics.get(), counters.get(), kind);
@@ -221,7 +223,7 @@ public:
if (worker_id < worker_entry_ns.size())
worker_entry_ns[worker_id] = steady_now_ns();
std::size_t active = metrics->active_workers.fetch_add(1, std::memory_order_acq_rel) + 1;
metrics->started_tasks.fetch_add(1, std::memory_order_relaxed);
metrics->started_task_nodes.fetch_add(1, std::memory_order_relaxed);
update_peak(metrics->peak_concurrency, static_cast<std::uint64_t>(active));
}
@@ -233,10 +235,10 @@ public:
std::uint64_t begin_ns = worker_entry_ns[worker_id];
std::uint64_t end_ns = steady_now_ns();
std::uint64_t duration_ns = end_ns > begin_ns ? end_ns - begin_ns : 0;
metrics->task_duration_total_ns.fetch_add(duration_ns, std::memory_order_relaxed);
update_peak(metrics->task_duration_max_ns, duration_ns);
metrics->task_node_duration_total_ns.fetch_add(duration_ns, std::memory_order_relaxed);
update_peak(metrics->task_node_duration_max_ns, duration_ns);
}
metrics->completed_tasks.fetch_add(1, std::memory_order_relaxed);
metrics->completed_task_nodes.fetch_add(1, std::memory_order_relaxed);
metrics->active_workers.fetch_sub(1, std::memory_order_acq_rel);
}
@@ -246,7 +248,7 @@ private:
};
class Render_Executor_Private {
struct Task_Run_Record {
struct Topology_Run_Record {
std::uint64_t begin_ns{};
std::uint64_t queue_wait_ns{};
};
@@ -282,11 +284,13 @@ public:
}
Render_Executor_Snapshot snapshot() const {
std::uint64_t submitted = metrics->submitted_tasks.load(std::memory_order_acquire);
std::uint64_t completed = metrics->completed_tasks.load(std::memory_order_acquire);
std::uint64_t started = metrics->started_tasks.load(std::memory_order_acquire);
std::uint64_t queue_total = metrics->queue_wait_total_ns.load(std::memory_order_acquire);
std::uint64_t duration_total = metrics->task_duration_total_ns.load(std::memory_order_acquire);
std::uint64_t submitted_topologies = metrics->submitted_topologies.load(std::memory_order_acquire);
std::uint64_t started_topologies = metrics->started_topologies.load(std::memory_order_acquire);
std::uint64_t completed_topologies = metrics->completed_topologies.load(std::memory_order_acquire);
std::uint64_t completed_task_nodes = metrics->completed_task_nodes.load(std::memory_order_acquire);
std::uint64_t started_task_nodes = metrics->started_task_nodes.load(std::memory_order_acquire);
std::uint64_t queue_total = metrics->topology_queue_wait_total_ns.load(std::memory_order_acquire);
std::uint64_t duration_total = metrics->task_node_duration_total_ns.load(std::memory_order_acquire);
std::size_t active = metrics->active_workers.load(std::memory_order_acquire);
std::size_t workers = metrics->worker_count;
return {
@@ -295,16 +299,18 @@ public:
workers > active ? workers - active : 0,
metrics->active_frame_jobs.load(std::memory_order_acquire),
metrics->queued_frame_jobs.load(std::memory_order_acquire),
submitted,
started,
completed,
submitted_topologies,
started_topologies,
completed_topologies,
started_task_nodes,
completed_task_nodes,
metrics->rejected_frame_jobs.load(std::memory_order_acquire),
metrics->dropped_frame_attempts.load(std::memory_order_acquire),
metrics->peak_concurrency.load(std::memory_order_acquire),
submitted ? queue_total / submitted : 0,
metrics->queue_wait_max_ns.load(std::memory_order_acquire),
completed ? duration_total / completed : 0,
metrics->task_duration_max_ns.load(std::memory_order_acquire),
started_topologies ? queue_total / started_topologies : 0,
metrics->topology_queue_wait_max_ns.load(std::memory_order_acquire),
completed_task_nodes ? duration_total / completed_task_nodes : 0,
metrics->task_node_duration_max_ns.load(std::memory_order_acquire),
metrics->curve_tasks.load(std::memory_order_acquire),
metrics->waterfall_tasks.load(std::memory_order_acquire),
metrics->primitive_tasks.load(std::memory_order_acquire),
@@ -319,7 +325,7 @@ public:
private:
void record_submission(Render_Executor_Task& task) {
metrics->submitted_tasks.fetch_add(1, std::memory_order_relaxed);
metrics->submitted_topologies.fetch_add(1, std::memory_order_relaxed);
if (task.frame_job)
metrics->queued_frame_jobs.fetch_add(1, std::memory_order_relaxed);
if (task.frame_stat)
@@ -329,8 +335,8 @@ private:
void submit_to_executor(Render_Executor_Task task, std::uint64_t enqueue_ns) {
auto task_ptr = std::make_shared<Render_Executor_Task>(std::move(task));
auto completion = std::make_shared<Task>(std::move(task_ptr->completion));
auto run_record = std::make_shared<Task_Run_Record>();
auto frame_counters = std::make_shared<Frame_Task_Counters>();
auto run_record = std::make_shared<Topology_Run_Record>();
auto frame_counters = std::make_shared<Frame_Business_Task_Counters>();
auto topology = std::make_shared<tf::Taskflow>();
auto entry = topology->emplace([this, enqueue_ns, task_ptr, run_record]() mutable {
Render_Executor_Task& task = *task_ptr;
@@ -338,8 +344,9 @@ private:
run_record->begin_ns = begin_ns;
std::uint64_t queue_wait_ns = begin_ns > enqueue_ns ? begin_ns - enqueue_ns : 0;
run_record->queue_wait_ns = queue_wait_ns;
metrics->queue_wait_total_ns.fetch_add(queue_wait_ns, std::memory_order_relaxed);
update_peak(metrics->queue_wait_max_ns, queue_wait_ns);
metrics->started_topologies.fetch_add(1, std::memory_order_relaxed);
metrics->topology_queue_wait_total_ns.fetch_add(queue_wait_ns, std::memory_order_relaxed);
update_peak(metrics->topology_queue_wait_max_ns, queue_wait_ns);
if (task.frame_job) {
metrics->queued_frame_jobs.fetch_sub(1, std::memory_order_relaxed);
metrics->active_frame_jobs.fetch_add(1, std::memory_order_relaxed);
@@ -353,19 +360,19 @@ private:
std::uint64_t run_ns = end_ns > begin_ns ? end_ns - begin_ns : 0;
if (task.frame_stat) {
Frame_Worker_Stats& stat = *task.frame_stat;
std::uint32_t task_count = frame_counters ? frame_counters->task_count.load(std::memory_order_acquire) : 0;
std::uint32_t peak_parallelism = frame_counters ? frame_counters->peak_parallelism.load(std::memory_order_acquire) : 0;
std::uint64_t task_duration_total_ns = frame_counters ? frame_counters->task_duration_total_ns.load(std::memory_order_acquire) : 0;
stat.task_count += task_count;
stat.peak_parallelism = std::max(stat.peak_parallelism, peak_parallelism);
stat.queue_wait_total_ns += queue_wait_ns;
stat.queue_wait_max_ns = std::max(stat.queue_wait_max_ns, queue_wait_ns);
stat.worker_run_total_ns += task_duration_total_ns;
stat.parallel_stage_wall_ns += run_ns;
stat.executor_at_finish = snapshot();
std::uint32_t business_task_count = frame_counters ? frame_counters->business_task_count.load(std::memory_order_acquire) : 0;
std::uint32_t business_task_peak_parallelism = frame_counters ? frame_counters->business_task_peak_parallelism.load(std::memory_order_acquire) : 0;
std::uint64_t business_task_duration_total_ns = frame_counters ? frame_counters->business_task_duration_total_ns.load(std::memory_order_acquire) : 0;
stat.business_task_count += business_task_count;
stat.business_task_peak_parallelism = std::max(stat.business_task_peak_parallelism, business_task_peak_parallelism);
stat.topology_queue_wait_total_ns += queue_wait_ns;
stat.topology_queue_wait_max_ns = std::max(stat.topology_queue_wait_max_ns, queue_wait_ns);
stat.business_task_run_total_ns += business_task_duration_total_ns;
stat.topology_run_total_ns += run_ns;
}
if (task.frame_job)
metrics->active_frame_jobs.fetch_sub(1, std::memory_order_relaxed);
metrics->completed_topologies.fetch_add(1, std::memory_order_relaxed);
});
if (task_ptr->build_graph) {
Taskflow_Graph_Impl graph_impl(*topology, metrics, frame_counters);
@@ -384,7 +391,8 @@ private:
work.precede(exit);
}
executor.run(*topology, [this, topology, completion, task_ptr]() mutable {
(void)this;
if (task_ptr->frame_stat)
task_ptr->frame_stat->executor_at_finish = snapshot();
if (completion && *completion)
(*completion)();
});
@@ -396,7 +404,7 @@ private:
else
metrics->dropped_frame_attempts.fetch_add(1, std::memory_order_relaxed);
if (task.frame_stat)
task.frame_stat->rejected_task_count++;
task.frame_stat->rejected_topology_count++;
}
std::shared_ptr<Render_Executor_Metrics> metrics;
@@ -161,22 +161,24 @@ void Low_Latency_Feedback_Controller::on_input_changed(std::uint64_t now_ns, std
void Low_Latency_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) {
exit_producer_starvation(now_ns, "render_started");
if (last_render_admission_ns && now_ns > last_render_admission_ns)
producer_interval.update(static_cast<double>(now_ns - last_render_admission_ns) / 1000000.0);
last_render_admission_ns = now_ns;
if (last_render_start_ns && now_ns > last_render_start_ns)
producer_interval.update(static_cast<double>(now_ns - last_render_start_ns) / 1000000.0);
last_render_start_ns = now_ns;
last_render_begin_ns = now_ns;
frame.render_begin_ns = now_ns;
frame.render_queue_wait_ns = now_ns > frame.render_role_enter_ns ? now_ns - frame.render_role_enter_ns : 0;
if (!frame.render_begin_ns)
frame.render_begin_ns = now_ns;
refresh_control_snapshot(now_ns);
write_state(frame);
}
void Low_Latency_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record& frame) {
if (frame.render_begin_ns && frame.render_begin_ns != last_render_start_ns)
on_render_started(frame, frame.render_begin_ns);
exit_producer_starvation(frame.render_end_ns, "render_completed");
std::uint64_t render_duration_ns = frame.render_end_ns > frame.render_begin_ns ? frame.render_end_ns - frame.render_begin_ns : 0;
last_render_begin_ns = frame.render_begin_ns;
render_service_period.update(static_cast<double>(render_duration_ns) / 1000000.0);
executor_queue_period.update_wait(static_cast<double>(frame.render_queue_wait_ns) / 1000000.0);
executor_queue_period.update_wait(static_cast<double>(frame.executor_queue_wait_ns) / 1000000.0);
if (consumer_epoch_active)
++consumer_epoch.rendered_frames;
refresh_control_snapshot(frame.render_end_ns);
@@ -306,21 +308,42 @@ void Low_Latency_Feedback_Controller::observe_runtime(std::uint64_t now_ns, bool
refresh_control_snapshot(now_ns);
}
void Low_Latency_Feedback_Controller::note_admission_deadline_wait(std::uint64_t wait_ns, std::uint64_t now_ns) {
admission_deadline_wait_ns = wait_ns;
void Low_Latency_Feedback_Controller::note_admission_wait(std::uint64_t remaining_ns, std::uint64_t now_ns) {
if (!admission_wait_begin_ns)
admission_wait_begin_ns = now_ns;
admission_wait_elapsed_ns = now_ns > admission_wait_begin_ns ? now_ns - admission_wait_begin_ns : 0;
admission_wait_remaining_ns = remaining_ns;
last_decision = Low_Latency_Feedback_Decision::Hold_User_Period;
decision_reason = "admission_deadline_wait";
decision_reason = "admission_wait";
decision_timestamp_ns = now_ns;
refresh_control_snapshot(now_ns);
}
void Low_Latency_Feedback_Controller::note_render_admitted(std::uint64_t now_ns) {
if (!admission_wait_begin_ns && !admission_wait_elapsed_ns && !admission_wait_remaining_ns)
return;
admission_wait_elapsed_ns = admission_wait_begin_ns && now_ns > admission_wait_begin_ns ? now_ns - admission_wait_begin_ns : 0;
admission_wait_remaining_ns = 0;
admission_wait_begin_ns = 0;
refresh_control_snapshot(now_ns);
}
void Low_Latency_Feedback_Controller::cancel_admission_wait(std::uint64_t now_ns) {
if (!admission_wait_begin_ns && !admission_wait_elapsed_ns && !admission_wait_remaining_ns)
return;
admission_wait_begin_ns = 0;
admission_wait_elapsed_ns = 0;
admission_wait_remaining_ns = 0;
refresh_control_snapshot(now_ns);
}
void Low_Latency_Feedback_Controller::note_update_posted(std::uint64_t now_ns) {
++performance_counters.update_posted_count;
refresh_control_snapshot(now_ns);
}
void Low_Latency_Feedback_Controller::note_update_coalesced(std::uint64_t now_ns) {
++performance_counters.update_coalesced_count;
void Low_Latency_Feedback_Controller::note_duplicate_present_request_suppressed(std::uint64_t now_ns) {
++performance_counters.duplicate_present_request_suppressed_count;
refresh_control_snapshot(now_ns);
}
@@ -714,7 +737,8 @@ void Low_Latency_Feedback_Controller::refresh_control_snapshot(std::uint64_t now
state_value.target_period_ns = target_period;
state_value.effective_min_render_interval_ns = effective_interval;
state_value.next_render_time_ns = resolved_next_render_time;
state_value.admission_deadline_wait_ns = admission_deadline_wait_ns;
state_value.admission_wait_elapsed_ns = admission_wait_elapsed_ns;
state_value.admission_wait_remaining_ns = admission_wait_remaining_ns;
state_value.executor_queue_wait_ns = executor_queue_period.srtt_ns();
state_value.present_wait_ns = raw_display_interval.srtt_ns();
state_value.consumer_sample_accepted_count = consumer_sample_accepted_count;
@@ -737,7 +761,7 @@ void Low_Latency_Feedback_Controller::refresh_control_snapshot(std::uint64_t now
state_value.mode_enter_count = mode_enter_count;
state_value.mode_exit_count = mode_exit_count;
state_value.update_posted_count = performance_counters.update_posted_count;
state_value.update_coalesced_count = performance_counters.update_coalesced_count;
state_value.duplicate_present_request_suppressed_count = performance_counters.duplicate_present_request_suppressed_count;
state_value.update_ack_count = performance_counters.update_ack_count;
state_value.update_repost_count = performance_counters.update_repost_count;
state_value.paint_without_posted_update_count = performance_counters.paint_without_posted_update_count;
@@ -39,9 +39,11 @@ public:
void on_frame_underrun();
void on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome);
void observe_runtime(std::uint64_t now_ns, bool backlog_pressure, bool dirty, bool render_active);
void note_admission_deadline_wait(std::uint64_t wait_ns, std::uint64_t now_ns);
void note_admission_wait(std::uint64_t remaining_ns, std::uint64_t now_ns);
void note_render_admitted(std::uint64_t now_ns);
void cancel_admission_wait(std::uint64_t now_ns);
void note_update_posted(std::uint64_t now_ns);
void note_update_coalesced(std::uint64_t now_ns);
void note_duplicate_present_request_suppressed(std::uint64_t now_ns);
void note_update_ack(std::uint64_t now_ns);
void note_update_repost(std::uint64_t now_ns);
void note_paint_without_posted_update(std::uint64_t now_ns);
@@ -93,7 +95,7 @@ private:
std::uint64_t latest_input_version{};
std::uint64_t last_input_change_ns{};
std::uint64_t last_render_begin_ns{};
std::uint64_t last_render_admission_ns{};
std::uint64_t last_render_start_ns{};
std::uint64_t last_consume_sample_ns{};
std::uint64_t last_consumed_frame_id{};
std::uint64_t latest_event_ns{};
@@ -130,7 +132,9 @@ private:
std::string decision_reason = "initial";
std::uint64_t decision_timestamp_ns{};
std::uint64_t effective_interval_current_ns{};
std::uint64_t admission_deadline_wait_ns{};
std::uint64_t admission_wait_begin_ns{};
std::uint64_t admission_wait_elapsed_ns{};
std::uint64_t admission_wait_remaining_ns{};
Low_Latency_Performance_Diagnostics performance_counters;
std::uint64_t presented_frame_count{};
std::uint64_t superseded_frame_count{};
@@ -45,8 +45,8 @@ void record_pipeline_publish_result(Low_Latency_Feedback_Controller& feedback, c
feedback.note_middle_superseded(now_ns);
if (result.update_posted)
feedback.note_update_posted(now_ns);
if (result.update_coalesced)
feedback.note_update_coalesced(now_ns);
if (result.duplicate_present_request_suppressed)
feedback.note_duplicate_present_request_suppressed(now_ns);
}
} // namespace
@@ -113,8 +113,8 @@ struct Low_Latency_Frame_Flow::Private {
record_feedback_event("MiddleSuperseded", now_ns, result.superseded_frame_record.get(), nullptr, nullptr, true);
if (result.update_posted)
record_feedback_event("UpdatePosted", now_ns);
if (result.update_coalesced)
record_feedback_event("UpdateCoalesced", now_ns);
if (result.duplicate_present_request_suppressed)
record_feedback_event("DuplicatePresentRequestSuppressed", now_ns);
}
void evaluate(std::uint64_t now_ns) {
@@ -123,6 +123,7 @@ struct Low_Latency_Frame_Flow::Private {
feedback.set_max_render_fps(max_render_fps.load(std::memory_order_acquire));
Frame_Flow_Runtime_Snapshot runtime = host->runtime_snapshot(now_ns);
if (!active.load(std::memory_order_acquire) || runtime.destroying) {
feedback.cancel_admission_wait(runtime.now_ns);
host->cancel_render_deadline();
return;
}
@@ -130,32 +131,37 @@ struct Low_Latency_Frame_Flow::Private {
Refresh_Control_Snapshot before_observe = feedback.state();
feedback.observe_runtime(runtime.now_ns, runtime.middle_has_new_frame || update_posted || runtime.paint_active, runtime.dirty, runtime.render_job_active);
record_state_transition_events(before_observe, runtime.now_ns, nullptr, &runtime);
if (runtime.middle_has_new_frame && !runtime.paint_active) {
if (!update_posted) {
Refresh_Control_Snapshot before_repost = feedback.state();
feedback.note_update_repost(runtime.now_ns);
record_feedback_event("UpdateRepost", runtime.now_ns, nullptr, &runtime, &before_repost);
record_state_transition_events(before_repost, runtime.now_ns, nullptr, &runtime);
}
if (runtime.middle_has_new_frame && !runtime.paint_active && !update_posted) {
Refresh_Control_Snapshot before_repost = feedback.state();
feedback.note_update_repost(runtime.now_ns);
record_feedback_event("UpdateRepost", runtime.now_ns, nullptr, &runtime, &before_repost);
record_state_transition_events(before_repost, runtime.now_ns, nullptr, &runtime);
host->request_paint_from_middle();
}
if (!runtime.dirty || runtime.render_job_active) {
if (!runtime.dirty)
if (!runtime.dirty) {
feedback.cancel_admission_wait(runtime.now_ns);
host->cancel_render_deadline();
}
return;
}
std::uint64_t next_ns = feedback.next_render_time_ns();
if (next_ns && !feedback.rate_limit_ready(runtime.now_ns)) {
Refresh_Control_Snapshot before_wait = feedback.state();
feedback.note_admission_deadline_wait(next_ns > runtime.now_ns ? next_ns - runtime.now_ns : 0, runtime.now_ns);
feedback.note_admission_wait(next_ns > runtime.now_ns ? next_ns - runtime.now_ns : 0, runtime.now_ns);
record_feedback_event("AdmissionDeadlineWait", runtime.now_ns, nullptr, &runtime, &before_wait);
record_state_transition_events(before_wait, runtime.now_ns, nullptr, &runtime);
host->schedule_render_deadline(next_ns);
return;
}
record_feedback_event("RenderAdmitted", runtime.now_ns, nullptr, &runtime);
if (host->try_render_once())
record_feedback_event("RenderStarted", runtime.now_ns, nullptr, &runtime);
Refresh_Control_Snapshot before_admit = feedback.state();
if (!host->try_render_once()) {
feedback.cancel_admission_wait(runtime.now_ns);
return;
}
feedback.note_render_admitted(runtime.now_ns);
record_feedback_event("RenderAdmitted", runtime.now_ns, nullptr, &runtime, &before_admit);
record_state_transition_events(before_admit, runtime.now_ns, nullptr, &runtime);
}
};
@@ -352,10 +358,16 @@ void Low_Latency_Frame_Flow::notify_viewport_changed(Size, std::uint64_t now_ns)
}
void Low_Latency_Frame_Flow::notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) {
Refresh_Control_Snapshot before = d->feedback.state();
if (frame.render_begin_ns) {
Refresh_Control_Snapshot before_start = d->feedback.state();
d->feedback.on_render_started(frame, frame.render_begin_ns);
d->record_feedback_event("RenderStarted", frame.render_begin_ns, &frame, nullptr, &before_start);
d->record_state_transition_events(before_start, frame.render_begin_ns, &frame);
}
Refresh_Control_Snapshot before_complete = d->feedback.state();
d->feedback.on_render_completed(frame);
d->record_feedback_event("RenderCompleted", now_ns, &frame, nullptr, &before);
d->record_state_transition_events(before, now_ns, &frame);
d->record_feedback_event("RenderCompleted", now_ns, &frame, nullptr, &before_complete);
d->record_state_transition_events(before_complete, now_ns, &frame);
d->evaluate(now_ns);
}
@@ -158,7 +158,7 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::req
return result;
if (paint_request_pending.load(std::memory_order_acquire)) {
result.update_coalesced = true;
result.duplicate_present_request_suppressed = true;
return result;
}
result.update_request_id = ++next_present_request_id;
@@ -49,7 +49,7 @@ public:
bool request_present{};
bool middle_published{};
bool update_posted{};
bool update_coalesced{};
bool duplicate_present_request_suppressed{};
};
struct Frame_Consume_Result {
Low_Latency_Frame_Lease lease;
+20 -15
View File
@@ -23,7 +23,7 @@
namespace renderive {
namespace {
constexpr int Log_Schema_Version = 2;
constexpr int Log_Schema_Version = 3;
std::uint64_t process_id() {
#ifdef _WIN32
@@ -532,8 +532,11 @@ bool Performance_Log_Service::enqueue_frame(Performance_Log_Plot_Identity identi
out.add("target_period_ns", r.target_period_ns);
out.add("effective_interval_ns", r.effective_min_render_interval_ns);
out.add("next_render_time_ns", r.next_render_time_ns);
out.add("admission_deadline_wait_ns", r.admission_deadline_wait_ns);
out.add("executor_queue_wait_ns", r.executor_queue_wait_ns);
out.add("admission_wait_elapsed_ns", r.admission_wait_elapsed_ns);
out.add("admission_wait_remaining_ns", r.admission_wait_remaining_ns);
out.add("render_slot_idle_wait_ns", frame->render_slot_idle_wait_ns);
out.add("executor_queue_wait_current_ns", frame->executor_queue_wait_ns);
out.add("executor_queue_wait_srtt_ns", r.executor_queue_wait_ns);
out.add("present_queue_wait_ns", r.present_wait_ns);
out.add("paint_return_wait_ns", frame->paint_return_wait_ns);
out.add("consumer_window_size", r.consumer_window_size);
@@ -554,7 +557,7 @@ bool Performance_Log_Service::enqueue_frame(Performance_Log_Plot_Identity identi
out.add("mode_enter_total", r.mode_enter_count);
out.add("mode_exit_total", r.mode_exit_count);
out.add("update_posted", r.update_posted_count);
out.add("update_coalesced", r.update_coalesced_count);
out.add("duplicate_present_request_suppressed", r.duplicate_present_request_suppressed_count);
out.add("update_ack", r.update_ack_count);
out.add("update_repost", r.update_repost_count);
out.add("paint_without_core_post", r.paint_without_posted_update_count);
@@ -568,17 +571,19 @@ bool Performance_Log_Service::enqueue_frame(Performance_Log_Plot_Identity identi
out.add("logger_dropped", dropped_record_count());
out.add("executor_workers", e.worker_count);
out.add("executor_active_workers", e.active_workers);
out.add("executor_queued_frames", e.queued_frame_jobs);
out.add("executor_active_frames", e.active_frame_jobs);
out.add("executor_submitted_frames", e.submitted_jobs);
out.add("executor_started_tasks", e.started_task_nodes);
out.add("executor_completed_tasks", e.completed_task_nodes);
out.add("executor_rejected_frames", e.rejected_frame_jobs);
out.add("frame_task_count", frame->worker.task_count);
out.add("frame_peak_parallelism", frame->worker.peak_parallelism);
out.add("frame_task_wait_max_ns", frame->worker.queue_wait_max_ns);
out.add("frame_task_run_total_ns", frame->worker.worker_run_total_ns);
out.add("request_wait_ns", duration_ns(frame->render_request_ns, frame->render_begin_ns));
out.add("executor_queued_frame_jobs", e.queued_frame_jobs);
out.add("executor_active_frame_jobs", e.active_frame_jobs);
out.add("executor_submitted_topologies", e.submitted_topologies);
out.add("executor_started_topologies", e.started_topologies);
out.add("executor_completed_topologies", e.completed_topologies);
out.add("executor_started_task_nodes", e.started_task_nodes);
out.add("executor_completed_task_nodes", e.completed_task_nodes);
out.add("executor_rejected_frame_jobs", e.rejected_frame_jobs);
out.add("frame_business_task_count", frame->worker.business_task_count);
out.add("frame_business_task_peak_parallelism", frame->worker.business_task_peak_parallelism);
out.add("frame_topology_queue_wait_ns", frame->worker.topology_queue_wait_max_ns);
out.add("frame_business_task_run_total_ns", frame->worker.business_task_run_total_ns);
out.add("render_request_to_start_ns", duration_ns(frame->render_request_ns, frame->render_begin_ns));
out.add("prepare_ns", duration_ns(frame->prepare_begin_ns, frame->prepare_end_ns));
out.add("draw_ns", duration_ns(frame->draw_begin_ns, frame->draw_end_ns));
out.add("render_total_ns", duration_ns(frame->render_begin_ns, frame->render_end_ns));
+8 -5
View File
@@ -69,8 +69,7 @@ public:
frame.pixel_height = static_cast<std::uint64_t>(size.height);
frame.viewport_version = context->viewport_version.load(std::memory_order_acquire);
frame.dirty_reason_mask = pending_dirty_reason_mask.exchange(0, std::memory_order_acq_rel);
frame.render_queue_wait_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0;
frame.render_begin_ns = submit_time;
frame.render_slot_idle_wait_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0;
auto job = std::make_shared<Frame_Render_Job>();
job->owner = shared_from_this();
job->generation = generation.load(std::memory_order_acquire);
@@ -138,7 +137,7 @@ public:
if (submitted)
return true;
frame.outcome = Frame_Outcome::Worker_Budget_Dropped;
frame.worker.rejected_task_count++;
frame.worker.rejected_topology_count++;
if (flow)
flow->discard_render_frame(std::move(job->frame_surface));
release_frame_views(job);
@@ -164,7 +163,9 @@ public:
if (!frame_job_valid(job))
return;
Frame_Lifecycle_Record& frame = *job->lifecycle;
frame.prepare_begin_ns = steady_now_ns();
std::uint64_t begin_ns = steady_now_ns();
frame.render_begin_ns = begin_ns;
frame.prepare_begin_ns = begin_ns;
job->prepared_output_count.store(0, std::memory_order_release);
}
void dispatch_prepare_subflow(const std::shared_ptr<Frame_Render_Job>& job, Render_Task_Subflow& subflow) {
@@ -231,8 +232,10 @@ public:
if (!job || !job->buffer || !job->lifecycle)
return;
Frame_Lifecycle_Record& frame = *job->lifecycle;
if (job->worker_stat)
if (job->worker_stat) {
frame.worker = *job->worker_stat;
frame.executor_queue_wait_ns = frame.worker.topology_queue_wait_total_ns;
}
bool valid_generation = job->generation == generation.load(std::memory_order_acquire);
bool valid_context = !destroying();
bool render_complete = job->buffer->render_complete.load(std::memory_order_acquire);
+12 -9
View File
@@ -183,9 +183,11 @@ static void request_performance_update(const std::shared_ptr<Performance_Worker_
callback();
}
}
void schedule_performance_metrics_worker(const std::shared_ptr<Performance_Worker_State>& state) {
void schedule_performance_metrics_worker(const std::shared_ptr<Performance_Worker_State>& state, bool request_repaint) {
if (!state || state->cancelled.load(std::memory_order_acquire))
return;
if (request_repaint)
state->repaint_requested.store(true, std::memory_order_release);
state->work_requested.store(true, std::memory_order_release);
if (state->worker_active.exchange(true, std::memory_order_acq_rel))
return;
@@ -332,13 +334,14 @@ void Performance_Overlay::set_enabled(bool enabled) {
d()->enabled_value.store(enabled, std::memory_order_release);
worker->enabled.store(enabled, std::memory_order_release);
if (!enabled) {
worker->repaint_requested.store(false, std::memory_order_release);
worker->published_snapshot.store({}, std::memory_order_release);
request_performance_update(worker);
return;
}
worker->reset_requested.store(true, std::memory_order_release);
worker->snapshot_rebuild_requested.store(true, std::memory_order_release);
schedule_performance_metrics_worker(worker);
schedule_performance_metrics_worker(worker, true);
}
bool Performance_Overlay::enabled() const {
return const_cast<Performance_Overlay*>(this)->d()->enabled_value.load(std::memory_order_acquire);
@@ -356,7 +359,7 @@ void Performance_Overlay::set_options(Performance_Overlay_Options options) {
}
worker->snapshot_rebuild_requested.store(true, std::memory_order_release);
if (enabled())
schedule_performance_metrics_worker(worker);
schedule_performance_metrics_worker(worker, true);
}
Performance_Overlay_Options Performance_Overlay::options() const {
auto worker = const_cast<Performance_Overlay*>(this)->d()->metrics_worker;
@@ -417,7 +420,7 @@ void Performance_Overlay::set_viewport(Size viewport, std::uint64_t version) {
d()->metrics_worker->viewport_height.store(viewport.height, std::memory_order_release);
d()->metrics_worker->viewport_version.store(version, std::memory_order_release);
d()->metrics_worker->snapshot_rebuild_requested.store(true, std::memory_order_release);
schedule_performance_metrics_worker(d()->metrics_worker);
schedule_performance_metrics_worker(d()->metrics_worker, true);
}
bool Performance_Overlay::handle_wheel(PointF position, double angle_delta_y, double pixel_delta_y) {
auto snapshot = display_snapshot();
@@ -431,7 +434,7 @@ bool Performance_Overlay::handle_wheel(PointF position, double angle_delta_y, do
delta = -(angle_delta_y / 120.0) * 3.0 * std::max(1.0, line_height);
if (delta != 0.0) {
performance_atomic_add(d()->metrics_worker->pending_wheel_delta_px, delta);
schedule_performance_metrics_worker(d()->metrics_worker);
schedule_performance_metrics_worker(d()->metrics_worker, true);
}
return true;
}
@@ -451,13 +454,13 @@ bool Performance_Overlay::handle_pointer_press(PointF position) {
d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release);
d()->metrics_worker->drag_end_requested.store(false, std::memory_order_release);
d()->metrics_worker->drag_active.store(true, std::memory_order_release);
schedule_performance_metrics_worker(d()->metrics_worker);
schedule_performance_metrics_worker(d()->metrics_worker, true);
return true;
}
if (snapshot->scroll_track_rect.contains(position)) {
int page = position.y < snapshot->scroll_thumb_rect.y ? -1 : 1;
d()->metrics_worker->pending_page_delta.fetch_add(page, std::memory_order_acq_rel);
schedule_performance_metrics_worker(d()->metrics_worker);
schedule_performance_metrics_worker(d()->metrics_worker, true);
return true;
}
return false;
@@ -465,7 +468,7 @@ bool Performance_Overlay::handle_pointer_press(PointF position) {
bool Performance_Overlay::handle_pointer_move(PointF position) {
if (d()->metrics_worker->drag_active.load(std::memory_order_acquire)) {
d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release);
schedule_performance_metrics_worker(d()->metrics_worker);
schedule_performance_metrics_worker(d()->metrics_worker, true);
return true;
}
auto snapshot = display_snapshot();
@@ -493,7 +496,7 @@ bool Performance_Overlay::handle_pointer_release(PointF position) {
return false;
d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release);
d()->metrics_worker->drag_end_requested.store(true, std::memory_order_release);
schedule_performance_metrics_worker(d()->metrics_worker);
schedule_performance_metrics_worker(d()->metrics_worker, true);
return true;
}
bool Performance_Overlay::handle_pointer_leave() {
+57 -41
View File
@@ -74,8 +74,9 @@ struct Renderable_Aggregate_Stat {
struct Performance_Aggregate {
Psc::Value_Statistics request_wait_ms;
Psc::Value_Statistics producer_interval_ms;
Psc::Value_Statistics admission_deadline_wait_ms;
Psc::Value_Statistics render_queue_wait_ms;
Psc::Value_Statistics admission_wait_ms;
Psc::Value_Statistics render_slot_idle_wait_ms;
Psc::Value_Statistics executor_queue_wait_ms;
Psc::Value_Statistics prepare_ms;
Psc::Value_Statistics draw_ms;
Psc::Value_Statistics render_ms;
@@ -130,7 +131,8 @@ struct Performance_Aggregate {
summary.paint_end_ns = frame.paint_end_ns;
summary.presented_frame_id = frame.presented_frame_id;
summary.transition_32_ns = frame.transition_32_ns;
summary.render_queue_wait_ns = frame.render_queue_wait_ns;
summary.render_slot_idle_wait_ns = frame.render_slot_idle_wait_ns;
summary.executor_queue_wait_ns = frame.executor_queue_wait_ns;
summary.ready_queue_wait_ns = frame.ready_queue_wait_ns;
summary.present_queue_wait_ns = frame.present_queue_wait_ns;
summary.paint_return_wait_ns = frame.paint_return_wait_ns;
@@ -161,9 +163,9 @@ struct Performance_Aggregate {
update_duration(request_wait_ms, frame.render_request_ns, frame.render_begin_ns);
if (frame.refresh.producer_interval_ns)
producer_interval_ms.update(ns_to_ms(frame.refresh.producer_interval_ns));
if (frame.refresh.admission_deadline_wait_ns)
admission_deadline_wait_ms.update(ns_to_ms(frame.refresh.admission_deadline_wait_ns));
update_duration(render_queue_wait_ms, frame.render_role_enter_ns, frame.render_begin_ns);
admission_wait_ms.update(ns_to_ms(frame.refresh.admission_wait_elapsed_ns));
render_slot_idle_wait_ms.update(ns_to_ms(frame.render_slot_idle_wait_ns));
executor_queue_wait_ms.update(ns_to_ms(frame.executor_queue_wait_ns));
update_duration(prepare_ms, frame.prepare_begin_ns, frame.prepare_end_ns);
update_duration(draw_ms, frame.draw_begin_ns, frame.draw_end_ns);
update_duration(render_ms, frame.render_begin_ns, frame.render_end_ns);
@@ -299,7 +301,8 @@ enum class Performance_Metric_Id : std::uint16_t {
Paint_Period,
User_Period,
Target_Period,
Render_Queue_Wait,
Render_Slot_Idle_Wait,
Executor_Queue_Wait,
Ready_Queue_Wait,
Present_Queue_Wait,
Paint_Return_Wait,
@@ -307,18 +310,20 @@ enum class Performance_Metric_Id : std::uint16_t {
Active_Workers,
Queued_Frame_Jobs,
Active_Frame_Jobs,
Submitted_Jobs,
Submitted_Topologies,
Started_Topologies,
Completed_Topologies,
Started_Task_Nodes,
Completed_Task_Nodes,
Rejected_Frame_Jobs,
Queue_Wait_Avg,
Queue_Wait_Max,
Task_Run_Avg,
Task_Run_Max,
Frame_Task_Count,
Frame_Peak_Parallelism,
Frame_Wait_Max,
Frame_Run_Total,
Topology_Queue_Wait_Avg,
Topology_Queue_Wait_Max,
Task_Node_Run_Avg,
Task_Node_Run_Max,
Frame_Business_Task_Count,
Frame_Business_Task_Peak_Parallelism,
Frame_Topology_Queue_Wait,
Frame_Business_Task_Run_Total,
Outcome,
Cache_Hit,
Cache_Miss,
@@ -384,6 +389,7 @@ struct Performance_Worker_State {
std::atomic_bool cancelled{false};
std::atomic_bool reset_requested{false};
std::atomic_bool snapshot_rebuild_requested{false};
std::atomic_bool repaint_requested{false};
std::atomic<Button_State> copy_button_state{Button_State::Normal};
std::atomic<Button_State> close_button_state{Button_State::Normal};
std::atomic<double> scroll_offset{0.0};
@@ -427,7 +433,7 @@ inline void performance_atomic_add(std::atomic<double>& value, double delta) {
double current = value.load(std::memory_order_acquire);
while (!value.compare_exchange_weak(current, current + delta, std::memory_order_acq_rel, std::memory_order_acquire)) {}
}
void schedule_performance_metrics_worker(const std::shared_ptr<Performance_Worker_State>& state);
void schedule_performance_metrics_worker(const std::shared_ptr<Performance_Worker_State>& state, bool request_repaint = false);
struct Performance_Metric_Cell {
std::string label;
@@ -532,8 +538,10 @@ struct Performance_Layout_Builder {
return Performance_Metric_Id::User_Period;
if (key == "target_period")
return Performance_Metric_Id::Target_Period;
if (key == "render_queue_wait")
return Performance_Metric_Id::Render_Queue_Wait;
if (key == "render_slot_idle_wait")
return Performance_Metric_Id::Render_Slot_Idle_Wait;
if (key == "executor_queue_wait")
return Performance_Metric_Id::Executor_Queue_Wait;
if (key == "ready_queue_wait")
return Performance_Metric_Id::Ready_Queue_Wait;
if (key == "present_queue_wait")
@@ -549,7 +557,11 @@ struct Performance_Layout_Builder {
if (key == "taskflow_active_frames")
return Performance_Metric_Id::Active_Frame_Jobs;
if (key == "taskflow_submitted")
return Performance_Metric_Id::Submitted_Jobs;
return Performance_Metric_Id::Submitted_Topologies;
if (key == "taskflow_topologies_started")
return Performance_Metric_Id::Started_Topologies;
if (key == "taskflow_topologies_completed")
return Performance_Metric_Id::Completed_Topologies;
if (key == "taskflow_started")
return Performance_Metric_Id::Started_Task_Nodes;
if (key == "taskflow_completed")
@@ -557,21 +569,21 @@ struct Performance_Layout_Builder {
if (key == "taskflow_rejected")
return Performance_Metric_Id::Rejected_Frame_Jobs;
if (key == "taskflow_queue_wait_avg")
return Performance_Metric_Id::Queue_Wait_Avg;
if (key == "taskflow_queue_wait_p95")
return Performance_Metric_Id::Queue_Wait_Max;
return Performance_Metric_Id::Topology_Queue_Wait_Avg;
if (key == "taskflow_queue_wait_max")
return Performance_Metric_Id::Topology_Queue_Wait_Max;
if (key == "taskflow_run_avg")
return Performance_Metric_Id::Task_Run_Avg;
if (key == "taskflow_run_p95")
return Performance_Metric_Id::Task_Run_Max;
return Performance_Metric_Id::Task_Node_Run_Avg;
if (key == "taskflow_run_max")
return Performance_Metric_Id::Task_Node_Run_Max;
if (key == "frame_worker_tasks")
return Performance_Metric_Id::Frame_Task_Count;
return Performance_Metric_Id::Frame_Business_Task_Count;
if (key == "frame_worker_peak")
return Performance_Metric_Id::Frame_Peak_Parallelism;
return Performance_Metric_Id::Frame_Business_Task_Peak_Parallelism;
if (key == "frame_worker_wait_max")
return Performance_Metric_Id::Frame_Wait_Max;
return Performance_Metric_Id::Frame_Topology_Queue_Wait;
if (key == "frame_worker_run_total")
return Performance_Metric_Id::Frame_Run_Total;
return Performance_Metric_Id::Frame_Business_Task_Run_Total;
if (key == "frame_outcome")
return Performance_Metric_Id::Outcome;
if (key == "cache_hit_count")
@@ -955,19 +967,22 @@ struct Performance_Layout_Builder {
lines.push_back("[low_latency] consumer_samples accepted:" + count_text("consumer_accepted", refresh.consumer_sample_accepted_count) + " rejected:" + count_text("consumer_rejected", refresh.consumer_sample_total_count - refresh.consumer_sample_accepted_count) + " ratio:" + number_text("consumer_ratio", refresh.consumer_sample_accepted_ratio * 100.0) + "% last:" + consumer_sample_result_text(refresh.last_consumer_sample_result));
lines.push_back("[low_latency] timing producer_interval:" + period_text("producer_interval", refresh.producer_interval_ns) + " render_service:" + period_text("render_period", refresh.render_period_ns) + " request_to_post:" + period_text("request_to_update_post", refresh.request_to_update_post_srtt_ns) + " post_to_paint:" + period_text("update_to_paint", refresh.update_post_to_paint_srtt_ns) + " paint_service:" + period_text("paint_period", refresh.paint_service_ns) + " raw_display:" + period_text("raw_display", refresh.raw_display_interval_srtt_ns) + " accepted_consumer:" + period_text("accepted_consumer", refresh.accepted_consumer_interval_srtt_ns));
lines.push_back("[low_latency] consumer_window size:" + count_text("consumer_window_size", refresh.consumer_window_size) + " accepted:" + count_text("consumer_window_accepted", refresh.consumer_window_accepted) + " rejected:" + count_text("consumer_window_rejected", refresh.consumer_window_size - refresh.consumer_window_accepted) + " ratio:" + number_text("consumer_window_ratio", refresh.consumer_window_accepted_ratio * 100.0) + "%");
lines.push_back("[low_latency] queue admission_deadline_wait:" + ns_text("admission_deadline_wait", refresh.admission_deadline_wait_ns) + "ms executor_queue_wait:" + ns_text("executor_queue_wait", frame.render_queue_wait_ns) + "ms present_wait:" + ns_text("present_wait", frame.present_queue_wait_ns) + "ms paint_return:" + ns_text("paint_return_wait", frame.paint_return_wait_ns) + "ms");
lines.push_back("[low_latency] update posted:" + count_text("update_posted_count", refresh.update_posted_count) + " coalesced:" + count_text("update_coalesced_count", refresh.update_coalesced_count) + " ack:" + count_text("update_ack_count", refresh.update_ack_count) + " repost:" + count_text("update_repost_count", refresh.update_repost_count) + " paint_without_posted:" + count_text("paint_without_posted", refresh.paint_without_posted_update_count));
lines.push_back("[low_latency] pacing admission_wait:" + ns_text("admission_wait", refresh.admission_wait_elapsed_ns) + "ms render_slot_idle:" + ns_text("render_slot_idle_wait", frame.render_slot_idle_wait_ns) + "ms");
lines.push_back("[pipeline] queue executor_wait:" + ns_text("executor_queue_wait", frame.executor_queue_wait_ns) + "ms present_wait:" + ns_text("present_wait", frame.present_queue_wait_ns) + "ms paint_return:" + ns_text("paint_return_wait", frame.paint_return_wait_ns) + "ms");
lines.push_back("[low_latency] update posted:" + count_text("update_posted_count", refresh.update_posted_count) + " duplicate_suppressed:" + count_text("duplicate_present_request_suppressed_count", refresh.duplicate_present_request_suppressed_count) + " ack:" + count_text("update_ack_count", refresh.update_ack_count) + " repost:" + count_text("update_repost_count", refresh.update_repost_count) + " paint_without_posted:" + count_text("paint_without_posted", refresh.paint_without_posted_update_count));
lines.push_back("[logger] dropped:" + count_text("logger_dropped", performance_log_service().dropped_record_count()));
lines.push_back("[low_latency] buffers rendered:" + count_text("presented_feedback", refresh.presented_frame_count) + " middle_published:" + count_text("middle_published", refresh.middle_published_count) + " middle_superseded:" + count_text("middle_superseded", refresh.middle_superseded_count) + " painting_advanced:" + count_text("painting_advanced", refresh.painting_advanced_count) + " painting_repeated:" + count_text("repeated_feedback", refresh.repeated_frame_count) + " underrun:" + count_text("underrun_feedback", refresh.underrun_count));
lines.push_back("[low_latency] decision:" + feedback_decision_text(refresh.last_decision) + " reason:" + refresh.decision_reason + " at:" + count_text("decision_time", refresh.decision_timestamp_ns) + " producer_starvation:" + count_text("producer_starvation", refresh.producer_starvation_count) + " starvation_active:" + (refresh.producer_starvation_active ? std::string("1") : std::string("0")) + " enter:" + count_text("mode_enter", refresh.mode_enter_count) + " exit:" + count_text("mode_exit", refresh.mode_exit_count) + " efficiency:" + number_text("display_efficiency", refresh.display_efficiency * 100.0) + "%");
lines.push_back("[executor] taskflow workers:" + count_text("taskflow_workers", static_cast<std::uint64_t>(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast<std::uint64_t>(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast<std::uint64_t>(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast<std::uint64_t>(executor.active_frame_jobs)));
lines.push_back("[executor] taskflow tasks submitted:" + count_text("taskflow_submitted", executor.submitted_jobs) + " started:" + count_text("taskflow_started", executor.started_task_nodes) + " completed:" + count_text("taskflow_completed", executor.completed_task_nodes) + " rejected_frames:" + count_text("taskflow_rejected", executor.rejected_frame_jobs));
lines.push_back("[executor] taskflow wait avg:" + ns_text("taskflow_queue_wait_avg", executor.average_queue_wait_ns) + "ms p95:" + ns_text("taskflow_queue_wait_p95", executor.max_queue_wait_ns) + "ms run avg:" + ns_text("taskflow_run_avg", executor.average_task_duration_ns) + "ms p95:" + ns_text("taskflow_run_p95", executor.max_task_duration_ns) + "ms");
lines.push_back("frame worker tasks:" + count_text("frame_worker_tasks", worker.task_count) + " peak_parallel:" + count_text("frame_worker_peak", worker.peak_parallelism) + " wait_max:" + ns_text("frame_worker_wait_max", worker.queue_wait_max_ns) + "ms run_total:" + ns_text("frame_worker_run_total", worker.worker_run_total_ns) + "ms");
lines.push_back(duration_line("request_wait", "lifecycle request_wait", aggregate.request_wait_ms));
lines.push_back("[executor/global] workers:" + count_text("taskflow_workers", static_cast<std::uint64_t>(executor.worker_count)) + " active_workers:" + count_text("taskflow_active", static_cast<std::uint64_t>(executor.active_workers)) + " queued_frame_jobs:" + count_text("taskflow_queued_frames", static_cast<std::uint64_t>(executor.queued_frame_jobs)) + " active_frame_jobs:" + count_text("taskflow_active_frames", static_cast<std::uint64_t>(executor.active_frame_jobs)));
lines.push_back("[executor/global] topologies submitted:" + count_text("taskflow_submitted", executor.submitted_topologies) + " started:" + count_text("taskflow_topologies_started", executor.started_topologies) + " completed:" + count_text("taskflow_topologies_completed", executor.completed_topologies) + " rejected_frame_jobs:" + count_text("taskflow_rejected", executor.rejected_frame_jobs));
lines.push_back("[executor/global] task_nodes started:" + count_text("taskflow_started", executor.started_task_nodes) + " completed:" + count_text("taskflow_completed", executor.completed_task_nodes));
lines.push_back("[executor/global] topology_queue_wait avg:" + ns_text("taskflow_queue_wait_avg", executor.average_topology_queue_wait_ns) + "ms max:" + ns_text("taskflow_queue_wait_max", executor.max_topology_queue_wait_ns) + "ms task_node_run avg:" + ns_text("taskflow_run_avg", executor.average_task_node_duration_ns) + "ms max:" + ns_text("taskflow_run_max", executor.max_task_node_duration_ns) + "ms");
lines.push_back("[executor/frame] business_tasks:" + count_text("frame_worker_tasks", worker.business_task_count) + " peak_parallel:" + count_text("frame_worker_peak", worker.business_task_peak_parallelism) + " topology_queue_wait:" + ns_text("frame_worker_wait_max", worker.topology_queue_wait_max_ns) + "ms business_run_total:" + ns_text("frame_worker_run_total", worker.business_task_run_total_ns) + "ms");
lines.push_back(duration_line("request_wait", "lifecycle render_request_to_start", aggregate.request_wait_ms));
lines.push_back(duration_line("producer_interval", "timing producer_interval", aggregate.producer_interval_ms));
lines.push_back(duration_line("admission_deadline_wait", "timing admission_deadline_wait", aggregate.admission_deadline_wait_ms));
lines.push_back(duration_line("render_queue_wait", "timing executor_queue_wait", aggregate.render_queue_wait_ms));
lines.push_back(duration_line("admission_wait", "timing admission_wait", aggregate.admission_wait_ms));
lines.push_back(duration_line("render_slot_idle_wait", "timing render_slot_idle_wait", aggregate.render_slot_idle_wait_ms));
lines.push_back(duration_line("executor_queue_wait", "timing executor_queue_wait", aggregate.executor_queue_wait_ms));
lines.push_back(duration_line("prepare", "lifecycle prepare", aggregate.prepare_ms));
lines.push_back(duration_line("draw", "lifecycle draw", aggregate.draw_ms));
lines.push_back(duration_line("render", "lifecycle render_total", aggregate.render_ms));
@@ -1288,7 +1303,7 @@ struct Performance_Overlay_Private {
metrics_worker->close_button_state.store(close_button_interaction.state(), std::memory_order_release);
metrics_worker->snapshot_rebuild_requested.store(true, std::memory_order_release);
if (enabled_value.load(std::memory_order_acquire))
schedule_performance_metrics_worker(metrics_worker);
schedule_performance_metrics_worker(metrics_worker, true);
}
void reset_button_states() {
@@ -1359,7 +1374,8 @@ struct Performance_Overlay_Private {
}
if (changed && state->enabled.load(std::memory_order_acquire)) {
state->published_snapshot.store(build_display_snapshot(*state), std::memory_order_release);
invoke_update_callback(*state);
if (state->repaint_requested.exchange(false, std::memory_order_acq_rel))
invoke_update_callback(*state);
}
if (state->work_requested.load(std::memory_order_acquire))
continue;
+3 -20
View File
@@ -123,7 +123,7 @@ void Abs_Plot::paintEvent(QPaintEvent* event) {
bool Abs_Plot::event(QEvent* event) {
bool base_result = QWidget::event(event);
return d->event(this, event, base_result);
return d->event(event, base_result);
}
void Abs_Plot_Private::paint_event(Abs_Plot* plot, QPaintEvent* event) {
@@ -155,19 +155,7 @@ void Abs_Plot_Private::paint_event(Abs_Plot* plot, QPaintEvent* event) {
core->end_present(std::move(frame), paint_begin_time, steady_now_ns());
}
void Abs_Plot_Private::request_performance_overlay_update(Abs_Plot* plot) {
if (!plot || performance_update_pending->exchange(true, std::memory_order_acq_rel))
return;
QPointer<Abs_Plot> guarded_plot(plot);
auto pending = performance_update_pending;
QTimer::singleShot(0, plot, [guarded_plot, pending]() mutable {
pending->store(false, std::memory_order_release);
if (guarded_plot)
guarded_plot->update();
});
}
bool Abs_Plot_Private::event(Abs_Plot* plot, QEvent* event, bool base_result) {
bool Abs_Plot_Private::event(QEvent* event, bool base_result) {
switch (event->type()) {
case QEvent::Resize: {
auto* resize_event = static_cast<QResizeEvent*>(event);
@@ -190,8 +178,7 @@ bool Abs_Plot_Private::event(Abs_Plot* plot, QEvent* event, bool base_result) {
break;
}
case QEvent::Leave: {
if (performance_overlay_handle_pointer_leave(*core))
request_performance_overlay_update(plot);
performance_overlay_handle_pointer_leave(*core);
Event leave_event(Event_Type::Leave);
core->dispatch_event(leave_event);
break;
@@ -200,7 +187,6 @@ bool Abs_Plot_Private::event(Abs_Plot* plot, QEvent* event, bool base_result) {
auto wheel_event = Qt_Event_Adapter::to_wheel_event(static_cast<QWheelEvent*>(event));
if (performance_overlay_handle_wheel(*core, wheel_event.position, wheel_event.angle_delta_y, wheel_event.pixel_delta_y)) {
event->accept();
request_performance_overlay_update(plot);
return true;
}
core->dispatch_event(wheel_event);
@@ -210,7 +196,6 @@ bool Abs_Plot_Private::event(Abs_Plot* plot, QEvent* event, bool base_result) {
auto pointer_event = Qt_Event_Adapter::to_pointer_event(static_cast<QMouseEvent*>(event), Event_Type::Pointer_Move);
if (performance_overlay_handle_pointer_move(*core, pointer_event.position)) {
event->accept();
request_performance_overlay_update(plot);
return true;
}
core->dispatch_event(pointer_event);
@@ -221,7 +206,6 @@ bool Abs_Plot_Private::event(Abs_Plot* plot, QEvent* event, bool base_result) {
auto pointer_event = Qt_Event_Adapter::to_pointer_event(mouse_event, Event_Type::Pointer_Press);
if (mouse_event->button() == Qt::LeftButton && performance_overlay_handle_pointer_press(*core, pointer_event.position)) {
event->accept();
request_performance_overlay_update(plot);
return true;
}
core->dispatch_event(pointer_event);
@@ -232,7 +216,6 @@ bool Abs_Plot_Private::event(Abs_Plot* plot, QEvent* event, bool base_result) {
auto pointer_event = Qt_Event_Adapter::to_pointer_event(mouse_event, Event_Type::Pointer_Release);
if (mouse_event->button() == Qt::LeftButton && performance_overlay_handle_pointer_release(*core, pointer_event.position)) {
event->accept();
request_performance_overlay_update(plot);
return true;
}
core->dispatch_event(pointer_event);
+1 -2
View File
@@ -14,9 +14,8 @@ struct Abs_Plot_Private {
virtual ~Abs_Plot_Private();
void attach_widget(Abs_Plot* plot);
bool event(Abs_Plot* plot, QEvent* event, bool base_result);
bool event(QEvent* event, bool base_result);
void paint_event(Abs_Plot* plot, QPaintEvent* event);
void request_performance_overlay_update(Abs_Plot* plot);
Frame_Flow* flow{};
std::unique_ptr<Plot_Core> core;
+118 -80
View File
@@ -31,6 +31,7 @@
#include <initializer_list>
#include <latch>
#include <limits>
#include <map>
#include <rigtorp/MPMCQueue.h>
#include <sstream>
#include <string>
@@ -248,31 +249,33 @@ std::vector<std::string> read_lines(const std::filesystem::path& path) {
lines.push_back(line);
return lines;
}
std::vector<std::string> split_csv_row(const std::string& line) {
std::vector<std::string> cells;
std::string cell;
bool in_quote = false;
for (std::size_t i = 0; i < line.size(); ++i) {
char ch = line[i];
if (ch == '"') {
if (in_quote && i + 1 < line.size() && line[i + 1] == '"') {
cell.push_back('"');
++i;
}
else {
in_quote = !in_quote;
}
using Parsed_Log_Record = std::map<std::string, std::string>;
std::vector<Parsed_Log_Record> read_log_records(const std::filesystem::path& path, std::string_view kind) {
std::vector<Parsed_Log_Record> records;
Parsed_Log_Record current;
bool in_record = false;
std::string begin = "[" + std::string(kind) + "]";
std::string end = "[/" + std::string(kind) + "]";
for (const auto& line : read_lines(path)) {
if (line == begin) {
current.clear();
in_record = true;
continue;
}
if (ch == ',' && !in_quote) {
cells.push_back(cell);
cell.clear();
if (line == end) {
if (in_record)
records.push_back(std::move(current));
current = {};
in_record = false;
continue;
}
cell.push_back(ch);
if (!in_record)
continue;
std::size_t separator = line.find('=');
if (separator != std::string::npos)
current.emplace(line.substr(0, separator), line.substr(separator + 1));
}
cells.push_back(cell);
return cells;
return records;
}
Frame_Feedback_Event_Record feedback_event(std::string event, const Frame_Lifecycle_Record& frame, std::uint64_t timestamp_ns) {
Frame_Feedback_Event_Record record;
@@ -1471,7 +1474,7 @@ TEST(Renderive_Frame_Feedback, AdmissionDeadlineWaitIsNotProducerStarvation) {
auto frame = feedback_frame(3, 100000000, 2000000, 1000000);
controller.note_middle_published(frame.transition_12_ns);
controller.on_render_completed(frame);
controller.note_admission_deadline_wait(20000000, frame.transition_12_ns + 1);
controller.note_admission_wait(20000000, frame.transition_12_ns + 1);
controller.on_frame_presented(frame);
EXPECT_EQ(controller.state().consumer_sample_accepted_count, accepted + 1);
@@ -1596,6 +1599,26 @@ TEST(Renderive_Frame_Feedback, ProducerStarvationCountsStateTransitionOnce) {
controller.observe_runtime(deadline + 500000000, false, true, false);
EXPECT_EQ(controller.state().producer_starvation_count, 1u);
}
TEST(Renderive_Frame_Feedback, ProducerIntervalUsesActualRenderStart) {
Low_Latency_Feedback_Controller controller;
auto first = feedback_frame(1, 100000000, 2000000, 1000000);
auto second = feedback_frame(2, 116666667, 2000000, 1000000);
controller.on_render_completed(first);
controller.on_render_completed(second);
EXPECT_NEAR(static_cast<double>(controller.state().producer_interval_current_ns), 16666667.0, 1.0);
}
TEST(Renderive_Frame_Feedback, AdmissionWaitRecordsActualElapsedTimeAndClears) {
Low_Latency_Feedback_Controller controller;
controller.note_admission_wait(5000000, 100000000);
EXPECT_EQ(controller.state().admission_wait_remaining_ns, 5000000u);
controller.note_render_admitted(104000000);
EXPECT_EQ(controller.state().admission_wait_elapsed_ns, 4000000u);
EXPECT_EQ(controller.state().admission_wait_remaining_ns, 0u);
controller.note_admission_wait(5000000, 200000000);
controller.cancel_admission_wait(201000000);
EXPECT_EQ(controller.state().admission_wait_elapsed_ns, 0u);
EXPECT_EQ(controller.state().admission_wait_remaining_ns, 0u);
}
TEST(Renderive_Frame_Flow, LatencyEagerRendersWhileMiddleIsPending) {
Low_Latency_Frame_Flow flow;
@@ -1609,19 +1632,33 @@ TEST(Renderive_Frame_Flow, LatencyEagerRendersWhileMiddleIsPending) {
EXPECT_EQ(host.paint_requests, 1);
EXPECT_EQ(host.render_attempts, 1);
}
TEST(Renderive_Frame_Flow, PendingPresentRequestSuppressesDuplicateRequest) {
Low_Latency_Frame_Flow flow;
auto published = publish_low_latency_flow_frame(flow, 1, 20);
ASSERT_TRUE(published.request_present);
Flow_Test_Host host;
host.snapshot.middle_has_new_frame = true;
flow.attach(host);
flow.activate_view(30);
EXPECT_EQ(host.paint_requests, 0);
}
TEST(Renderive_Frame_Flow, LowLatencyEmitsFeedbackEventsThroughHost) {
Low_Latency_Frame_Flow flow;
Flow_Test_Host host;
host.snapshot.dirty = true;
flow.attach(host);
flow.notify_model_dirty(Dirty_Reason::Core_Data, 10);
flow.activate_view(20);
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "InputChanged"), host.feedback_events.end());
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderAdmitted"), host.feedback_events.end());
EXPECT_EQ(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderStarted"), host.feedback_events.end());
Frame_Lifecycle_Record frame;
frame.render_begin_ns = 30;
frame.render_end_ns = 40;
flow.notify_render_finished(frame, 40);
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderStarted"), host.feedback_events.end());
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderCompleted"), host.feedback_events.end());
}
TEST(Renderive_Frame_Pipeline, NewRenderSupersedesOldMiddle) {
@@ -1638,7 +1675,7 @@ TEST(Renderive_Frame_Pipeline, NewRenderSupersedesOldMiddle) {
EXPECT_TRUE(third.middle_published);
ASSERT_NE(third.superseded_frame_record, nullptr);
EXPECT_EQ(third.superseded_frame_record->frame_id, 2u);
EXPECT_TRUE(third.update_coalesced);
EXPECT_TRUE(third.duplicate_present_request_suppressed);
}
TEST(Renderive_Frame_Pipeline, OnlyOneUpdateRemainsPosted) {
@@ -1649,7 +1686,7 @@ TEST(Renderive_Frame_Pipeline, OnlyOneUpdateRemainsPosted) {
auto repeated = pipeline.request_present(30);
EXPECT_FALSE(repeated.request_present);
EXPECT_TRUE(repeated.update_coalesced);
EXPECT_TRUE(repeated.duplicate_present_request_suppressed);
EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
}
TEST(Renderive_Frame_Flow, LowLatencyOwnsActiveState) {
@@ -2208,6 +2245,27 @@ TEST(Renderive_Hover_Tooltip, DefaultTextPenContrastsWithDefaultBackground) {
EXPECT_EQ(state.tooltip_background_brush.color, Color::white());
EXPECT_EQ(state.tooltip_text_pen.color, Color::black());
}
TEST(Renderive_Performance_Overlay, MetricUpdatesDoNotRequestIndependentRepaint) {
auto state = std::make_shared<Performance_Worker_State>();
state->enabled.store(true, std::memory_order_release);
std::atomic_int update_count{};
state->update_callback = [&update_count]() {
update_count.fetch_add(1, std::memory_order_relaxed);
};
Performance_Frame_Record frame = performance_frame(1);
ASSERT_TRUE(state->metric_queue.try_push(frame));
schedule_performance_metrics_worker(state);
ASSERT_TRUE(wait_until([&]() {
return state->published_snapshot.load(std::memory_order_acquire) && !state->worker_active.load(std::memory_order_acquire);
}));
EXPECT_EQ(update_count.load(std::memory_order_acquire), 0);
state->snapshot_rebuild_requested.store(true, std::memory_order_release);
schedule_performance_metrics_worker(state, true);
ASSERT_TRUE(wait_until([&]() {
return update_count.load(std::memory_order_acquire) == 1 && !state->worker_active.load(std::memory_order_acquire);
}));
state->cancelled.store(true, std::memory_order_release);
}
TEST(Renderive_Performance_Overlay, WorkerConsumesSharedFrameRecord) {
auto state = std::make_shared<Performance_Worker_State>();
state->enabled.store(true, std::memory_order_release);
@@ -2256,7 +2314,7 @@ TEST(Renderive_Performance_Overlay, LowLatencyDiagnosticsExposePresentFeedbackFi
frame->refresh.producer_starvation_count = 2;
frame->refresh.middle_superseded_count = 3;
frame->refresh.update_posted_count = 4;
frame->refresh.update_coalesced_count = 1;
frame->refresh.duplicate_present_request_suppressed_count = 1;
frame->refresh.update_ack_count = 3;
frame->refresh.update_repost_count = 1;
shower.collect_frame(frame);
@@ -2275,7 +2333,7 @@ TEST(Renderive_Performance_Overlay, LowLatencyDiagnosticsExposePresentFeedbackFi
EXPECT_NE(text.find("update_to_paint"), std::string::npos);
EXPECT_NE(text.find("producer_starvation"), std::string::npos);
EXPECT_NE(text.find("middle_superseded"), std::string::npos);
EXPECT_NE(text.find("coalesced:"), std::string::npos);
EXPECT_NE(text.find("duplicate_suppressed:"), std::string::npos);
}
TEST(Renderive_Performance_Overlay, PerformanceLogDisabledWritesNothing) {
@@ -2290,8 +2348,8 @@ TEST(Renderive_Performance_Overlay, PerformanceLogDisabledWritesNothing) {
shower.set_enabled(false);
shower.collect_frame(performance_frame(1));
performance_log_service().shutdown();
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_frames.csv"));
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_feedback.csv"));
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_frames.log"));
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_feedback.log"));
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_meta.json"));
std::filesystem::remove_all(directory);
}
@@ -2320,18 +2378,20 @@ TEST(Renderive_Performance_Overlay, PerformanceLogWorksWhenOverlayHidden) {
shower.collect_feedback_event(event);
performance_log_service().shutdown();
auto frame_lines = read_lines(directory / "hidden_frames.csv");
auto feedback_lines = read_lines(directory / "hidden_feedback.csv");
ASSERT_GE(frame_lines.size(), 2u);
ASSERT_GE(feedback_lines.size(), 2u);
EXPECT_EQ(frame_lines.front(), performance_frame_csv_header());
EXPECT_EQ(feedback_lines.front(), performance_feedback_csv_header());
EXPECT_EQ(split_csv_row(frame_lines.front()).size(), split_csv_row(frame_lines[1]).size());
EXPECT_EQ(split_csv_row(feedback_lines.front()).size(), split_csv_row(feedback_lines[1]).size());
auto frame_cells = split_csv_row(frame_lines[1]);
ASSERT_GT(frame_cells.size(), 4u);
EXPECT_EQ(frame_cells[2], "42");
EXPECT_EQ(frame_cells[3], "hidden,plot\"a");
auto frame_records = read_log_records(directory / "hidden_frames.log", "frame");
auto feedback_records = read_log_records(directory / "hidden_feedback.log", "feedback");
ASSERT_EQ(frame_records.size(), 1u);
ASSERT_EQ(feedback_records.size(), 1u);
const auto& frame_record = frame_records.front();
EXPECT_EQ(frame_record.at("schema_version"), "3");
EXPECT_EQ(frame_record.at("plot_id"), "42");
EXPECT_EQ(frame_record.at("plot_name"), "hidden,plot\"a");
EXPECT_TRUE(frame_record.contains("render_slot_idle_wait_ns"));
EXPECT_TRUE(frame_record.contains("executor_queue_wait_current_ns"));
EXPECT_TRUE(frame_record.contains("executor_started_topologies"));
EXPECT_TRUE(frame_record.contains("executor_completed_topologies"));
EXPECT_TRUE(frame_record.contains("render_request_to_start_ns"));
EXPECT_TRUE(frame_record.contains("duplicate_present_request_suppressed"));
EXPECT_TRUE(std::filesystem::exists(directory / "hidden_meta.json"));
std::filesystem::remove_all(directory);
}
@@ -2357,15 +2417,13 @@ TEST(Renderive_Performance_Overlay, PerformanceLogSeparatesMultiplePlots) {
second.collect_frame(performance_frame(2));
performance_log_service().shutdown();
auto lines = read_lines(directory / "multi_frames.csv");
ASSERT_GE(lines.size(), 3u);
auto records = read_log_records(directory / "multi_frames.log", "frame");
ASSERT_EQ(records.size(), 2u);
bool saw_spectrum = false;
bool saw_waterfall = false;
for (std::size_t i = 1; i < lines.size(); ++i) {
auto cells = split_csv_row(lines[i]);
ASSERT_GT(cells.size(), 4u);
saw_spectrum = saw_spectrum || (cells[2] == "11" && cells[3] == "spectrum");
saw_waterfall = saw_waterfall || (cells[2] == "12" && cells[3] == "waterfall");
for (const auto& record : records) {
saw_spectrum = saw_spectrum || (record.at("plot_id") == "11" && record.at("plot_name") == "spectrum");
saw_waterfall = saw_waterfall || (record.at("plot_id") == "12" && record.at("plot_name") == "waterfall");
}
EXPECT_TRUE(saw_spectrum);
EXPECT_TRUE(saw_waterfall);
@@ -2398,27 +2456,20 @@ TEST(Renderive_Performance_Overlay, FeedbackEventLogUsesRuntimeEventRecord) {
shower.collect_feedback_event(event);
performance_log_service().shutdown();
auto lines = read_lines(directory / "feedback_feedback.csv");
ASSERT_GE(lines.size(), 2u);
auto header = split_csv_row(lines.front());
auto row = split_csv_row(lines[1]);
ASSERT_EQ(header.size(), row.size());
auto index_of = [&](const std::string& name) {
auto it = std::find(header.begin(), header.end(), name);
EXPECT_NE(it, header.end());
return static_cast<std::size_t>(std::distance(header.begin(), it));
};
EXPECT_EQ(row[index_of("event")], "ProducerStarvationEnter");
EXPECT_EQ(row[index_of("mode_before")], "latency_eager");
EXPECT_EQ(row[index_of("mode_after")], "consumer_paced");
EXPECT_EQ(row[index_of("dirty")], "1");
EXPECT_EQ(row[index_of("producer_starved")], "1");
EXPECT_EQ(row[index_of("effective_interval_before_ns")], "16666667");
EXPECT_EQ(row[index_of("effective_interval_after_ns")], "33333333");
auto records = read_log_records(directory / "feedback_feedback.log", "feedback");
ASSERT_EQ(records.size(), 1u);
const auto& record = records.front();
EXPECT_EQ(record.at("event"), "ProducerStarvationEnter");
EXPECT_EQ(record.at("mode_before"), "latency_eager");
EXPECT_EQ(record.at("mode_after"), "consumer_paced");
EXPECT_EQ(record.at("dirty"), "1");
EXPECT_EQ(record.at("producer_starved"), "1");
EXPECT_EQ(record.at("effective_interval_before_ns"), "16666667");
EXPECT_EQ(record.at("effective_interval_after_ns"), "33333333");
std::filesystem::remove_all(directory);
}
TEST(Renderive_Performance_Overlay, SummaryUsesMultipleColumnsAndTextRoles) {
TEST(Renderive_Performance_Overlay, SummaryUsesSingleColumnAndTextRoles) {
Performance_Overlay shower;
Performance_Overlay_Options options;
options.section_color = Color{10, 20, 30, 255};
@@ -2445,26 +2496,13 @@ TEST(Renderive_Performance_Overlay, SummaryUsesMultipleColumnsAndTextRoles) {
bool has_label = false;
bool has_value = false;
bool has_warning = false;
int max_column = 0;
std::array<double, 4> section_x{};
std::array<bool, 4> saw_section_column{};
for (const auto& run : snapshot->text_runs) {
max_column = std::max(max_column, run.column_index);
EXPECT_EQ(run.column_index, 0);
has_section = has_section || run.role == Performance_Text_Role::Section;
has_label = has_label || run.role == Performance_Text_Role::Label;
has_value = has_value || run.role == Performance_Text_Role::Value;
has_warning = has_warning || run.role == Performance_Text_Role::Warning;
if (run.role == Performance_Text_Role::Section && run.column_index >= 0 && run.column_index < 4) {
if (!saw_section_column[static_cast<std::size_t>(run.column_index)]) {
section_x[static_cast<std::size_t>(run.column_index)] = run.rect.x;
saw_section_column[static_cast<std::size_t>(run.column_index)] = true;
}
else {
EXPECT_EQ(run.rect.x, section_x[static_cast<std::size_t>(run.column_index)]);
}
}
}
EXPECT_GE(max_column, 2);
EXPECT_TRUE(has_section);
EXPECT_TRUE(has_label);
EXPECT_TRUE(has_value);