修复了一些bug
This commit is contained in:
@@ -226,6 +226,8 @@ public:
|
||||
std::uint64_t buffered_bytes,
|
||||
std::uint64_t changed_pixel_frames,
|
||||
std::uint64_t duplicate_pixel_frames,
|
||||
std::uint64_t frame_request_timeout_count,
|
||||
double last_pixel_receive_age_ms,
|
||||
double last_pixel_change_age_ms) noexcept {
|
||||
client_transport_fps_ = std::isfinite(transport_fps) ?
|
||||
std::clamp(transport_fps, 0.0, 100000.0) : 0.0;
|
||||
@@ -234,6 +236,9 @@ public:
|
||||
client_buffered_bytes_ = buffered_bytes;
|
||||
client_changed_pixel_frames_ = changed_pixel_frames;
|
||||
client_duplicate_pixel_frames_ = duplicate_pixel_frames;
|
||||
client_frame_request_timeout_count_ = frame_request_timeout_count;
|
||||
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;
|
||||
}
|
||||
@@ -560,7 +565,9 @@ public:
|
||||
std::chrono::duration<double>(telemetry_now - performance_started_).count(),
|
||||
1e-9);
|
||||
const double render_fps = recent_rate(render_history_, telemetry_now);
|
||||
const double pixel_fps = recent_rate(pixel_history_, telemetry_now);
|
||||
const double pixel_fps = recent_pixel_rate(pixel_history_, telemetry_now);
|
||||
const double pixel_megabytes_per_second = recent_pixel_megabytes_per_second(
|
||||
pixel_history_, telemetry_now);
|
||||
nlohmann::json telemetry{
|
||||
{"case", case_id_},
|
||||
{"frame_mode", frame_mode_name(frame_mode_)},
|
||||
@@ -587,8 +594,7 @@ public:
|
||||
{"maximum_pixel_encode_ms", maximum_pixel_encode_ms_},
|
||||
{"last_pixel_request_ms", last_pixel_request_ms_},
|
||||
{"last_pixel_bytes", last_pixel_bytes_},
|
||||
{"pixel_payload_megabytes_per_second",
|
||||
pixel_fps * static_cast<double>(last_pixel_bytes_) / 1e6},
|
||||
{"pixel_payload_megabytes_per_second", pixel_megabytes_per_second},
|
||||
{"automatic_low_latency_scheduler",
|
||||
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency},
|
||||
{"kernel_paint_ms", static_cast<double>(observer.paint_duration_ns) / 1e6},
|
||||
@@ -630,6 +636,8 @@ public:
|
||||
{"websocket_buffered_bytes", client_buffered_bytes_},
|
||||
{"changed_pixel_frames", client_changed_pixel_frames_},
|
||||
{"duplicate_pixel_frames", client_duplicate_pixel_frames_},
|
||||
{"frame_request_timeout_count", client_frame_request_timeout_count_},
|
||||
{"last_pixel_receive_age_ms", client_last_pixel_receive_age_ms_},
|
||||
{"last_pixel_change_age_ms", client_last_pixel_change_age_ms_}
|
||||
}},
|
||||
{"last_action_result", last_action_result_}
|
||||
@@ -787,6 +795,10 @@ public:
|
||||
|
||||
private:
|
||||
using Performance_Clock = std::chrono::steady_clock;
|
||||
struct Pixel_Performance_Sample {
|
||||
Performance_Clock::time_point time;
|
||||
std::size_t bytes;
|
||||
};
|
||||
|
||||
static void record_timestamp(std::deque<Performance_Clock::time_point>& history,
|
||||
Performance_Clock::time_point now) {
|
||||
@@ -805,6 +817,39 @@ private:
|
||||
return seconds > 0.0 ? static_cast<double>(history.size() - 1) / seconds : 0.0;
|
||||
}
|
||||
|
||||
static void record_pixel_sample(std::deque<Pixel_Performance_Sample>& history,
|
||||
Performance_Clock::time_point now,
|
||||
std::size_t bytes) {
|
||||
history.push_back({now, bytes});
|
||||
const auto oldest = now - std::chrono::seconds(1);
|
||||
while (history.size() > 2 && history.front().time < oldest)
|
||||
history.pop_front();
|
||||
}
|
||||
|
||||
static double recent_pixel_rate(const std::deque<Pixel_Performance_Sample>& history,
|
||||
Performance_Clock::time_point now) {
|
||||
if (history.size() < 2 || now - history.back().time > std::chrono::seconds(1))
|
||||
return 0.0;
|
||||
const double seconds =
|
||||
std::chrono::duration<double>(history.back().time - history.front().time).count();
|
||||
return seconds > 0.0 ? static_cast<double>(history.size() - 1) / seconds : 0.0;
|
||||
}
|
||||
|
||||
static double recent_pixel_megabytes_per_second(
|
||||
const std::deque<Pixel_Performance_Sample>& history,
|
||||
Performance_Clock::time_point now) {
|
||||
if (history.size() < 2 || now - history.back().time > std::chrono::seconds(1))
|
||||
return 0.0;
|
||||
const double seconds =
|
||||
std::chrono::duration<double>(history.back().time - history.front().time).count();
|
||||
if (seconds <= 0.0)
|
||||
return 0.0;
|
||||
std::size_t bytes{};
|
||||
for (std::size_t index = 1; index < history.size(); ++index)
|
||||
bytes += history[index].bytes;
|
||||
return static_cast<double>(bytes) / seconds / 1e6;
|
||||
}
|
||||
|
||||
void record_performance(std::chrono::steady_clock::time_point started, bool rendered) {
|
||||
const auto finished = std::chrono::steady_clock::now();
|
||||
++render_attempt_count_;
|
||||
@@ -834,7 +879,7 @@ private:
|
||||
total_pixel_encode_ms_ += last_pixel_encode_ms_;
|
||||
last_pixel_bytes_ = pixel_bytes;
|
||||
++pixel_frame_count_;
|
||||
record_timestamp(pixel_history_, encode_finished);
|
||||
record_pixel_sample(pixel_history_, encode_finished, pixel_bytes);
|
||||
}
|
||||
|
||||
void maybe_log_performance(Performance_Clock::time_point now) {
|
||||
@@ -845,7 +890,9 @@ private:
|
||||
const auto unix_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
const double pixel_fps = recent_rate(pixel_history_, now);
|
||||
const double pixel_fps = recent_pixel_rate(pixel_history_, now);
|
||||
const double pixel_megabytes_per_second = recent_pixel_megabytes_per_second(
|
||||
pixel_history_, now);
|
||||
const nlohmann::json line{
|
||||
{"event", "gallery_performance"},
|
||||
{"unix_ms", unix_ms},
|
||||
@@ -860,14 +907,15 @@ private:
|
||||
{"client_presentation_fps", client_presentation_fps_},
|
||||
{"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_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_},
|
||||
{"last_pixel_snapshot_ms", last_pixel_snapshot_ms_},
|
||||
{"last_pixel_encode_ms", last_pixel_encode_ms_},
|
||||
{"last_pixel_request_ms", last_pixel_request_ms_},
|
||||
{"pixel_payload_bytes", last_pixel_bytes_},
|
||||
{"pixel_payload_megabytes_per_second",
|
||||
pixel_fps * static_cast<double>(last_pixel_bytes_) / 1e6},
|
||||
{"pixel_payload_megabytes_per_second", pixel_megabytes_per_second},
|
||||
{"websocket_buffered_bytes", client_buffered_bytes_},
|
||||
{"automatic_low_latency_scheduler",
|
||||
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency},
|
||||
@@ -1371,12 +1419,14 @@ private:
|
||||
double maximum_pixel_encode_ms_{};
|
||||
double last_pixel_request_ms_{};
|
||||
std::size_t last_pixel_bytes_{};
|
||||
std::deque<Performance_Clock::time_point> pixel_history_;
|
||||
std::deque<Pixel_Performance_Sample> pixel_history_;
|
||||
double client_transport_fps_{};
|
||||
double client_presentation_fps_{};
|
||||
std::uint64_t client_buffered_bytes_{};
|
||||
std::uint64_t client_changed_pixel_frames_{};
|
||||
std::uint64_t client_duplicate_pixel_frames_{};
|
||||
std::uint64_t client_frame_request_timeout_count_{};
|
||||
double client_last_pixel_receive_age_ms_{};
|
||||
double client_last_pixel_change_age_ms_{};
|
||||
bool rendered_since_last_pixel_{};
|
||||
std::string last_action_result_;
|
||||
@@ -1385,12 +1435,7 @@ private:
|
||||
struct Gallery_Plot_Session::Impl {
|
||||
explicit Impl(bool enable_automatic_low_latency)
|
||||
: automatic_low_latency(enable_automatic_low_latency),
|
||||
session_id(next_session_id()) {
|
||||
if (automatic_low_latency) {
|
||||
render_worker = std::jthread(
|
||||
[this](std::stop_token stop) { run_automatic_renderer(stop); });
|
||||
}
|
||||
}
|
||||
session_id(next_session_id()) {}
|
||||
|
||||
~Impl() {
|
||||
render_worker.request_stop();
|
||||
@@ -1402,6 +1447,13 @@ struct Gallery_Plot_Session::Impl {
|
||||
return next.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void ensure_render_worker() {
|
||||
if (!automatic_low_latency || render_worker.joinable())
|
||||
return;
|
||||
render_worker = std::jthread(
|
||||
[this](std::stop_token stop) { run_automatic_renderer(stop); });
|
||||
}
|
||||
|
||||
void update_client_metrics(std::string_view message) {
|
||||
if (!scene)
|
||||
return;
|
||||
@@ -1433,6 +1485,8 @@ struct Gallery_Plot_Session::Impl {
|
||||
finite_metric("presentation_fps"), buffered_bytes,
|
||||
unsigned_metric("changed_pixel_frames"),
|
||||
unsigned_metric("duplicate_pixel_frames"),
|
||||
unsigned_metric("frame_request_timeout_count"),
|
||||
finite_metric("last_pixel_receive_age_ms"),
|
||||
finite_metric("last_pixel_change_age_ms"));
|
||||
}
|
||||
|
||||
@@ -1497,11 +1551,10 @@ struct Gallery_Plot_Session::Impl {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto spin_window = interval <= std::chrono::milliseconds(10) ?
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::milliseconds(2)) :
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::microseconds(250));
|
||||
const auto spin_window = std::min(
|
||||
interval / 4,
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::microseconds(250)));
|
||||
const auto coarse_deadline = deadline > cycle_started + spin_window ?
|
||||
deadline - spin_window : cycle_started;
|
||||
if (Clock::now() < coarse_deadline) {
|
||||
@@ -1515,8 +1568,8 @@ struct Gallery_Plot_Session::Impl {
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
while (!stop.stop_requested() && Clock::now() < deadline) {
|
||||
}
|
||||
while (!stop.stop_requested() && Clock::now() < deadline)
|
||||
std::this_thread::yield();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
@@ -1560,6 +1613,8 @@ struct Gallery_Plot_Session::Impl {
|
||||
Gallery_Protocol::default_state(open->case_id, open->frame_mode),
|
||||
open->frame_mode, automatic_low_latency);
|
||||
++scene_generation;
|
||||
if (scene->frame_mode() == Gallery_Frame_Mode::Low_Latency)
|
||||
ensure_render_worker();
|
||||
return Web_Response{Web_Response_Type::Json,
|
||||
Gallery_Protocol::case_json(scene->case_id(), scene->state(),
|
||||
scene->telemetry_json(),
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace renderive::web {
|
||||
namespace {
|
||||
@@ -74,6 +76,31 @@ nlohmann::json patch_controls(Gallery_Plot_Session& session, nlohmann::json patc
|
||||
Gallery_Request_Kind::Patch, request.dump())));
|
||||
}
|
||||
|
||||
template <class Predicate>
|
||||
bool wait_for_condition(Predicate&& predicate,
|
||||
std::chrono::milliseconds timeout = std::chrono::milliseconds(750)) {
|
||||
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
do {
|
||||
if (predicate())
|
||||
return true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
} while (std::chrono::steady_clock::now() < deadline);
|
||||
return predicate();
|
||||
}
|
||||
|
||||
void set_gallery_view_active(Gallery_Plot_Session& session, bool active) {
|
||||
const auto event = Web_Event_Adapter::decode(active ?
|
||||
R"({"category":"event","type":"show"})" :
|
||||
R"({"category":"event","type":"hide"})");
|
||||
ASSERT_TRUE(event.has_value());
|
||||
EXPECT_FALSE(session.handle(*event).has_value());
|
||||
}
|
||||
|
||||
std::uint64_t successful_render_count(Gallery_Plot_Session& session) {
|
||||
return observe_telemetry(session).at("performance").at("successful_render_count")
|
||||
.get<std::uint64_t>();
|
||||
}
|
||||
|
||||
TEST(RenderiveWebBridge, DecodesOnlyEventMessages) {
|
||||
const auto resize = Web_Event_Adapter::decode(
|
||||
R"({"category":"event","type":"resize","width":800,"height":450})");
|
||||
@@ -648,7 +675,7 @@ TEST(RenderiveWebGallery, EveryCanvasAcceptsTheCompleteWebEventSetAndStillRender
|
||||
for (const auto case_id : cases) {
|
||||
for (const auto mode : modes) {
|
||||
SCOPED_TRACE(std::string(case_id) + "/" + std::string(mode));
|
||||
Gallery_Plot_Session session;
|
||||
Gallery_Plot_Session session(mode == "low_latency");
|
||||
ASSERT_EQ(response_json(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message(case_id, mode)))).at("type"),
|
||||
"case_state");
|
||||
@@ -704,41 +731,41 @@ TEST(RenderiveWebGallery, EveryIndependentCore2CanvasBuildsAndRenders) {
|
||||
};
|
||||
constexpr std::array<std::string_view, 3> modes{"manual", "low_latency", "playback"};
|
||||
for (const std::string_view mode : modes) {
|
||||
for (const std::string_view case_id : cases) {
|
||||
Gallery_Plot_Session session;
|
||||
const auto opened = session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message(case_id, mode)));
|
||||
ASSERT_TRUE(opened.has_value()) << case_id;
|
||||
ASSERT_EQ(opened->type, Web_Response_Type::Json) << case_id;
|
||||
const auto state = parse_json(opened->payload);
|
||||
EXPECT_EQ(state.at("type"), "case_state") << case_id;
|
||||
EXPECT_EQ(state.at("case").at("id").get<std::string>(), std::string(case_id)) << case_id;
|
||||
EXPECT_EQ(state.at("frame_mode").at("id"), std::string(mode)) << case_id;
|
||||
EXPECT_EQ(state.at("controls").at("descriptor").at("protocol"),
|
||||
"adminive.resource") << case_id;
|
||||
EXPECT_EQ(state.at("controls").at("view").at("protocol"),
|
||||
"adminive.view") << case_id;
|
||||
|
||||
EXPECT_FALSE(session.handle(Viewport_Resize{{480, 280}}).has_value()) << case_id;
|
||||
const auto frame = session.handle(Frame_Request{});
|
||||
ASSERT_TRUE(frame.has_value()) << case_id;
|
||||
ASSERT_EQ(frame->type, Web_Response_Type::Pixels) << case_id;
|
||||
EXPECT_EQ(frame->payload.substr(0, 4), "RVP1") << case_id;
|
||||
EXPECT_EQ(read_u32_le(frame->payload, 4), 480U) << case_id;
|
||||
EXPECT_EQ(read_u32_le(frame->payload, 8), 280U) << case_id;
|
||||
const auto observed = session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Observe,
|
||||
R"({"category":"event","type":"gallery_observe"})"));
|
||||
ASSERT_TRUE(observed.has_value()) << case_id;
|
||||
const auto telemetry = parse_json(observed->payload);
|
||||
EXPECT_EQ(telemetry.at("type"), "observer_state") << case_id;
|
||||
EXPECT_EQ(telemetry.at("frame_mode").at("id"), std::string(mode)) << case_id;
|
||||
EXPECT_TRUE(telemetry.at("telemetry").contains("kernel_observer")) << case_id;
|
||||
EXPECT_TRUE(telemetry.at("telemetry").contains("performance")) << case_id;
|
||||
EXPECT_TRUE(telemetry.at("telemetry").contains("data_shape")) << case_id;
|
||||
EXPECT_GT(telemetry.at("telemetry").at("data_shape").at("rendered_elements")
|
||||
.get<std::size_t>(), 0U) << case_id;
|
||||
}
|
||||
for (const std::string_view case_id : cases) {
|
||||
Gallery_Plot_Session session(mode == "low_latency");
|
||||
const auto opened = session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message(case_id, mode)));
|
||||
ASSERT_TRUE(opened.has_value()) << case_id;
|
||||
ASSERT_EQ(opened->type, Web_Response_Type::Json) << case_id;
|
||||
const auto state = parse_json(opened->payload);
|
||||
EXPECT_EQ(state.at("type"), "case_state") << case_id;
|
||||
EXPECT_EQ(state.at("case").at("id").get<std::string>(), std::string(case_id))
|
||||
<< case_id;
|
||||
EXPECT_EQ(state.at("frame_mode").at("id"), std::string(mode)) << case_id;
|
||||
EXPECT_EQ(state.at("controls").at("descriptor").at("protocol"),
|
||||
"adminive.resource") << case_id;
|
||||
EXPECT_EQ(state.at("controls").at("view").at("protocol"),
|
||||
"adminive.view") << case_id;
|
||||
EXPECT_FALSE(session.handle(Viewport_Resize{{480, 280}}).has_value()) << case_id;
|
||||
const auto frame = session.handle(Frame_Request{});
|
||||
ASSERT_TRUE(frame.has_value()) << case_id;
|
||||
ASSERT_EQ(frame->type, Web_Response_Type::Pixels) << case_id;
|
||||
EXPECT_EQ(frame->payload.substr(0, 4), "RVP1") << case_id;
|
||||
EXPECT_EQ(read_u32_le(frame->payload, 4), 480U) << case_id;
|
||||
EXPECT_EQ(read_u32_le(frame->payload, 8), 280U) << case_id;
|
||||
const auto observed = session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Observe,
|
||||
R"({"category":"event","type":"gallery_observe"})"));
|
||||
ASSERT_TRUE(observed.has_value()) << case_id;
|
||||
const auto telemetry = parse_json(observed->payload);
|
||||
EXPECT_EQ(telemetry.at("type"), "observer_state") << case_id;
|
||||
EXPECT_EQ(telemetry.at("frame_mode").at("id"), std::string(mode)) << case_id;
|
||||
EXPECT_TRUE(telemetry.at("telemetry").contains("kernel_observer")) << case_id;
|
||||
EXPECT_TRUE(telemetry.at("telemetry").contains("performance")) << case_id;
|
||||
EXPECT_TRUE(telemetry.at("telemetry").contains("data_shape")) << case_id;
|
||||
EXPECT_GT(telemetry.at("telemetry").at("data_shape").at("rendered_elements")
|
||||
.get<std::size_t>(), 0U) << case_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,7 +930,7 @@ TEST(RenderiveWebGallery, ManualStrategyRunsOnlyExplicitPrepareRefreshRenderCycl
|
||||
sequence_after_actions);
|
||||
}
|
||||
|
||||
TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveAt1000Hz) {
|
||||
TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveUnderAggressiveFrequency) {
|
||||
Gallery_Plot_Session session(true);
|
||||
ASSERT_EQ(response_json(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
|
||||
@@ -913,7 +940,9 @@ TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveAt1000Hz) {
|
||||
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":1000,"pixel_stream_fps":60}})"))).at("type"),
|
||||
"case_state");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
||||
ASSERT_TRUE(wait_for_condition([&session] {
|
||||
return successful_render_count(session) >= 4;
|
||||
}));
|
||||
std::string first_pixels;
|
||||
std::string last_pixels;
|
||||
for (int iteration = 0; iteration < 12; ++iteration) {
|
||||
@@ -935,13 +964,11 @@ TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveAt1000Hz) {
|
||||
const auto telemetry = observe_telemetry(session);
|
||||
const auto& observer = telemetry.at("kernel_observer");
|
||||
EXPECT_EQ(observer.at("target_interval_ns"), 1'000'000);
|
||||
EXPECT_TRUE(observer.at("limit_state") == "paint_limited" ||
|
||||
EXPECT_GT(observer.at("latest_sequence").get<std::uint64_t>(), 4U);
|
||||
EXPECT_GT(telemetry.at("performance").at("successful_render_count").get<std::uint64_t>(), 4U);
|
||||
EXPECT_TRUE(observer.at("limit_state") == "frequency_limited" ||
|
||||
observer.at("limit_state") == "paint_limited" ||
|
||||
observer.at("limit_state") == "render_limited");
|
||||
EXPECT_GT(observer.at("latest_sequence").get<std::uint64_t>(), 12U);
|
||||
EXPECT_GT(telemetry.at("performance").at("measured_fps").get<double>(), 100.0);
|
||||
EXPECT_TRUE(telemetry.at("low_latency_limit").at("paint_limited").get<bool>() ||
|
||||
telemetry.at("low_latency_limit").at("render_limited").get<bool>());
|
||||
EXPECT_FALSE(telemetry.at("low_latency_limit").at("frequency_limited").get<bool>());
|
||||
}
|
||||
|
||||
TEST(RenderiveWebGallery, PlaybackStrategyQueuesWithoutAutomaticConsumptionAndReportsEmptyQueue) {
|
||||
@@ -1023,38 +1050,98 @@ TEST(RenderiveWebGallery, ProductionLowLatencySessionRendersWithoutPixelPulls) {
|
||||
Gallery_Request_Kind::Open, open_message("spectrum", "low_latency"))));
|
||||
ASSERT_TRUE(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Patch,
|
||||
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":1000}})")));
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(350));
|
||||
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":120}})")));
|
||||
ASSERT_TRUE(wait_for_condition([&session] {
|
||||
return successful_render_count(session) >= 4;
|
||||
}));
|
||||
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,"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,"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");
|
||||
SCOPED_TRACE(telemetry.dump(2));
|
||||
const auto& performance = telemetry.at("performance");
|
||||
RecordProperty("configured_fps", telemetry.at("kernel_observer")
|
||||
.at("configured_frequency_hz").get<double>());
|
||||
RecordProperty("measured_fps", performance.at("measured_fps").get<double>());
|
||||
RecordProperty("successful_render_count",
|
||||
performance.at("successful_render_count").get<std::uint64_t>());
|
||||
RecordProperty("limit_state",
|
||||
telemetry.at("kernel_observer").at("limit_state").get<std::string>());
|
||||
EXPECT_TRUE(performance.at("automatic_low_latency_scheduler").get<bool>());
|
||||
EXPECT_GT(performance.at("successful_render_count").get<std::uint64_t>(), 100U);
|
||||
EXPECT_GT(performance.at("measured_fps").get<double>(), 300.0);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("kernel_observer").at("configured_frequency_hz"), 1000.0);
|
||||
EXPECT_EQ(telemetry.at("kernel_observer").at("target_interval_ns"), 1'000'000);
|
||||
EXPECT_TRUE(telemetry.at("low_latency_limit").at("paint_limited").get<bool>() ||
|
||||
telemetry.at("low_latency_limit").at("render_limited").get<bool>());
|
||||
EXPECT_FALSE(telemetry.at("low_latency_limit").at("frequency_limited").get<bool>());
|
||||
EXPECT_GE(performance.at("successful_render_count").get<std::uint64_t>(), 4U);
|
||||
EXPECT_GT(performance.at("measured_fps").get<double>(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("kernel_observer").at("configured_frequency_hz"), 120.0);
|
||||
EXPECT_EQ(telemetry.at("kernel_observer").at("target_interval_ns"), 8'333'333);
|
||||
EXPECT_TRUE(telemetry.at("kernel_observer").at("limit_state") == "frequency_limited" ||
|
||||
telemetry.at("kernel_observer").at("limit_state") == "paint_limited" ||
|
||||
telemetry.at("kernel_observer").at("limit_state") == "render_limited");
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("transport_fps"), 120.0);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("presentation_fps"), 60.0);
|
||||
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("last_pixel_receive_age_ms"), 8.5);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("last_pixel_change_age_ms"), 12.5);
|
||||
}
|
||||
|
||||
TEST(RenderiveWebGallery, ProductionLowLatencyStopsWhileHiddenAndResumes) {
|
||||
Gallery_Plot_Session session(true);
|
||||
ASSERT_TRUE(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message("spectrum", "low_latency"))));
|
||||
ASSERT_TRUE(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Patch,
|
||||
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":120}})")));
|
||||
ASSERT_TRUE(wait_for_condition([&session] {
|
||||
return successful_render_count(session) >= 3;
|
||||
}));
|
||||
set_gallery_view_active(session, false);
|
||||
const auto stopped_count = successful_render_count(session);
|
||||
EXPECT_FALSE(session.handle(Frame_Request{}).has_value());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
||||
EXPECT_EQ(successful_render_count(session), stopped_count);
|
||||
set_gallery_view_active(session, true);
|
||||
EXPECT_TRUE(wait_for_condition([&session, stopped_count] {
|
||||
return successful_render_count(session) > stopped_count;
|
||||
}));
|
||||
EXPECT_TRUE(session.handle(Frame_Request{}).has_value());
|
||||
}
|
||||
|
||||
TEST(RenderiveWebGallery, ProductionLowLatencyOnlyRendersActiveSessions) {
|
||||
constexpr std::size_t session_count = 8;
|
||||
std::vector<std::unique_ptr<Gallery_Plot_Session>> sessions;
|
||||
sessions.reserve(session_count);
|
||||
for (std::size_t index = 0; index < session_count; ++index) {
|
||||
auto session = std::make_unique<Gallery_Plot_Session>(true);
|
||||
ASSERT_TRUE(session->handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message("spectrum", "low_latency"))));
|
||||
ASSERT_TRUE(session->handle(gallery_request(
|
||||
Gallery_Request_Kind::Patch,
|
||||
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":60}})")));
|
||||
if (index >= 2)
|
||||
set_gallery_view_active(*session, false);
|
||||
sessions.push_back(std::move(session));
|
||||
}
|
||||
for (std::size_t index = 0; index < 2; ++index) {
|
||||
ASSERT_TRUE(wait_for_condition([&sessions, index] {
|
||||
return successful_render_count(*sessions[index]) >= 2;
|
||||
}));
|
||||
}
|
||||
std::array<std::uint64_t, session_count> hidden_counts{};
|
||||
for (std::size_t index = 2; index < session_count; ++index)
|
||||
hidden_counts[index] = successful_render_count(*sessions[index]);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
||||
for (std::size_t index = 2; index < session_count; ++index)
|
||||
EXPECT_EQ(successful_render_count(*sessions[index]), hidden_counts[index]) << index;
|
||||
set_gallery_view_active(*sessions[0], false);
|
||||
set_gallery_view_active(*sessions[1], false);
|
||||
const auto first_stopped = successful_render_count(*sessions[0]);
|
||||
const auto second_stopped = successful_render_count(*sessions[1]);
|
||||
set_gallery_view_active(*sessions[2], true);
|
||||
set_gallery_view_active(*sessions[3], true);
|
||||
EXPECT_TRUE(wait_for_condition([&sessions, baseline = hidden_counts[2]] {
|
||||
return successful_render_count(*sessions[2]) > baseline;
|
||||
}));
|
||||
EXPECT_TRUE(wait_for_condition([&sessions, baseline = hidden_counts[3]] {
|
||||
return successful_render_count(*sessions[3]) > baseline;
|
||||
}));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
||||
EXPECT_EQ(successful_render_count(*sessions[0]), first_stopped);
|
||||
EXPECT_EQ(successful_render_count(*sessions[1]), second_stopped);
|
||||
}
|
||||
|
||||
void expect_low_latency_canvas_to_change(std::string_view case_id) {
|
||||
SCOPED_TRACE(std::string(case_id));
|
||||
Gallery_Plot_Session session(true);
|
||||
|
||||
Reference in New Issue
Block a user