架构微调

This commit is contained in:
2026-08-11 09:41:06 +08:00
parent 462baeca76
commit 996e6f6154
6 changed files with 108 additions and 95 deletions
+37 -13
View File
@@ -37,8 +37,8 @@ double number_value(const Gallery_State& state, std::string_view id) {
return state_value<double>(state, id);
}
std::uint64_t frequency_interval_ns(double frequency_hz) {
return static_cast<std::uint64_t>(1'000'000'000.0 / frequency_hz);
std::uint64_t milliseconds_to_ns(double milliseconds) {
return milliseconds > 0.0 ? static_cast<std::uint64_t>(std::llround(milliseconds * 1'000'000.0)) : 0;
}
int integer_value(const Gallery_State& state, std::string_view id) {
@@ -231,6 +231,9 @@ public:
std::uint64_t changed_pixel_frames,
std::uint64_t duplicate_pixel_frames,
std::uint64_t frame_request_timeout_count,
double frame_round_trip_ms,
double display_interval_ms,
std::uint64_t overwritten_pixel_frames,
double last_pixel_receive_age_ms,
double last_pixel_change_age_ms) noexcept {
client_transport_fps_ = std::isfinite(transport_fps) ?
@@ -241,10 +244,20 @@ public:
client_changed_pixel_frames_ = changed_pixel_frames;
client_duplicate_pixel_frames_ = duplicate_pixel_frames;
client_frame_request_timeout_count_ = frame_request_timeout_count;
client_frame_round_trip_ms_ = std::isfinite(frame_round_trip_ms) ?
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_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;
client_last_pixel_change_age_ms_ = std::isfinite(last_pixel_change_age_ms) ?
std::max(0.0, last_pixel_change_age_ms) : 0.0;
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) {
const auto interval_ns = std::max(milliseconds_to_ns(client_frame_round_trip_ms_),
milliseconds_to_ns(client_display_interval_ms_));
plot_.set_consumer_feedback({interval_ns});
}
}
[[nodiscard]] bool requires_rebuild(const Gallery_State& candidate) const {
@@ -642,6 +655,9 @@ public:
{"changed_pixel_frames", client_changed_pixel_frames_},
{"duplicate_pixel_frames", client_duplicate_pixel_frames_},
{"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_},
{"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_}
}},
@@ -658,8 +674,7 @@ public:
{"render_limited", observer.limit_state == "render_limited"},
{"consumer_limited", observer.limit_state == "consumer_limited"},
{"configured_frequency_hz", observer.frequency_hz},
{"consumer_feedback_source", "web_pixel_stream"},
{"web_pixel_stream_fps", number_value(state_, "pixel_stream_fps")},
{"consumer_feedback_source", "web_runtime"},
{"target_interval_ns", observer.target_interval_ns},
{"paint_duration_ns", observer.paint_duration_ns},
{"render_duration_ns", observer.render_duration_ns},
@@ -914,7 +929,6 @@ private:
{"case", case_id_},
{"frame_mode", frame_mode_name(frame_mode_)},
{"configured_fps", low_latency ? plot_.max_render_fps() : 0.0},
{"web_pixel_stream_fps", low_latency ? number_value(state_, "pixel_stream_fps") : 0.0},
{"automatic_scheduler_interval_ns", scheduler_interval_ns},
{"automatic_scheduler_fps", scheduler_interval_ns == 0 ? 0.0 :
1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)},
@@ -927,6 +941,9 @@ private:
{"client_changed_pixel_frames", client_changed_pixel_frames_},
{"client_duplicate_pixel_frames", client_duplicate_pixel_frames_},
{"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_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_},
{"last_render_ms", last_render_ms_},
@@ -1205,11 +1222,8 @@ private:
void apply_state() {
plot_.set_background_color(color_from_hex(string_value(state_, "background_color")));
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) {
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency)
plot_.set_max_render_fps(number_value(state_, "max_render_fps"));
plot_.set_consumer_feedback(
{frequency_interval_ns(number_value(state_, "pixel_stream_fps"))});
}
set_performance_plot_name(plot_, case_id_ + "/" + std::string(frame_mode_name(frame_mode_)));
set_performance_overlay_enabled(plot_, bool_value(state_, "performance_overlay"));
if (primary_) {
@@ -1449,6 +1463,9 @@ private:
std::uint64_t client_changed_pixel_frames_{};
std::uint64_t client_duplicate_pixel_frames_{};
std::uint64_t client_frame_request_timeout_count_{};
double client_frame_round_trip_ms_{};
double client_display_interval_ms_{};
std::uint64_t client_overwritten_pixel_frames_{};
double client_last_pixel_receive_age_ms_{};
double client_last_pixel_change_age_ms_{};
bool rendered_since_last_pixel_{};
@@ -1477,13 +1494,13 @@ struct Gallery_Plot_Session::Impl {
[this](std::stop_token stop) { run_automatic_renderer(stop); });
}
void update_client_metrics(std::string_view message) {
bool update_client_metrics(std::string_view message) {
if (!scene)
return;
return false;
const auto root = nlohmann::json::parse(message, nullptr, false);
if (root.is_discarded() || !root.is_object() || !root.contains("client_metrics") ||
!root["client_metrics"].is_object())
return;
return false;
const auto& metrics = root["client_metrics"];
const auto finite_metric = [&metrics](std::string_view name) {
const auto iterator = metrics.find(std::string(name));
@@ -1509,8 +1526,12 @@ struct Gallery_Plot_Session::Impl {
unsigned_metric("changed_pixel_frames"),
unsigned_metric("duplicate_pixel_frames"),
unsigned_metric("frame_request_timeout_count"),
finite_metric("frame_round_trip_ms"),
finite_metric("display_interval_ms"),
unsigned_metric("overwritten_pixel_frames"),
finite_metric("last_pixel_receive_age_ms"),
finite_metric("last_pixel_change_age_ms"));
return true;
}
[[nodiscard]] std::unique_lock<std::mutex> acquire_foreground_lock() {
@@ -1648,7 +1669,10 @@ struct Gallery_Plot_Session::Impl {
return Web_Response{Web_Response_Type::Json,
Gallery_Protocol::error_json("请先发送 gallery_open")};
if (request.kind == Gallery_Request_Kind::Observe) {
update_client_metrics(request.message);
if (update_client_metrics(request.message)) {
++scheduler_revision;
scheduler_condition.notify_all();
}
return Web_Response{Web_Response_Type::Json,
Gallery_Protocol::observer_json(
scene->case_id(), scene->frame_mode(), scene->telemetry_json())};
-4
View File
@@ -126,10 +126,6 @@ void append_common(std::vector<Control_Definition>& result, Gallery_Frame_Mode f
result.push_back(number("max_render_fps", "最大渲染 FPS", "Plot_Core::set_max_render_fps",
30, 0.01, 1'000'000'000, 1, "Low_Latency_Strategy", false,
"Kernel 接受任意有限正频率;高目标用于触发并观察 Painter 或后台 Render 瓶颈。"));
result.push_back(number("pixel_stream_fps", "WebSocket 像素 FPS",
"Plot_Core::set_consumer_feedback", 30, 1, 60, 1,
"Web Transport", false,
"限制浏览器拉取完整 RGBA 像素图的频率;Web 适配层将该频率转换为通用消费者反馈,Kernel 不感知 WebSocket。"));
}
result.push_back(flag("performance_overlay", "性能叠层", "set_performance_overlay_enabled",
false, "Plot_Core"));
+36 -28
View File
@@ -841,12 +841,11 @@ TEST(RenderiveWebGallery, ThreeFrameStrategiesExposeTheirRealActionsAndObservers
EXPECT_TRUE(low_telemetry.contains("render_fps"));
ASSERT_TRUE(low_telemetry.contains("low_latency_limit"));
const auto& limit = low_telemetry.at("low_latency_limit");
EXPECT_TRUE(limit.at("consumer_limited").get<bool>());
EXPECT_FALSE(limit.at("consumer_limited").get<bool>());
EXPECT_FALSE(limit.at("frequency_limited").get<bool>());
EXPECT_FALSE(limit.at("paint_limited").get<bool>());
EXPECT_FALSE(limit.at("render_limited").get<bool>());
EXPECT_TRUE(limit.at("paint_limited").get<bool>() || limit.at("render_limited").get<bool>());
EXPECT_EQ(limit.at("target_interval_ns"), 1);
EXPECT_EQ(limit.at("consumer_interval_ns"), 33'333'333);
EXPECT_EQ(limit.at("consumer_interval_ns"), 0);
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"));
@@ -939,7 +938,7 @@ TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveUnderAggres
"case_state");
ASSERT_EQ(response_json(session.handle(gallery_request(
Gallery_Request_Kind::Patch,
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":1000,"pixel_stream_fps":60}})"))).at("type"),
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":1000}})"))).at("type"),
"case_state");
ASSERT_TRUE(wait_for_condition([&session] {
@@ -974,35 +973,41 @@ TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveUnderAggres
observer.at("limit_state") == "consumer_limited");
}
TEST(RenderiveWebGallery, WebAdapterPublishesGenericConsumerFeedbackToKernelScheduler) {
TEST(RenderiveWebGallery, WebRuntimeMetricsPublishGenericConsumerFeedbackWithoutTransportFpsCap) {
Gallery_Plot_Session session(true);
ASSERT_EQ(response_json(session.handle(gallery_request(
Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
"case_state");
const auto patched = response_json(session.handle(gallery_request(
ASSERT_EQ(response_json(session.handle(gallery_request(
Gallery_Request_Kind::Patch,
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":1000000000,"pixel_stream_fps":20}})")));
ASSERT_EQ(patched.at("type"), "case_state");
const auto& telemetry = patched.at("telemetry");
const auto& observer = telemetry.at("kernel_observer");
const auto& limit = telemetry.at("low_latency_limit");
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":1000000000}})"))).at("type"),
"case_state");
auto observed = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Observe,
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":20,"presentation_fps":20,"websocket_buffered_bytes":0,"changed_pixel_frames":10,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":500,"display_interval_ms":500,"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_EQ(observer.at("configured_frequency_hz"), 1'000'000'000.0);
EXPECT_EQ(observer.at("target_interval_ns"), 1);
EXPECT_EQ(observer.at("consumer_interval_ns"), 50'000'000);
EXPECT_EQ(observer.at("next_refresh_interval_ns"), 50'000'000);
EXPECT_EQ(observer.at("consumer_interval_ns"), 500'000'000);
EXPECT_EQ(observer.at("next_refresh_interval_ns"), 500'000'000);
EXPECT_EQ(observer.at("limit_state"), "consumer_limited");
EXPECT_EQ(limit.at("current"), "consumer_limited");
EXPECT_TRUE(limit.at("consumer_limited").get<bool>());
EXPECT_EQ(limit.at("consumer_feedback_source"), "web_pixel_stream");
EXPECT_EQ(limit.at("consumer_interval_ns"), 50'000'000);
EXPECT_EQ(limit.at("scheduler_interval_ns"), 50'000'000);
EXPECT_DOUBLE_EQ(limit.at("scheduler_frequency_hz"), 20.0);
const auto baseline = successful_render_count(session);
ASSERT_TRUE(wait_for_condition([&session, baseline] {
return successful_render_count(session) > baseline;
}));
std::this_thread::sleep_for(std::chrono::milliseconds(250));
EXPECT_LE(successful_render_count(session) - baseline, 9U);
EXPECT_EQ(limit.at("consumer_feedback_source"), "web_runtime");
EXPECT_FALSE(limit.contains("web_pixel_stream_fps"));
observed = response_json(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":20,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":2,"display_interval_ms":20,"overwritten_pixel_frames":1,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
observer = observed.at("telemetry").at("kernel_observer");
EXPECT_EQ(observer.at("consumer_interval_ns"), 20'000'000);
observed = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Observe,
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":120,"presentation_fps":120,"websocket_buffered_bytes":0,"changed_pixel_frames":30,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":2,"display_interval_ms":8,"overwritten_pixel_frames":1,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
observer = observed.at("telemetry").at("kernel_observer");
EXPECT_EQ(observer.at("consumer_interval_ns"), 8'000'000);
const auto rejected = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Patch,
R"({"category":"event","type":"gallery_patch","patch":{"pixel_stream_fps":30}})")));
EXPECT_EQ(rejected.at("type"), "error");
}
TEST(RenderiveWebGallery, PlaybackStrategyQueuesWithoutAutomaticConsumptionAndReportsEmptyQueue) {
@@ -1090,7 +1095,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,"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,"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");
@@ -1108,6 +1113,9 @@ TEST(RenderiveWebGallery, ProductionLowLatencySessionRendersWithoutPixelPulls) {
EXPECT_EQ(telemetry.at("client_performance").at("changed_pixel_frames"), 17);
EXPECT_EQ(telemetry.at("client_performance").at("duplicate_pixel_frames"), 3);
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_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);
}
@@ -1185,7 +1193,7 @@ void expect_low_latency_canvas_to_change(std::string_view case_id) {
ASSERT_FALSE(session.handle(Viewport_Resize{{240, 180}}).has_value());
ASSERT_TRUE(session.handle(gallery_request(
Gallery_Request_Kind::Patch,
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":120,"pixel_stream_fps":30}})")));
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":120}})")));
std::this_thread::sleep_for(std::chrono::milliseconds(90));
const auto first = session.handle(Frame_Request{});