修复
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
#include <rigtorp/MPMCQueue.h>
|
||||
#include "../architecture/Frame_Scheduler.h"
|
||||
#include "../architecture/Render_Data.h"
|
||||
#include "../performance/Performance_Log_Service.h"
|
||||
#include "../base/Color.h"
|
||||
#include "../base/Geometry.h"
|
||||
#include "../base/Memory.h"
|
||||
@@ -71,6 +72,8 @@ 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 prepare_ms;
|
||||
Psc::Value_Statistics draw_ms;
|
||||
@@ -78,6 +81,8 @@ struct Performance_Aggregate {
|
||||
Psc::Value_Statistics ready_wait_ms;
|
||||
Psc::Value_Statistics update_wait_ms;
|
||||
Psc::Value_Statistics paint_ms;
|
||||
Psc::Value_Statistics display_interval_ms;
|
||||
Psc::Value_Statistics effective_interval_ms;
|
||||
Psc::Value_Statistics frame_age_ms;
|
||||
Psc::Value_Statistics frame_total_ms;
|
||||
Psc::Value_Statistics cache_rebuild_ms;
|
||||
@@ -118,9 +123,11 @@ struct Performance_Aggregate {
|
||||
summary.transition_12_ns = frame.transition_12_ns;
|
||||
summary.update_request_id = frame.update_request_id;
|
||||
summary.update_request_time_ns = frame.update_request_time_ns;
|
||||
summary.update_posted_ns = frame.update_posted_ns;
|
||||
summary.transition_23_ns = frame.transition_23_ns;
|
||||
summary.paint_begin_ns = frame.paint_begin_ns;
|
||||
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.ready_queue_wait_ns = frame.ready_queue_wait_ns;
|
||||
@@ -151,6 +158,10 @@ struct Performance_Aggregate {
|
||||
std::uint64_t frame_render_time_ns = duration_ns(frame.render_begin_ns, frame.render_end_ns);
|
||||
render_time_ns += frame_render_time_ns;
|
||||
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);
|
||||
update_duration(prepare_ms, frame.prepare_begin_ns, frame.prepare_end_ns);
|
||||
update_duration(draw_ms, frame.draw_begin_ns, frame.draw_end_ns);
|
||||
@@ -158,6 +169,10 @@ struct Performance_Aggregate {
|
||||
update_duration(ready_wait_ms, frame.transition_12_ns, frame.paint_begin_ns ? frame.paint_begin_ns : frame.superseded_time_ns);
|
||||
update_duration(update_wait_ms, frame.update_request_time_ns, frame.paint_begin_ns);
|
||||
update_duration(paint_ms, frame.paint_begin_ns, frame.paint_end_ns);
|
||||
if (frame.refresh.display_frame_interval_ns)
|
||||
display_interval_ms.update(ns_to_ms(frame.refresh.display_frame_interval_ns));
|
||||
if (frame.refresh.effective_min_render_interval_ns)
|
||||
effective_interval_ms.update(ns_to_ms(frame.refresh.effective_min_render_interval_ns));
|
||||
update_duration(frame_age_ms, frame.core_data_time_ns, frame.paint_end_ns);
|
||||
update_duration(frame_total_ms, frame.render_request_ns, frame.transition_32_ns ? frame.transition_32_ns : frame.superseded_time_ns);
|
||||
cache_hit_count += frame.renderable_cache_hit_count;
|
||||
@@ -317,11 +332,15 @@ enum class Performance_Metric_Id : std::uint16_t {
|
||||
Current_Ms,
|
||||
Smooth_Ms,
|
||||
Variation_Ms,
|
||||
P50_Ms,
|
||||
P95_Ms,
|
||||
P99_Ms,
|
||||
Current_Percent,
|
||||
Smooth_Percent,
|
||||
Variation_Percent,
|
||||
P50_Percent,
|
||||
P95_Percent,
|
||||
P99_Percent,
|
||||
Raw_Input_Count,
|
||||
Prepared_Output_Count,
|
||||
Observed_Frame_Count,
|
||||
@@ -340,9 +359,12 @@ struct Performance_Field_Layout_State {
|
||||
struct Performance_Text_Span {
|
||||
bool value{};
|
||||
Performance_Metric_Id field_id = Performance_Metric_Id::Generic_Number;
|
||||
Performance_Text_Role role = Performance_Text_Role::Value;
|
||||
std::string text;
|
||||
double measured_width{};
|
||||
double reserved_width{};
|
||||
double x_offset{};
|
||||
int column_index{};
|
||||
};
|
||||
struct Performance_Line_Layout {
|
||||
std::vector<Performance_Text_Span> runs;
|
||||
@@ -403,10 +425,30 @@ inline void performance_atomic_add(std::atomic<double>& value, double delta) {
|
||||
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);
|
||||
|
||||
struct Performance_Metric_Cell {
|
||||
std::string label;
|
||||
std::string value;
|
||||
std::string unit;
|
||||
Performance_Metric_Id metric_id = Performance_Metric_Id::Generic_Number;
|
||||
Performance_Text_Role value_role = Performance_Text_Role::Value;
|
||||
};
|
||||
|
||||
struct Performance_Metric_Row {
|
||||
std::vector<Performance_Metric_Cell> cells;
|
||||
};
|
||||
|
||||
struct Performance_Metric_Section {
|
||||
std::string title;
|
||||
std::vector<Performance_Metric_Row> rows;
|
||||
};
|
||||
|
||||
struct Performance_Layout_Builder {
|
||||
Performance_Aggregate aggregate;
|
||||
std::vector<Performance_Metric_Section> sections;
|
||||
std::vector<std::string> lines;
|
||||
std::vector<Performance_Line_Layout> render_lines;
|
||||
std::vector<Performance_Display_Text_Run> text_runs;
|
||||
std::array<Performance_Field_Layout_State, performance_field_count> field_layout_states{};
|
||||
RectF panel_rect;
|
||||
RectF scroll_bar_rect;
|
||||
@@ -553,8 +595,12 @@ struct Performance_Layout_Builder {
|
||||
return Performance_Metric_Id::Smooth_Percent;
|
||||
if (ends_with(key, "_variation"))
|
||||
return Performance_Metric_Id::Variation_Percent;
|
||||
if (ends_with(key, "_p50"))
|
||||
return Performance_Metric_Id::P50_Percent;
|
||||
if (ends_with(key, "_p95"))
|
||||
return Performance_Metric_Id::P95_Percent;
|
||||
if (ends_with(key, "_p99"))
|
||||
return Performance_Metric_Id::P99_Percent;
|
||||
return Performance_Metric_Id::Current_Percent;
|
||||
}
|
||||
if (ends_with(key, "_instant"))
|
||||
@@ -563,8 +609,12 @@ struct Performance_Layout_Builder {
|
||||
return Performance_Metric_Id::Smooth_Ms;
|
||||
if (ends_with(key, "_variation") || ends_with(key, "_jitter"))
|
||||
return Performance_Metric_Id::Variation_Ms;
|
||||
if (ends_with(key, "_p50"))
|
||||
return Performance_Metric_Id::P50_Ms;
|
||||
if (ends_with(key, "_p95"))
|
||||
return Performance_Metric_Id::P95_Ms;
|
||||
if (ends_with(key, "_p99"))
|
||||
return Performance_Metric_Id::P99_Ms;
|
||||
if (ends_with(key, "_fps") || ends_with(key, "_count"))
|
||||
return Performance_Metric_Id::Generic_Count;
|
||||
return Performance_Metric_Id::Generic_Number;
|
||||
@@ -589,27 +639,17 @@ struct Performance_Layout_Builder {
|
||||
measure_run(canvas, run);
|
||||
}
|
||||
}
|
||||
static std::string marked_value(Performance_Metric_Id field_id, std::string text) {
|
||||
std::string result;
|
||||
result.reserve(text.size() + 12);
|
||||
result.push_back('\x1f');
|
||||
result += std::to_string(static_cast<std::uint16_t>(field_id));
|
||||
result.push_back('\x1e');
|
||||
result += text;
|
||||
result.push_back('\x1d');
|
||||
return result;
|
||||
}
|
||||
std::string number_text(const std::string& key, double number, int precision = 2) {
|
||||
return marked_value(field_id_from_key(key), value(number, precision));
|
||||
return value(number, precision);
|
||||
}
|
||||
std::string count_text(const std::string& key, std::uint64_t count) {
|
||||
return marked_value(field_id_from_key(key), std::to_string(count));
|
||||
return std::to_string(count);
|
||||
}
|
||||
std::string duration_line(const std::string& key, const std::string& name, const Psc::Value_Statistics& statistics) {
|
||||
return name + " current:" + number_text(key + "_instant", statistics.instant) + "ms smooth:" + number_text(key + "_smooth", statistics.smooth) + "ms var:" + number_text(key + "_variation", statistics.variation) + "ms P95:" + number_text(key + "_p95", statistics.p95) + "ms";
|
||||
return name + " current:" + number_text(key + "_instant", statistics.instant) + "ms smooth:" + number_text(key + "_smooth", statistics.smooth) + "ms P50:" + number_text(key + "_p50", statistics.p50) + "ms P95:" + number_text(key + "_p95", statistics.p95) + "ms P99:" + number_text(key + "_p99", statistics.p99) + "ms";
|
||||
}
|
||||
std::string percent_stat_line(const std::string& key, const std::string& name, const Psc::Value_Statistics& statistics) {
|
||||
return name + " current:" + number_text(key + "_instant", statistics.instant) + "% smooth:" + number_text(key + "_smooth", statistics.smooth) + "% var:" + number_text(key + "_variation", statistics.variation) + "% P95:" + number_text(key + "_p95", statistics.p95) + "%";
|
||||
return name + " current:" + number_text(key + "_instant", statistics.instant) + "% smooth:" + number_text(key + "_smooth", statistics.smooth) + "% P50:" + number_text(key + "_p50", statistics.p50) + "% P95:" + number_text(key + "_p95", statistics.p95) + "% P99:" + number_text(key + "_p99", statistics.p99) + "%";
|
||||
}
|
||||
std::string interval_line(const std::string& key, const std::string& name, double srtt, double jitter, double fps) {
|
||||
return name + " interval_smooth:" + number_text(key + "_srtt", srtt) + "ms var:" + number_text(key + "_jitter", jitter) + "ms actual:" + number_text(key + "_fps", fps) + "fps";
|
||||
@@ -650,15 +690,71 @@ struct Performance_Layout_Builder {
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
static std::string production_strategy_text(Low_Latency_Production_Strategy strategy) {
|
||||
switch (strategy) {
|
||||
case Low_Latency_Production_Strategy::Latency_Eager:
|
||||
static std::string production_mode_text(Low_Latency_Production_Mode mode) {
|
||||
switch (mode) {
|
||||
case Low_Latency_Production_Mode::Latency_Eager:
|
||||
return "latency_eager";
|
||||
case Low_Latency_Production_Strategy::Cost_Balanced:
|
||||
return "cost_balanced";
|
||||
case Low_Latency_Production_Mode::Consumer_Paced:
|
||||
return "consumer_paced";
|
||||
}
|
||||
return "latency_eager";
|
||||
}
|
||||
static std::string limit_reason_text(Low_Latency_Limit_Reason reason) {
|
||||
switch (reason) {
|
||||
case Low_Latency_Limit_Reason::User_Fps:
|
||||
return "user_fps";
|
||||
case Low_Latency_Limit_Reason::Consumer_Capacity:
|
||||
return "consumer_capacity";
|
||||
case Low_Latency_Limit_Reason::Render_Capacity:
|
||||
return "render_capacity";
|
||||
case Low_Latency_Limit_Reason::No_Work:
|
||||
return "no_work";
|
||||
case Low_Latency_Limit_Reason::Shutdown:
|
||||
return "shutdown";
|
||||
}
|
||||
return "user_fps";
|
||||
}
|
||||
static std::string consumer_confidence_text(Consumer_Confidence confidence) {
|
||||
switch (confidence) {
|
||||
case Consumer_Confidence::Low:
|
||||
return "low";
|
||||
case Consumer_Confidence::Medium:
|
||||
return "medium";
|
||||
case Consumer_Confidence::High:
|
||||
return "high";
|
||||
}
|
||||
return "low";
|
||||
}
|
||||
static std::string consumer_sample_result_text(Consumer_Sample_Result result) {
|
||||
switch (result) {
|
||||
case Consumer_Sample_Result::Accepted:
|
||||
return "accepted";
|
||||
case Consumer_Sample_Result::Rejected_No_Backlog:
|
||||
return "rejected_no_backlog";
|
||||
case Consumer_Sample_Result::Rejected_Producer_Starved:
|
||||
return "rejected_producer_starved";
|
||||
case Consumer_Sample_Result::Rejected_No_New_Frame:
|
||||
return "rejected_no_new_frame";
|
||||
case Consumer_Sample_Result::Rejected_Invalid_Timestamp:
|
||||
return "rejected_invalid_timestamp";
|
||||
}
|
||||
return "rejected_invalid_timestamp";
|
||||
}
|
||||
static std::string feedback_decision_text(Low_Latency_Feedback_Decision decision) {
|
||||
switch (decision) {
|
||||
case Low_Latency_Feedback_Decision::Hold_User_Period:
|
||||
return "hold_user_period";
|
||||
case Low_Latency_Feedback_Decision::Enter_Consumer_Paced:
|
||||
return "enter_consumer_paced";
|
||||
case Low_Latency_Feedback_Decision::Exit_Consumer_Paced:
|
||||
return "exit_consumer_paced";
|
||||
case Low_Latency_Feedback_Decision::Reject_Consumer_Sample:
|
||||
return "reject_consumer_sample";
|
||||
case Low_Latency_Feedback_Decision::Producer_Starved:
|
||||
return "producer_starved";
|
||||
}
|
||||
return "hold_user_period";
|
||||
}
|
||||
static std::string dirty_reason_text(std::uint32_t mask) {
|
||||
std::string result;
|
||||
auto append = [&result](const char* text) {
|
||||
@@ -735,7 +831,7 @@ struct Performance_Layout_Builder {
|
||||
return stat.render_count || stat.self_draw_count || stat.total_render_ms.times || stat.self_draw_ms.times;
|
||||
}
|
||||
std::string cache_duration_text(const std::string& key, const std::string& name, const Psc::Value_Statistics& statistics) {
|
||||
return name + " current:" + number_text(key + "_instant", statistics.instant) + "ms smooth:" + number_text(key + "_smooth", statistics.smooth) + "ms var:" + number_text(key + "_variation", statistics.variation) + "ms P95:" + number_text(key + "_p95", statistics.p95) + "ms";
|
||||
return name + " current:" + number_text(key + "_instant", statistics.instant) + "ms smooth:" + number_text(key + "_smooth", statistics.smooth) + "ms P50:" + number_text(key + "_p50", statistics.p50) + "ms P95:" + number_text(key + "_p95", statistics.p95) + "ms P99:" + number_text(key + "_p99", statistics.p99) + "ms";
|
||||
}
|
||||
void append_cache_node_lines(const Renderable_Cache_Aggregate_Stat& stat) {
|
||||
std::string key = cache_key(stat);
|
||||
@@ -826,25 +922,32 @@ struct Performance_Layout_Builder {
|
||||
std::uint64_t latest_render_time = frame_render_time_ns(frame);
|
||||
double cache_hit_ratio = aggregate_cache_total ? static_cast<double>(aggregate.cache_hit_count) * 100.0 / static_cast<double>(aggregate_cache_total) : 0.0;
|
||||
lines.clear();
|
||||
lines.push_back("[common] frame:" + count_text("frame_id", frame.frame_id) + " input_ver:" + count_text("input_version", frame.input_version) + " outcome:" + marked_value(Performance_Metric_Id::Outcome, outcome_text(frame.outcome)) + " dirty:" + dirty_reason_text(frame.dirty_reason_mask));
|
||||
lines.push_back("[low_latency] limit:" + limit_source_text(refresh.source) + " stable:" + bottleneck_text(refresh.stable_bottleneck) + " candidate:" + bottleneck_text(refresh.candidate_bottleneck) + " candidate_age:" + period_text("candidate_age", refresh.candidate_age_ns) + " samples:" + count_text("candidate_samples", refresh.candidate_samples));
|
||||
lines.push_back("[low_latency] bottleneck:" + bottleneck_text(refresh.bottleneck) + " strategy:" + production_strategy_text(refresh.production_strategy) + " effective:" + period_text("effective_interval", refresh.effective_min_render_interval_ns) + " efficiency:" + number_text("display_efficiency", refresh.display_efficiency * 100.0) + "%");
|
||||
lines.push_back("[low_latency] period input:" + period_text("input_period", refresh.input_period_ns) + " render:" + period_text("render_period", refresh.render_period_ns) + " paint:" + period_text("paint_period", refresh.paint_period_ns) + " user:" + period_text("user_period", refresh.user_period_ns) + " target:" + period_text("target_period", refresh.target_period_ns));
|
||||
lines.push_back("[low_latency] jitter input:" + ns_text("input_jitter", refresh.input_jitter_ns) + "ms render:" + ns_text("render_jitter", refresh.render_jitter_ns) + "ms paint:" + ns_text("paint_jitter", refresh.paint_jitter_ns) + "ms age:" + ns_text("frame_age_jitter", refresh.frame_age_jitter_ns) + "ms");
|
||||
lines.push_back("[low_latency] queue render:" + ns_text("render_queue_wait", frame.render_queue_wait_ns) + "ms ready:" + ns_text("ready_queue_wait", frame.ready_queue_wait_ns) + "ms present:" + ns_text("present_queue_wait", frame.present_queue_wait_ns) + "ms return:" + ns_text("paint_return_wait", frame.paint_return_wait_ns) + "ms");
|
||||
lines.push_back("[low_latency] counts presented:" + count_text("presented_feedback", refresh.presented_frame_count) + " superseded:" + count_text("superseded_feedback", refresh.superseded_frame_count) + " repeated:" + count_text("repeated_feedback", refresh.repeated_frame_count) + " underrun:" + count_text("underrun_feedback", refresh.underrun_count));
|
||||
lines.push_back("[common] frame:" + count_text("frame_id", frame.frame_id) + " input_ver:" + count_text("input_version", frame.input_version) + " outcome:" + outcome_text(frame.outcome) + " dirty:" + dirty_reason_text(frame.dirty_reason_mask));
|
||||
lines.push_back("[low_latency] mode:" + production_mode_text(refresh.production_mode) + " limit_reason:" + limit_reason_text(refresh.limit_reason) + " consumer_confidence:" + consumer_confidence_text(refresh.consumer_confidence) + " effective:" + period_text("effective_interval", refresh.effective_min_render_interval_ns) + " user:" + period_text("user_period", refresh.user_period_ns));
|
||||
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("[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(duration_line("render_queue_wait", "lifecycle queue_wait", aggregate.render_queue_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("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));
|
||||
lines.push_back(duration_line("ready_wait", "lifecycle ready_to_paint", aggregate.ready_wait_ms));
|
||||
lines.push_back(duration_line("update_wait", "lifecycle update_wait", aggregate.update_wait_ms));
|
||||
lines.push_back(duration_line("paint", "lifecycle paint", aggregate.paint_ms));
|
||||
lines.push_back(duration_line("update_wait", "timing update_to_paint", aggregate.update_wait_ms));
|
||||
lines.push_back(duration_line("paint", "timing paint_service", aggregate.paint_ms));
|
||||
lines.push_back(duration_line("display_interval", "timing display_interval", aggregate.display_interval_ms));
|
||||
lines.push_back(duration_line("effective_interval", "timing effective_interval", aggregate.effective_interval_ms));
|
||||
lines.push_back(duration_line("frame_age", "lifecycle input_to_display_age", aggregate.frame_age_ms));
|
||||
lines.push_back(duration_line("frame_total", "lifecycle e2e_total", aggregate.frame_total_ms));
|
||||
lines.push_back("cache hit:" + count_text("cache_hit_count", aggregate.cache_hit_count) + " miss:" + count_text("cache_miss_count", aggregate.cache_miss_count) + " ratio:" + number_text("cache_hit_ratio", cache_hit_ratio));
|
||||
@@ -860,11 +963,7 @@ struct Performance_Layout_Builder {
|
||||
lines.push_back("observed:" + count_text("observed_frame_count", aggregate.observed_frame_count) + " consumed:" + count_text("consumed_outcome_count", aggregate.consumed_outcome_count) + " superseded:" + count_text("superseded_outcome_count", aggregate.superseded_outcome_count) + " cancelled:" + count_text("cancelled_outcome_count", aggregate.cancelled_outcome_count) + " dropped:" + count_text("input_capacity_dropped", aggregate.input_capacity_dropped));
|
||||
}
|
||||
std::vector<std::string> stripped_lines() const {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(lines.size());
|
||||
for (const auto& line : lines)
|
||||
result.push_back(strip_markers(line));
|
||||
return result;
|
||||
return lines;
|
||||
}
|
||||
std::vector<std::string> display_lines() const {
|
||||
std::vector<std::string> result;
|
||||
@@ -873,12 +972,67 @@ struct Performance_Layout_Builder {
|
||||
result.push_back(render_line_text(line));
|
||||
return result;
|
||||
}
|
||||
static int resolve_column_count(double available_width) {
|
||||
return std::clamp(static_cast<int>(available_width / 320.0), 1, 4);
|
||||
}
|
||||
static bool warning_line(std::string_view line) {
|
||||
return (contains(line, "underrun:") && !contains(line, "underrun:0"))
|
||||
|| (contains(line, "dropped:") && !contains(line, "dropped:0"))
|
||||
|| (contains(line, "starvation_active:1"))
|
||||
|| (contains(line, "confidence:low") && contains(line, "consumer_paced"));
|
||||
}
|
||||
std::vector<Performance_Text_Span> plain_line_runs(const std::string& text, Canvas& canvas, int column_index, double x_offset) {
|
||||
std::vector<Performance_Text_Span> runs;
|
||||
auto append = [&](std::string value, Performance_Text_Role role, double offset) {
|
||||
if (value.empty())
|
||||
return;
|
||||
Performance_Text_Span run;
|
||||
run.text = std::move(value);
|
||||
run.role = role;
|
||||
run.column_index = column_index;
|
||||
run.x_offset = x_offset + offset;
|
||||
measure_run(canvas, run);
|
||||
runs.push_back(std::move(run));
|
||||
};
|
||||
std::size_t section_end = text.find(']');
|
||||
if (!text.empty() && text.front() == '[' && section_end != std::string::npos) {
|
||||
std::string section = text.substr(0, section_end + 1);
|
||||
append(section, Performance_Text_Role::Section, 0.0);
|
||||
double section_width = runs.empty() ? 0.0 : runs.back().measured_width + canvas.measure_text(" ").width;
|
||||
Performance_Text_Role value_role = warning_line(text) ? Performance_Text_Role::Warning : Performance_Text_Role::Value;
|
||||
append(text.substr(section_end + 1), value_role, section_width);
|
||||
return runs;
|
||||
}
|
||||
std::size_t colon = text.find(':');
|
||||
if (colon != std::string::npos) {
|
||||
std::string label = text.substr(0, colon + 1);
|
||||
append(label, Performance_Text_Role::Label, 0.0);
|
||||
double label_width = runs.empty() ? 0.0 : runs.back().measured_width + canvas.measure_text(" ").width;
|
||||
Performance_Text_Role value_role = warning_line(text) ? Performance_Text_Role::Warning : Performance_Text_Role::Value;
|
||||
append(text.substr(colon + 1), value_role, label_width);
|
||||
return runs;
|
||||
}
|
||||
append(text, Performance_Text_Role::Value, 0.0);
|
||||
return runs;
|
||||
}
|
||||
void build_render_lines(Canvas& canvas, double available_width) {
|
||||
render_lines.clear();
|
||||
text_runs.clear();
|
||||
available_width = std::max(1.0, available_width);
|
||||
for (const auto& raw_line : lines) {
|
||||
std::vector<Performance_Text_Span> runs = parse_runs(raw_line, canvas);
|
||||
append_wrapped_runs(std::move(runs), canvas, available_width);
|
||||
int column_count = resolve_column_count(available_width);
|
||||
double column_gap = column_count > 1 ? 18.0 : 0.0;
|
||||
double column_width = (available_width - column_gap * static_cast<double>(column_count - 1)) / static_cast<double>(column_count);
|
||||
std::size_t row_count = (lines.size() + static_cast<std::size_t>(column_count) - 1) / static_cast<std::size_t>(column_count);
|
||||
render_lines.resize(std::max<std::size_t>(1, row_count));
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
int column = static_cast<int>(i % static_cast<std::size_t>(column_count));
|
||||
std::size_t row = i / static_cast<std::size_t>(column_count);
|
||||
double x_offset = static_cast<double>(column) * (column_width + column_gap);
|
||||
std::vector<Performance_Text_Span> runs = plain_line_runs(lines[i], canvas, column, x_offset);
|
||||
for (auto& run : runs) {
|
||||
render_lines[row].width = std::max(render_lines[row].width, run.x_offset + run.measured_width);
|
||||
render_lines[row].runs.push_back(run);
|
||||
}
|
||||
}
|
||||
if (render_lines.empty()) {
|
||||
Performance_Line_Layout line;
|
||||
@@ -900,20 +1054,35 @@ struct Performance_Layout_Builder {
|
||||
if (content_width <= 0.0)
|
||||
content_width = std::min(measured_width, static_cast<double>(std::max(0, viewport_width)));
|
||||
}
|
||||
void draw_render_line(Canvas& canvas, const Performance_Line_Layout& line, double x, double y) const {
|
||||
double cursor = x;
|
||||
static Color role_color(const Performance_Overlay_Render_State& state, Performance_Text_Role role) {
|
||||
switch (role) {
|
||||
case Performance_Text_Role::Section:
|
||||
return state.section_color;
|
||||
case Performance_Text_Role::Label:
|
||||
return state.label_color;
|
||||
case Performance_Text_Role::Value:
|
||||
return state.value_color;
|
||||
case Performance_Text_Role::Unit:
|
||||
return state.unit_color;
|
||||
case Performance_Text_Role::Warning:
|
||||
return state.warning_color;
|
||||
case Performance_Text_Role::Muted:
|
||||
return state.muted_color;
|
||||
}
|
||||
return state.value_color;
|
||||
}
|
||||
void draw_render_line(Canvas& canvas, const Performance_Line_Layout& line, double x, double y, double line_height, const Performance_Overlay_Render_State& state) {
|
||||
for (const auto& run : line.runs) {
|
||||
if (run.text.empty())
|
||||
continue;
|
||||
if (run.value) {
|
||||
double text_x = cursor + std::max(0.0, run.reserved_width - run.measured_width);
|
||||
canvas.draw_text(PointF{text_x, y}, run.text);
|
||||
cursor += run.reserved_width;
|
||||
}
|
||||
else {
|
||||
canvas.draw_text(PointF{cursor, y}, run.text);
|
||||
cursor += run.measured_width;
|
||||
}
|
||||
canvas.set_pen(Pen{role_color(state, run.role)});
|
||||
canvas.draw_text(PointF{x + run.x_offset, y}, run.text);
|
||||
text_runs.push_back(Performance_Display_Text_Run{
|
||||
run.text,
|
||||
run.role,
|
||||
RectF{x + run.x_offset, y, run.measured_width, line_height},
|
||||
run.column_index
|
||||
});
|
||||
}
|
||||
}
|
||||
static double run_width(const Performance_Text_Span& run) {
|
||||
@@ -1005,35 +1174,12 @@ struct Performance_Layout_Builder {
|
||||
return best;
|
||||
return next_utf8_boundary(text, 0);
|
||||
}
|
||||
std::vector<Performance_Text_Span> parse_runs(const std::string& line, Canvas& canvas) {
|
||||
std::vector<Performance_Text_Span> legacy_plain_runs(const std::string& line, Canvas& canvas) {
|
||||
std::vector<Performance_Text_Span> runs;
|
||||
std::size_t pos = 0;
|
||||
while (pos < line.size()) {
|
||||
if (line[pos] == '\x1f') {
|
||||
std::size_t sep = line.find('\x1e', pos + 1);
|
||||
std::size_t end = sep == std::string::npos ? std::string::npos : line.find('\x1d', sep + 1);
|
||||
if (sep != std::string::npos && end != std::string::npos) {
|
||||
unsigned long id = std::stoul(line.substr(pos + 1, sep - pos - 1));
|
||||
Performance_Text_Span run;
|
||||
run.value = true;
|
||||
if (id < performance_field_count)
|
||||
run.field_id = static_cast<Performance_Metric_Id>(id);
|
||||
run.text = line.substr(sep + 1, end - sep - 1);
|
||||
measure_run(canvas, run);
|
||||
runs.push_back(std::move(run));
|
||||
pos = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::size_t next = line.find('\x1f', pos);
|
||||
Performance_Text_Span run;
|
||||
run.text = line.substr(pos, next == std::string::npos ? std::string::npos : next - pos);
|
||||
measure_run(canvas, run);
|
||||
runs.push_back(std::move(run));
|
||||
if (next == std::string::npos)
|
||||
break;
|
||||
pos = next;
|
||||
}
|
||||
Performance_Text_Span run;
|
||||
run.text = line;
|
||||
measure_run(canvas, run);
|
||||
runs.push_back(std::move(run));
|
||||
return runs;
|
||||
}
|
||||
void measure_run(Canvas& canvas, Performance_Text_Span& run) {
|
||||
@@ -1054,28 +1200,16 @@ struct Performance_Layout_Builder {
|
||||
text += run.text;
|
||||
return text;
|
||||
}
|
||||
static std::string strip_markers(const std::string& line) {
|
||||
std::string text;
|
||||
for (std::size_t pos = 0; pos < line.size();) {
|
||||
if (line[pos] == '\x1f') {
|
||||
std::size_t sep = line.find('\x1e', pos + 1);
|
||||
std::size_t end = sep == std::string::npos ? std::string::npos : line.find('\x1d', sep + 1);
|
||||
if (sep != std::string::npos && end != std::string::npos) {
|
||||
text.append(line, sep + 1, end - sep - 1);
|
||||
pos = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
text.push_back(line[pos]);
|
||||
++pos;
|
||||
}
|
||||
return text;
|
||||
static std::string plain_text(const std::string& line) {
|
||||
return line;
|
||||
}
|
||||
};
|
||||
|
||||
struct Performance_Overlay_Private {
|
||||
std::atomic_bool enabled_value{false};
|
||||
std::shared_ptr<Performance_Worker_State> metrics_worker;
|
||||
mutable std::mutex identity_mutex;
|
||||
Performance_Log_Plot_Identity log_identity;
|
||||
|
||||
Performance_Overlay_Private()
|
||||
: metrics_worker(std::make_shared<Performance_Worker_State>()) {}
|
||||
@@ -1098,6 +1232,49 @@ struct Performance_Overlay_Private {
|
||||
schedule_performance_metrics_worker(metrics_worker);
|
||||
}
|
||||
|
||||
void set_identity(Performance_Log_Plot_Identity identity) {
|
||||
std::lock_guard<std::mutex> lock(identity_mutex);
|
||||
log_identity = std::move(identity);
|
||||
}
|
||||
|
||||
Performance_Log_Plot_Identity identity() const {
|
||||
std::lock_guard<std::mutex> lock(identity_mutex);
|
||||
return log_identity;
|
||||
}
|
||||
|
||||
static const char* feedback_outcome_event(Frame_Outcome outcome) {
|
||||
switch (outcome) {
|
||||
case Frame_Outcome::Presented:
|
||||
return "PaintingAdvanced";
|
||||
case Frame_Outcome::Superseded:
|
||||
return "MiddleSuperseded";
|
||||
case Frame_Outcome::Render_Acquire_Dropped:
|
||||
return "RenderAcquireDropped";
|
||||
case Frame_Outcome::Publish_Dropped:
|
||||
return "PublishDropped";
|
||||
case Frame_Outcome::Paint_Acquire_Dropped:
|
||||
return "PaintAcquireDropped";
|
||||
case Frame_Outcome::Worker_Budget_Dropped:
|
||||
return "WorkerBudgetDropped";
|
||||
case Frame_Outcome::Cancelled:
|
||||
return "Cancelled";
|
||||
}
|
||||
return "UnknownOutcome";
|
||||
}
|
||||
|
||||
void enqueue_frame_log(std::shared_ptr<const Frame_Lifecycle_Record> frame) {
|
||||
if (!frame)
|
||||
return;
|
||||
Performance_Log_Plot_Identity plot = identity();
|
||||
performance_log_service().enqueue_frame(plot, frame);
|
||||
}
|
||||
|
||||
void enqueue_feedback_log(std::shared_ptr<const Frame_Feedback_Event_Record> event) {
|
||||
if (!event)
|
||||
return;
|
||||
performance_log_service().enqueue_feedback_event(identity(), *event);
|
||||
}
|
||||
|
||||
static void run_metrics_worker(const std::shared_ptr<Performance_Worker_State>& state) {
|
||||
if (!state)
|
||||
return;
|
||||
@@ -1201,10 +1378,11 @@ struct Performance_Overlay_Private {
|
||||
canvas.set_clip_rect(text_rect);
|
||||
int first_line = std::max(0, static_cast<int>(std::floor(scroll / line_height)));
|
||||
double y = static_cast<double>(state.top_margin) - (scroll - static_cast<double>(first_line) * line_height);
|
||||
layout.text_runs.clear();
|
||||
for (int i = first_line; i < static_cast<int>(layout.render_lines.size()) && y < text_rect.bottom(); ++i, y += line_height) {
|
||||
if (y + line_height < text_rect.y)
|
||||
continue;
|
||||
layout.draw_render_line(canvas, layout.render_lines[static_cast<std::size_t>(i)], text_rect.x, y);
|
||||
layout.draw_render_line(canvas, layout.render_lines[static_cast<std::size_t>(i)], text_rect.x, y, line_height, state);
|
||||
}
|
||||
canvas.reset_clip();
|
||||
layout.update_scroll_bar_rects(&state, max_scroll, scroll, content_viewport_height, content_height);
|
||||
@@ -1223,6 +1401,7 @@ struct Performance_Overlay_Private {
|
||||
snapshot->max_scroll_offset = max_scroll;
|
||||
snapshot->line_height = line_height;
|
||||
snapshot->image = std::move(image);
|
||||
snapshot->text_runs = layout.text_runs;
|
||||
worker_state.last_scroll_track_rect = layout.scroll_bar_rect;
|
||||
worker_state.last_scroll_thumb_rect = layout.scroll_thumb_rect;
|
||||
worker_state.last_content_viewport_height = content_viewport_height;
|
||||
|
||||
Reference in New Issue
Block a user