架构微调
This commit is contained in:
@@ -546,6 +546,7 @@ Frame_Observer_Snapshot Plot_Core::frame_observer_snapshot() const {
|
||||
snapshot.consumer_sample_interval_ns = state.consumer_sample_interval_ns;
|
||||
snapshot.consumer_smoothed_interval_ns = state.consumer_smoothed_interval_ns;
|
||||
snapshot.consumer_variation_ns = state.consumer_variation_ns;
|
||||
snapshot.consumer_safety_interval_ns = state.consumer_safety_interval_ns;
|
||||
snapshot.consumer_interval_ns = state.consumer_interval_ns;
|
||||
snapshot.next_refresh_interval_ns = state.next_refresh_interval_ns;
|
||||
snapshot.end_to_end_ns = state.end_to_end_ns;
|
||||
|
||||
@@ -56,6 +56,7 @@ struct Frame_Observer_Snapshot {
|
||||
std::uint64_t consumer_sample_interval_ns{};
|
||||
std::uint64_t consumer_smoothed_interval_ns{};
|
||||
std::uint64_t consumer_variation_ns{};
|
||||
std::uint64_t consumer_safety_interval_ns{};
|
||||
std::uint64_t consumer_interval_ns{};
|
||||
std::uint64_t next_refresh_interval_ns{};
|
||||
std::uint64_t paint_lease_wait_ns{};
|
||||
|
||||
@@ -96,6 +96,7 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2
|
||||
EXPECT_EQ(snapshot.consumer_sample_interval_ns, 40'000'000u);
|
||||
EXPECT_EQ(snapshot.consumer_smoothed_interval_ns, 40'000'000u);
|
||||
EXPECT_EQ(snapshot.consumer_variation_ns, 0u);
|
||||
EXPECT_EQ(snapshot.consumer_safety_interval_ns, 40'000'000u);
|
||||
EXPECT_EQ(snapshot.consumer_interval_ns, 40'000'000u);
|
||||
EXPECT_EQ(snapshot.next_refresh_interval_ns, 40'000'000u);
|
||||
EXPECT_EQ(plot.refresh_feedback_snapshot().next_refresh_interval_ns, 40'000'000u);
|
||||
|
||||
@@ -78,6 +78,7 @@ public:
|
||||
std::uint64_t consumer_sample_interval_ns{};
|
||||
std::uint64_t consumer_smoothed_interval_ns{};
|
||||
std::uint64_t consumer_variation_ns{};
|
||||
std::uint64_t consumer_safety_interval_ns{};
|
||||
std::uint64_t consumer_interval_ns{};
|
||||
std::uint64_t end_to_end_ns{};
|
||||
std::uint64_t next_refresh_interval_ns{};
|
||||
|
||||
@@ -248,6 +248,7 @@ void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::clear_consumer_feedback
|
||||
state_.consumer_sample_interval_ns = 0;
|
||||
state_.consumer_smoothed_interval_ns = 0;
|
||||
state_.consumer_variation_ns = 0;
|
||||
state_.consumer_safety_interval_ns = 0;
|
||||
state_.consumer_interval_ns = 0;
|
||||
update_refresh_control();
|
||||
}
|
||||
@@ -368,29 +369,31 @@ void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_refresh_control(
|
||||
}
|
||||
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
|
||||
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_consumer_estimator(std::uint64_t sample_interval_ns) {
|
||||
const std::uint64_t previous_sample = state_.consumer_sample_interval_ns;
|
||||
state_.consumer_sample_interval_ns = sample_interval_ns;
|
||||
if (!state_.consumer_feedback_enabled) {
|
||||
state_.consumer_feedback_enabled = true;
|
||||
state_.consumer_smoothed_interval_ns = sample_interval_ns;
|
||||
state_.consumer_variation_ns = 0;
|
||||
} else {
|
||||
const std::uint64_t smoothed = state_.consumer_smoothed_interval_ns;
|
||||
const std::uint64_t deviation = sample_interval_ns >= smoothed ?
|
||||
sample_interval_ns - smoothed : smoothed - sample_interval_ns;
|
||||
if (deviation >= state_.consumer_variation_ns)
|
||||
state_.consumer_variation_ns += (deviation - state_.consumer_variation_ns) / 4;
|
||||
const std::uint64_t sample_delta = sample_interval_ns >= previous_sample ?
|
||||
sample_interval_ns - previous_sample : previous_sample - sample_interval_ns;
|
||||
if (sample_delta >= state_.consumer_variation_ns)
|
||||
state_.consumer_variation_ns += (sample_delta - state_.consumer_variation_ns) / 16;
|
||||
else
|
||||
state_.consumer_variation_ns -= (state_.consumer_variation_ns - deviation) / 4;
|
||||
state_.consumer_variation_ns -= (state_.consumer_variation_ns - sample_delta) / 16;
|
||||
if (sample_interval_ns >= state_.consumer_smoothed_interval_ns)
|
||||
state_.consumer_smoothed_interval_ns += (sample_interval_ns - state_.consumer_smoothed_interval_ns) / 8;
|
||||
state_.consumer_smoothed_interval_ns += (sample_interval_ns - state_.consumer_smoothed_interval_ns) / 2;
|
||||
else
|
||||
state_.consumer_smoothed_interval_ns -= (state_.consumer_smoothed_interval_ns - sample_interval_ns) / 8;
|
||||
}
|
||||
const auto maximum = std::numeric_limits<std::uint64_t>::max();
|
||||
const std::uint64_t variation_margin = state_.consumer_variation_ns > maximum / 4 ?
|
||||
maximum : state_.consumer_variation_ns * 4;
|
||||
state_.consumer_interval_ns = state_.consumer_smoothed_interval_ns > maximum - variation_margin ?
|
||||
maximum : state_.consumer_smoothed_interval_ns + variation_margin;
|
||||
const std::uint64_t safety_interval = state_.consumer_smoothed_interval_ns > maximum - variation_margin ?
|
||||
maximum : state_.consumer_smoothed_interval_ns + variation_margin;
|
||||
state_.consumer_interval_ns = std::max(state_.consumer_smoothed_interval_ns, sample_interval_ns);
|
||||
state_.consumer_safety_interval_ns = std::max(state_.consumer_interval_ns, safety_interval);
|
||||
}
|
||||
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
|
||||
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_state(const Frame& frame) {
|
||||
|
||||
+24
-4
@@ -125,7 +125,7 @@ TEST(low_latency_strategy_test, clamps_extremely_small_frequency_interval_withou
|
||||
}
|
||||
EXPECT_EQ(strategy.state().target_interval_ns, std::numeric_limits<std::uint64_t>::max());
|
||||
}
|
||||
TEST(low_latency_strategy_test, smooths_consumer_feedback_in_kernel) {
|
||||
TEST(low_latency_strategy_test, separates_consumer_rate_from_jitter_safety_margin) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
@@ -136,15 +136,34 @@ TEST(low_latency_strategy_test, smooths_consumer_feedback_in_kernel) {
|
||||
EXPECT_EQ(state.consumer_sample_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_smoothed_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_variation_ns, 0u);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 10'000'000u);
|
||||
strategy.set_consumer_feedback({14'000'000});
|
||||
state = strategy.state();
|
||||
EXPECT_EQ(state.consumer_sample_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.consumer_smoothed_interval_ns, 10'500'000u);
|
||||
EXPECT_EQ(state.consumer_variation_ns, 1'000'000u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 14'500'000u);
|
||||
EXPECT_EQ(state.consumer_smoothed_interval_ns, 12'000'000u);
|
||||
EXPECT_EQ(state.consumer_variation_ns, 250'000u);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::consumer_limited);
|
||||
}
|
||||
TEST(low_latency_strategy_test, consumer_jitter_changes_safety_deadline_without_becoming_rate_limit) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
strategy.set_frequency_hz(1000.0);
|
||||
strategy.set_consumer_feedback({20'000'000});
|
||||
strategy.set_consumer_feedback({22'000'000});
|
||||
strategy.set_consumer_feedback({20'000'000});
|
||||
const auto state = strategy.state();
|
||||
EXPECT_EQ(state.consumer_sample_interval_ns, 20'000'000u);
|
||||
EXPECT_EQ(state.consumer_smoothed_interval_ns, 20'875'000u);
|
||||
EXPECT_EQ(state.consumer_variation_ns, 242'187u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 20'875'000u);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 21'843'748u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 20'875'000u);
|
||||
}
|
||||
TEST(low_latency_strategy_test, allows_frequency_and_consumer_limits_to_be_cleared) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
@@ -154,6 +173,7 @@ TEST(low_latency_strategy_test, allows_frequency_and_consumer_limits_to_be_clear
|
||||
strategy.clear_consumer_feedback();
|
||||
auto state = strategy.state();
|
||||
EXPECT_FALSE(state.consumer_feedback_enabled);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 0u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 0u);
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
|
||||
strategy.clear_frequency_limit();
|
||||
|
||||
@@ -233,6 +233,9 @@ public:
|
||||
std::uint64_t frame_request_timeout_count,
|
||||
double frame_round_trip_ms,
|
||||
double display_interval_ms,
|
||||
double display_interval_latest_ms,
|
||||
double display_interval_p95_ms,
|
||||
double display_jitter_ms,
|
||||
std::uint64_t overwritten_pixel_frames,
|
||||
double last_pixel_receive_age_ms,
|
||||
double last_pixel_change_age_ms) noexcept {
|
||||
@@ -248,6 +251,12 @@ public:
|
||||
std::max(0.0, frame_round_trip_ms) : 0.0;
|
||||
client_display_interval_ms_ = std::isfinite(display_interval_ms) ?
|
||||
std::max(0.0, display_interval_ms) : 0.0;
|
||||
client_display_interval_latest_ms_ = std::isfinite(display_interval_latest_ms) ?
|
||||
std::max(0.0, display_interval_latest_ms) : 0.0;
|
||||
client_display_interval_p95_ms_ = std::isfinite(display_interval_p95_ms) ?
|
||||
std::max(0.0, display_interval_p95_ms) : 0.0;
|
||||
client_display_jitter_ms_ = std::isfinite(display_jitter_ms) ?
|
||||
std::max(0.0, display_jitter_ms) : 0.0;
|
||||
client_overwritten_pixel_frames_ = overwritten_pixel_frames;
|
||||
client_last_pixel_receive_age_ms_ = std::isfinite(last_pixel_receive_age_ms) ?
|
||||
std::max(0.0, last_pixel_receive_age_ms) : 0.0;
|
||||
@@ -255,8 +264,9 @@ public:
|
||||
std::max(0.0, last_pixel_change_age_ms) : 0.0;
|
||||
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency &&
|
||||
bool_value(state_, "consumer_feedback_enabled")) {
|
||||
const auto interval_ns = std::max(milliseconds_to_ns(client_frame_round_trip_ms_),
|
||||
milliseconds_to_ns(client_display_interval_ms_));
|
||||
const auto display_interval_ns = milliseconds_to_ns(client_display_interval_ms_);
|
||||
const auto interval_ns = display_interval_ns != 0 ?
|
||||
display_interval_ns : milliseconds_to_ns(client_frame_round_trip_ms_);
|
||||
if (interval_ns != 0)
|
||||
plot_.set_consumer_feedback({interval_ns});
|
||||
}
|
||||
@@ -648,6 +658,7 @@ public:
|
||||
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
|
||||
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
|
||||
{"consumer_variation_ns", observer.consumer_variation_ns},
|
||||
{"consumer_safety_interval_ns", observer.consumer_safety_interval_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"next_refresh_interval_ns", observer.next_refresh_interval_ns},
|
||||
{"paint_lease_wait_ns", observer.paint_lease_wait_ns},
|
||||
@@ -670,6 +681,9 @@ public:
|
||||
{"frame_request_timeout_count", client_frame_request_timeout_count_},
|
||||
{"frame_round_trip_ms", client_frame_round_trip_ms_},
|
||||
{"display_interval_ms", client_display_interval_ms_},
|
||||
{"display_interval_latest_ms", client_display_interval_latest_ms_},
|
||||
{"display_interval_p95_ms", client_display_interval_p95_ms_},
|
||||
{"display_jitter_ms", client_display_jitter_ms_},
|
||||
{"overwritten_pixel_frames", client_overwritten_pixel_frames_},
|
||||
{"last_pixel_receive_age_ms", client_last_pixel_receive_age_ms_},
|
||||
{"last_pixel_change_age_ms", client_last_pixel_change_age_ms_}
|
||||
@@ -697,6 +711,7 @@ public:
|
||||
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
|
||||
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
|
||||
{"consumer_variation_ns", observer.consumer_variation_ns},
|
||||
{"consumer_safety_interval_ns", observer.consumer_safety_interval_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"scheduler_interval_ns", scheduler_interval_ns},
|
||||
{"scheduler_frequency_hz", scheduler_interval_ns == 0 ? 0.0 :
|
||||
@@ -956,6 +971,7 @@ private:
|
||||
{"consumer_feedback_sample_interval_ns", low_latency ? observer.consumer_sample_interval_ns : 0},
|
||||
{"consumer_feedback_smoothed_interval_ns", low_latency ? observer.consumer_smoothed_interval_ns : 0},
|
||||
{"consumer_feedback_variation_ns", low_latency ? observer.consumer_variation_ns : 0},
|
||||
{"consumer_feedback_safety_interval_ns", low_latency ? observer.consumer_safety_interval_ns : 0},
|
||||
{"consumer_feedback_interval_ns", low_latency ? observer.consumer_interval_ns : 0},
|
||||
{"backend_render_fps", recent_rate(render_history_, now)},
|
||||
{"pixel_response_fps", pixel_fps},
|
||||
@@ -966,6 +982,9 @@ private:
|
||||
{"client_frame_request_timeout_count", client_frame_request_timeout_count_},
|
||||
{"client_frame_round_trip_ms", client_frame_round_trip_ms_},
|
||||
{"client_display_interval_ms", client_display_interval_ms_},
|
||||
{"client_display_interval_latest_ms", client_display_interval_latest_ms_},
|
||||
{"client_display_interval_p95_ms", client_display_interval_p95_ms_},
|
||||
{"client_display_jitter_ms", client_display_jitter_ms_},
|
||||
{"client_overwritten_pixel_frames", client_overwritten_pixel_frames_},
|
||||
{"client_last_pixel_receive_age_ms", client_last_pixel_receive_age_ms_},
|
||||
{"client_last_pixel_change_age_ms", client_last_pixel_change_age_ms_},
|
||||
@@ -994,6 +1013,7 @@ private:
|
||||
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
|
||||
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
|
||||
{"consumer_variation_ns", observer.consumer_variation_ns},
|
||||
{"consumer_safety_interval_ns", observer.consumer_safety_interval_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"next_refresh_interval_ns", observer.next_refresh_interval_ns},
|
||||
{"paint_lease_wait_ns", observer.paint_lease_wait_ns},
|
||||
@@ -1497,6 +1517,9 @@ private:
|
||||
std::uint64_t client_frame_request_timeout_count_{};
|
||||
double client_frame_round_trip_ms_{};
|
||||
double client_display_interval_ms_{};
|
||||
double client_display_interval_latest_ms_{};
|
||||
double client_display_interval_p95_ms_{};
|
||||
double client_display_jitter_ms_{};
|
||||
std::uint64_t client_overwritten_pixel_frames_{};
|
||||
double client_last_pixel_receive_age_ms_{};
|
||||
double client_last_pixel_change_age_ms_{};
|
||||
@@ -1560,6 +1583,9 @@ struct Gallery_Plot_Session::Impl {
|
||||
unsigned_metric("frame_request_timeout_count"),
|
||||
finite_metric("frame_round_trip_ms"),
|
||||
finite_metric("display_interval_ms"),
|
||||
finite_metric("display_interval_latest_ms"),
|
||||
finite_metric("display_interval_p95_ms"),
|
||||
finite_metric("display_jitter_ms"),
|
||||
unsigned_metric("overwritten_pixel_frames"),
|
||||
finite_metric("last_pixel_receive_age_ms"),
|
||||
finite_metric("last_pixel_change_age_ms"));
|
||||
|
||||
@@ -849,13 +849,13 @@ TEST(RenderiveWebGallery, ThreeFrameStrategiesExposeTheirRealActionsAndObservers
|
||||
EXPECT_GT(limit.at("bottleneck_duration_ns").get<std::uint64_t>(), 1U);
|
||||
EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("paint_lease_wait_ns"));
|
||||
EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("render_finish_state_wait_ns"));
|
||||
constexpr std::array<std::string_view, 32> observer_fields{
|
||||
constexpr std::array<std::string_view, 33> observer_fields{
|
||||
"mode", "last_event", "limit_state", "configured_frequency_hz", "observation_count",
|
||||
"produced_frame_count", "consumed_frame_count", "dropped_frame_count",
|
||||
"failed_operation_count", "pending_frame_count", "latest_sequence",
|
||||
"paint_duration_ns", "render_duration_ns", "target_interval_ns", "frequency_limit_enabled",
|
||||
"consumer_feedback_enabled", "bottleneck_duration_ns", "consumer_sample_interval_ns",
|
||||
"consumer_smoothed_interval_ns", "consumer_variation_ns", "consumer_interval_ns",
|
||||
"consumer_smoothed_interval_ns", "consumer_variation_ns", "consumer_safety_interval_ns", "consumer_interval_ns",
|
||||
"next_refresh_interval_ns", "paint_lease_wait_ns",
|
||||
"paint_state_wait_ns", "publish_state_wait_ns", "ready_wait_ns",
|
||||
"frame_age_at_render_ns", "render_lease_wait_ns", "render_state_wait_ns",
|
||||
@@ -1065,7 +1065,7 @@ TEST(RenderiveWebGallery, WebRuntimeMetricsPublishKernelSmoothedConsumerFeedback
|
||||
"case_state");
|
||||
auto observed = response_json(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Observe,
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":50,"presentation_fps":50,"websocket_buffered_bytes":0,"changed_pixel_frames":10,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":2,"display_interval_ms":20,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":50,"presentation_fps":50,"websocket_buffered_bytes":0,"changed_pixel_frames":10,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":80,"display_interval_ms":20,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
|
||||
auto observer = observed.at("telemetry").at("kernel_observer");
|
||||
auto limit = observed.at("telemetry").at("low_latency_limit");
|
||||
EXPECT_TRUE(observer.at("frequency_limit_enabled").get<bool>());
|
||||
@@ -1073,6 +1073,7 @@ TEST(RenderiveWebGallery, WebRuntimeMetricsPublishKernelSmoothedConsumerFeedback
|
||||
EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 20'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_smoothed_interval_ns"), 20'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_variation_ns"), 0);
|
||||
EXPECT_EQ(observer.at("consumer_safety_interval_ns"), 20'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_interval_ns"), 20'000'000);
|
||||
EXPECT_EQ(observer.at("limit_state"), "consumer_limited");
|
||||
EXPECT_EQ(limit.at("consumer_feedback_source"), "web_runtime");
|
||||
@@ -1081,9 +1082,10 @@ TEST(RenderiveWebGallery, WebRuntimeMetricsPublishKernelSmoothedConsumerFeedback
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":45,"presentation_fps":45,"websocket_buffered_bytes":0,"changed_pixel_frames":20,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":4,"display_interval_ms":22,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
|
||||
observer = observed.at("telemetry").at("kernel_observer");
|
||||
EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 22'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_smoothed_interval_ns"), 20'250'000);
|
||||
EXPECT_EQ(observer.at("consumer_variation_ns"), 500'000);
|
||||
EXPECT_EQ(observer.at("consumer_interval_ns"), 22'250'000);
|
||||
EXPECT_EQ(observer.at("consumer_smoothed_interval_ns"), 21'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_variation_ns"), 125'000);
|
||||
EXPECT_EQ(observer.at("consumer_safety_interval_ns"), 22'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_interval_ns"), 22'000'000);
|
||||
observed = response_json(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Patch,
|
||||
R"({"category":"event","type":"gallery_patch","patch":{"consumer_feedback_enabled":false,"frequency_limit_enabled":false}})")));
|
||||
@@ -1186,7 +1188,7 @@ TEST(RenderiveWebGallery, ProductionLowLatencySessionRendersWithoutPixelPulls) {
|
||||
}));
|
||||
const auto observed = session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Observe,
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":120,"presentation_fps":60,"websocket_buffered_bytes":0,"changed_pixel_frames":17,"duplicate_pixel_frames":3,"frame_request_timeout_count":2,"frame_round_trip_ms":4.5,"display_interval_ms":16.7,"overwritten_pixel_frames":5,"last_pixel_receive_age_ms":8.5,"last_pixel_change_age_ms":12.5}})"));
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":120,"presentation_fps":60,"websocket_buffered_bytes":0,"changed_pixel_frames":17,"duplicate_pixel_frames":3,"frame_request_timeout_count":2,"frame_round_trip_ms":4.5,"display_interval_ms":16.7,"display_interval_latest_ms":16.9,"display_interval_p95_ms":17.4,"display_jitter_ms":0.7,"overwritten_pixel_frames":5,"last_pixel_receive_age_ms":8.5,"last_pixel_change_age_ms":12.5}})"));
|
||||
ASSERT_TRUE(observed.has_value());
|
||||
const auto telemetry = parse_json(observed->payload).at("telemetry");
|
||||
const auto& performance = telemetry.at("performance");
|
||||
@@ -1206,6 +1208,9 @@ TEST(RenderiveWebGallery, ProductionLowLatencySessionRendersWithoutPixelPulls) {
|
||||
EXPECT_EQ(telemetry.at("client_performance").at("frame_request_timeout_count"), 2);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("frame_round_trip_ms"), 4.5);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_ms"), 16.7);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_latest_ms"), 16.9);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_p95_ms"), 17.4);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_jitter_ms"), 0.7);
|
||||
EXPECT_EQ(telemetry.at("client_performance").at("overwritten_pixel_frames"), 5);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("last_pixel_receive_age_ms"), 8.5);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("last_pixel_change_age_ms"), 12.5);
|
||||
|
||||
+50
-5
@@ -29,6 +29,10 @@ let streamsPaused = false;
|
||||
let toastTimer = 0;
|
||||
let lastAnimationFrameAt = 0;
|
||||
let displayIntervalMs = 0;
|
||||
let displayIntervalLatestMs = 0;
|
||||
let displayIntervalP95Ms = 0;
|
||||
let displayJitterMs = 0;
|
||||
const displayIntervalSamples = [];
|
||||
|
||||
const message = (type, payload = {}) => JSON.stringify({category: "event", type, ...payload});
|
||||
const setConnection = (state, text) => {
|
||||
@@ -63,6 +67,25 @@ function formatNanoseconds(value) {
|
||||
if (nanoseconds < 1_000_000) return `${(nanoseconds / 1_000).toFixed(2)} µs`;
|
||||
return `${(nanoseconds / 1_000_000).toFixed(3)} ms`;
|
||||
}
|
||||
function updateDisplayTiming(time) {
|
||||
if (lastAnimationFrameAt > 0) {
|
||||
displayIntervalLatestMs = Math.max(0, time - lastAnimationFrameAt);
|
||||
displayIntervalSamples.push({time, interval: displayIntervalLatestMs});
|
||||
}
|
||||
lastAnimationFrameAt = time;
|
||||
while (displayIntervalSamples.length && displayIntervalSamples[0].time < time - 1000) displayIntervalSamples.shift();
|
||||
if (!displayIntervalSamples.length) {
|
||||
displayIntervalMs = 0;
|
||||
displayIntervalP95Ms = 0;
|
||||
displayJitterMs = 0;
|
||||
return;
|
||||
}
|
||||
const values = displayIntervalSamples.map(sample => sample.interval).sort((left, right) => left - right);
|
||||
const percentile = ratio => values[Math.min(values.length - 1, Math.floor((values.length - 1) * ratio))];
|
||||
displayIntervalMs = percentile(0.5);
|
||||
displayIntervalP95Ms = percentile(0.95);
|
||||
displayJitterMs = Math.max(0, displayIntervalP95Ms - displayIntervalMs);
|
||||
}
|
||||
|
||||
class GalleryCard {
|
||||
constructor(definition, mode) {
|
||||
@@ -251,6 +274,7 @@ class GalleryCard {
|
||||
this.node.querySelector(".perf-timeouts").textContent = this.frameTimeoutCount.toLocaleString();
|
||||
this.node.querySelector(".perf-pixel-age").textContent = this.lastPixelReceivedAt ? `${Math.max(0, performance.now() - this.lastPixelReceivedAt).toFixed(0)} ms` : "—";
|
||||
this.updateObserverDashboard(observer);
|
||||
this.updateClientDashboard(this.telemetry.client_performance || {});
|
||||
this.updateMotionStatus();
|
||||
const setLimitFlag = (selector, enabled, active, duration) => {
|
||||
const flag = this.node.querySelector(selector);
|
||||
@@ -266,19 +290,36 @@ class GalleryCard {
|
||||
const nanosecondFields = new Set([
|
||||
"paint_duration_ns", "render_duration_ns", "target_interval_ns",
|
||||
"bottleneck_duration_ns", "consumer_sample_interval_ns", "consumer_smoothed_interval_ns",
|
||||
"consumer_variation_ns", "consumer_interval_ns", "next_refresh_interval_ns", "paint_lease_wait_ns",
|
||||
"consumer_variation_ns", "consumer_safety_interval_ns", "consumer_interval_ns", "next_refresh_interval_ns", "paint_lease_wait_ns",
|
||||
"paint_state_wait_ns", "publish_state_wait_ns", "ready_wait_ns",
|
||||
"frame_age_at_render_ns", "render_lease_wait_ns", "render_state_wait_ns",
|
||||
"render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns"
|
||||
]);
|
||||
for (const [name, field] of this.observerFields) {
|
||||
const raw = observer[name] ?? (name === "limit_state" ? "not_applicable" : name === "last_event" ? "none" : 0);
|
||||
if (name === "consumer_effective_fps") {
|
||||
const interval = Number(observer.consumer_interval_ns || 0);
|
||||
field.textContent = interval > 0 ? `${(1e9 / interval).toFixed(2)} FPS` : "0 FPS";
|
||||
field.title = interval > 0 ? `${1e9 / interval} FPS` : "0 FPS";
|
||||
continue;
|
||||
}
|
||||
field.textContent = nanosecondFields.has(name) ? formatNanoseconds(raw) :
|
||||
name === "configured_frequency_hz" ? (observer.frequency_limit_enabled === false ? "已关闭" : `${Number(raw || 0).toLocaleString()} Hz`) :
|
||||
typeof raw === "number" ? raw.toLocaleString() : String(raw);
|
||||
field.title = nanosecondFields.has(name) ? `${Number(raw || 0).toLocaleString()} ns` : String(raw);
|
||||
}
|
||||
}
|
||||
updateClientDashboard(client) {
|
||||
for (const field of this.node.querySelectorAll("[data-client-field]")) {
|
||||
const name = field.dataset.clientField;
|
||||
const raw = Number(client[name] || 0);
|
||||
if (name.endsWith("_fps")) field.textContent = `${raw.toFixed(2)} FPS`;
|
||||
else if (name.endsWith("_ms")) field.textContent = `${raw.toFixed(3)} ms`;
|
||||
else if (name === "websocket_buffered_bytes") field.textContent = `${raw.toLocaleString()} B`;
|
||||
else field.textContent = raw.toLocaleString();
|
||||
field.title = String(raw);
|
||||
}
|
||||
}
|
||||
pixelSignature(buffer, width, height, stride) {
|
||||
const bytes = new Uint8Array(buffer, 16, stride * height);
|
||||
let hash = (2166136261 ^ width ^ (height << 16)) >>> 0;
|
||||
@@ -409,6 +450,9 @@ class GalleryCard {
|
||||
frame_request_timeout_count: this.frameTimeoutCount,
|
||||
frame_round_trip_ms: this.frameRoundTripMs,
|
||||
display_interval_ms: displayIntervalMs,
|
||||
display_interval_latest_ms: displayIntervalLatestMs,
|
||||
display_interval_p95_ms: displayIntervalP95Ms,
|
||||
display_jitter_ms: displayJitterMs,
|
||||
overwritten_pixel_frames: this.overwrittenPixelFrames,
|
||||
last_pixel_receive_age_ms: this.lastPixelReceivedAt ? Math.max(0, time - this.lastPixelReceivedAt) : 0,
|
||||
last_pixel_change_age_ms: this.lastPixelChangeAt ? Math.max(0, time - this.lastPixelChangeAt) : 0
|
||||
@@ -627,10 +671,7 @@ function connectCatalog() {
|
||||
});
|
||||
}
|
||||
function loop(time) {
|
||||
if (lastAnimationFrameAt > 0) {
|
||||
displayIntervalMs = Math.max(0, time - lastAnimationFrameAt);
|
||||
}
|
||||
lastAnimationFrameAt = time;
|
||||
updateDisplayTiming(time);
|
||||
const page = pages.get(activeMode);
|
||||
for (const card of page?.cards || []) {
|
||||
card.presentLatest(time);
|
||||
@@ -653,6 +694,10 @@ document.addEventListener("pointerdown", event => { if (!elements.menu.hidden &&
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
lastAnimationFrameAt = 0;
|
||||
displayIntervalMs = 0;
|
||||
displayIntervalLatestMs = 0;
|
||||
displayIntervalP95Ms = 0;
|
||||
displayJitterMs = 0;
|
||||
displayIntervalSamples.length = 0;
|
||||
syncCardActivity();
|
||||
});
|
||||
window.addEventListener("beforeunload", () => { for (const page of pages.values()) for (const card of page.cards) { card.disposed = true; clearTimeout(card.frameTimeout); clearTimeout(card.reconnectTimer); card.send("hide"); card.socket?.close(); } });
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<div><dt class="perf-pending">0</dt><dd>Kernel 待处理</dd></div>
|
||||
<div><dt class="perf-dropped">0</dt><dd>Kernel 丢弃</dd></div>
|
||||
<div><dt class="perf-points">0→0</dt><dd>输入→绘制</dd></div>
|
||||
<div class="perf-limit-cell"><dt class="perf-limit">N/A</dt><dd>当前瓶颈</dd></div>
|
||||
<div><dt class="perf-limit">N/A</dt><dd>当前瓶颈</dd></div>
|
||||
<div><dt class="perf-event">none</dt><dd>观察事件</dd></div>
|
||||
<div><dt class="perf-timeouts">0</dt><dd>像素超时</dd></div>
|
||||
<div><dt class="perf-pixel-age">—</dt><dd>最近像素龄</dd></div>
|
||||
@@ -121,13 +121,31 @@
|
||||
<div><small>PaintEvent</small><b data-observer-field="paint_duration_ns">0 ns</b></div>
|
||||
<div><small>后台渲染</small><b data-observer-field="render_duration_ns">0 ns</b></div>
|
||||
<div><small>内部瓶颈</small><b data-observer-field="bottleneck_duration_ns">0 ns</b></div>
|
||||
<div><small>消费原始样本</small><b data-observer-field="consumer_sample_interval_ns">0 ns</b></div>
|
||||
<div><small>消费 SRTT</small><b data-observer-field="consumer_smoothed_interval_ns">0 ns</b></div>
|
||||
<div><small>消费 RTTVAR</small><b data-observer-field="consumer_variation_ns">0 ns</b></div>
|
||||
<div><small>消费者反馈</small><b data-observer-field="consumer_interval_ns">0 ns</b></div>
|
||||
<div><small>消费者原始采样</small><b data-observer-field="consumer_sample_interval_ns">0 ns</b></div>
|
||||
<div><small>消费者平滑周期</small><b data-observer-field="consumer_smoothed_interval_ns">0 ns</b></div>
|
||||
<div><small>消费者抖动</small><b data-observer-field="consumer_variation_ns">0 ns</b></div>
|
||||
<div><small>消费者安全期限</small><b data-observer-field="consumer_safety_interval_ns">0 ns</b></div>
|
||||
<div><small>消费者限速周期</small><b data-observer-field="consumer_interval_ns">0 ns</b></div>
|
||||
<div><small>消费者限速 FPS</small><b data-observer-field="consumer_effective_fps">0 FPS</b></div>
|
||||
<div><small>下次刷新</small><b data-observer-field="next_refresh_interval_ns">0 ns</b></div>
|
||||
<div class="critical"><small>端到端延迟</small><b data-observer-field="end_to_end_ns">0 ns</b></div>
|
||||
</div>
|
||||
<div class="client-summary" aria-label="浏览器消费者反馈全量统计">
|
||||
<div><small>RAF 最新周期</small><b data-client-field="display_interval_latest_ms">0 ms</b></div>
|
||||
<div><small>RAF 中位周期</small><b data-client-field="display_interval_ms">0 ms</b></div>
|
||||
<div><small>RAF P95 周期</small><b data-client-field="display_interval_p95_ms">0 ms</b></div>
|
||||
<div><small>RAF P95-P50 抖动</small><b data-client-field="display_jitter_ms">0 ms</b></div>
|
||||
<div><small>WS 往返</small><b data-client-field="frame_round_trip_ms">0 ms</b></div>
|
||||
<div><small>像素响应 FPS</small><b data-client-field="transport_fps">0 FPS</b></div>
|
||||
<div><small>浏览器呈现 FPS</small><b data-client-field="presentation_fps">0 FPS</b></div>
|
||||
<div><small>WS 缓冲</small><b data-client-field="websocket_buffered_bytes">0 B</b></div>
|
||||
<div><small>未呈现覆盖</small><b data-client-field="overwritten_pixel_frames">0</b></div>
|
||||
<div><small>变化像素帧</small><b data-client-field="changed_pixel_frames">0</b></div>
|
||||
<div><small>重复像素帧</small><b data-client-field="duplicate_pixel_frames">0</b></div>
|
||||
<div><small>像素请求超时</small><b data-client-field="frame_request_timeout_count">0</b></div>
|
||||
<div><small>最近像素龄</small><b data-client-field="last_pixel_receive_age_ms">0 ms</b></div>
|
||||
<div><small>最近变化龄</small><b data-client-field="last_pixel_change_age_ms">0 ms</b></div>
|
||||
</div>
|
||||
<div class="latency-details" aria-label="Kernel 各阶段等待耗时">
|
||||
<div><small>Painter Lease 等待</small><b data-observer-field="paint_lease_wait_ns">0 ns</b></div>
|
||||
<div><small>Paint State 等待</small><b data-observer-field="paint_state_wait_ns">0 ns</b></div>
|
||||
|
||||
@@ -62,9 +62,9 @@ button:hover { border-color: var(--accent); } button:active { transform: transla
|
||||
.kernel-observer-panel { border-bottom:1px solid var(--line); background:#080d14; font-family:ui-monospace,monospace; }
|
||||
.kernel-observer-panel > header { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:9px 11px; border-bottom:1px solid var(--line); color:var(--muted); font-size:8px; flex-wrap:wrap; }
|
||||
.kernel-observer-panel > header div:first-child { display:flex; align-items:center; gap:9px; min-width:0; flex-wrap:wrap; }.kernel-observer-panel > header span { color:var(--accent); letter-spacing:.08em; overflow-wrap:anywhere; }.kernel-observer-panel > header strong,.kernel-observer-panel > header b { color:var(--text); font-weight:700; overflow-wrap:anywhere; }
|
||||
.observer-counters,.latency-summary,.latency-details { display:grid; gap:1px; background:var(--line); }
|
||||
.observer-counters { grid-template-columns:repeat(auto-fit,minmax(90px,1fr)); }.latency-summary { grid-template-columns:repeat(auto-fit,minmax(105px,1fr)); border-top:1px solid var(--line); }.latency-details { grid-template-columns:repeat(auto-fit,minmax(145px,1fr)); border-top:1px solid var(--line); }
|
||||
.observer-counters div,.latency-summary div,.latency-details div { min-width:0; padding:8px 7px; background:#0b1119; }
|
||||
.observer-counters,.latency-summary,.client-summary,.latency-details { display:grid; gap:1px; background:var(--line); }
|
||||
.observer-counters { grid-template-columns:repeat(auto-fit,minmax(90px,1fr)); }.latency-summary,.client-summary { grid-template-columns:repeat(auto-fit,minmax(105px,1fr)); border-top:1px solid var(--line); }.latency-details { grid-template-columns:repeat(auto-fit,minmax(145px,1fr)); border-top:1px solid var(--line); }
|
||||
.observer-counters div,.latency-summary div,.client-summary div,.latency-details div { min-width:0; padding:8px 7px; background:#0b1119; }
|
||||
.kernel-observer-panel small { display:block; min-width:0; overflow:visible; margin-bottom:5px; color:var(--muted); font-size:7px; line-height:1.35; text-overflow:clip; white-space:normal; overflow-wrap:anywhere; word-break:break-word; }
|
||||
.kernel-observer-panel b { display:block; min-width:0; overflow:visible; color:#b7c7dc; font-size:9px; line-height:1.35; text-overflow:clip; white-space:normal; overflow-wrap:anywhere; word-break:break-word; }
|
||||
.latency-summary b { color:var(--accent); font-size:10px; }.latency-summary .critical { background:#10201e; }.latency-summary .critical b { color:#72f3d9; }
|
||||
@@ -81,4 +81,4 @@ button:hover { border-color: var(--accent); } button:active { transform: transla
|
||||
.menu-footer { display:flex; justify-content:space-between; gap:14px; padding:10px 14px; border-top:1px solid var(--line); color:var(--muted); font-size:9px; }.menu-footer code { color:var(--accent); }
|
||||
.toast { position:fixed; z-index:140; left:50%; bottom:22px; max-width:min(560px,calc(100vw - 30px)); padding:11px 15px; border:1px solid var(--strong); border-radius:8px; background:var(--panel2); box-shadow:0 18px 50px #00000073; transform:translateX(-50%); font-size:11px; }.toast[data-error="true"] { border-color:var(--danger); }
|
||||
@media (max-width:980px) { .hero { grid-template-columns:1fr; }.gallery { grid-template-columns:1fr; }.mode-tabs button { flex-direction:column; align-items:flex-start; }.page-header { align-items:flex-start; flex-direction:column; }.page-description { text-align:left; } }
|
||||
@media (max-width:650px) { .topbar { align-items:flex-start; }.topbar-actions { align-items:flex-end; flex-direction:column; }.connection-status span { display:none; }.metrics { grid-template-columns:repeat(2,1fr); }.mode-tabs { grid-template-columns:1fr; }.mode-tabs button { flex-direction:row; }.card-meta { grid-template-columns:1fr; }.performance-strip { grid-template-columns:repeat(auto-fit,minmax(92px,1fr)); }.limit-flags { grid-template-columns:1fr; }.observer-counters { grid-template-columns:repeat(auto-fit,minmax(80px,1fr)); }.latency-summary,.latency-details { grid-template-columns:repeat(auto-fit,minmax(100px,1fr)); }.context-menu { inset:auto 6px 6px!important; width:auto; max-height:calc(100vh - 12px); } }
|
||||
@media (max-width:650px) { .topbar { align-items:flex-start; }.topbar-actions { align-items:flex-end; flex-direction:column; }.connection-status span { display:none; }.metrics { grid-template-columns:repeat(2,1fr); }.mode-tabs { grid-template-columns:1fr; }.mode-tabs button { flex-direction:row; }.card-meta { grid-template-columns:1fr; }.performance-strip { grid-template-columns:repeat(auto-fit,minmax(92px,1fr)); }.limit-flags { grid-template-columns:1fr; }.observer-counters { grid-template-columns:repeat(auto-fit,minmax(80px,1fr)); }.latency-summary,.client-summary,.latency-details { grid-template-columns:repeat(auto-fit,minmax(100px,1fr)); }.context-menu { inset:auto 6px 6px!important; width:auto; max-height:calc(100vh - 12px); } }
|
||||
|
||||
Reference in New Issue
Block a user