This commit is contained in:
2026-08-12 13:46:53 +08:00
parent 898a58b554
commit d7cf626c25
22 changed files with 1639 additions and 292 deletions
+182 -22
View File
@@ -1,13 +1,21 @@
#pragma once
#include "Gallery_Observer_Adminive.h"
#include "Gallery_Renderables.h"
#include <renderive/scene/base/Scene_Base.hpp>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <array>
#include <charconv>
#include <concepts>
#include <functional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -28,28 +36,6 @@ public:
add(std::move(id), adminive::Type_Descriptor<Object>::get().label(), object);
}
private:
template <class Object>
void add(std::string id, std::string title, Object& object) {
using Model = adminive::Object_Model_Type<Object>;
auto* target = &object;
entries_.push_back({
std::move(id), std::move(title),
[target] {
return Json{
{"descriptor", adminive::to_descriptor_json<Json, Object>()},
{"view", adminive::to_view_json<Json, Model>(
adminive::describe_edit_view<Model>())},
{"data", adminive::to_frontend_json<Json>(*target)}
};
},
[target](const Json& patch) {
return adminive::apply_frontend_patch<Json>(*target, patch);
}
});
}
public:
[[nodiscard]] Json resources() const {
Json result = Json::array();
for (const auto& entry : entries_) {
@@ -61,6 +47,92 @@ public:
return result;
}
[[nodiscard]] Json observers(const Scene_Base& scene) const {
Json result = Json::array();
const auto topology = scene.topology_snapshot();
for (std::size_t index = 0; index < topology.renderables.size(); ++index) {
const auto* renderable =
dynamic_cast<const renderive::Renderable*>(topology.renderables[index].get());
if (!renderable)
continue;
const Entry* entry = find(renderable);
std::string title = entry ? entry->title : renderable->object_name();
if (title.empty())
title = "渲染节点 " + std::to_string(index + 1);
Json resource{
{"descriptor", adminive::to_descriptor_json<
Json, renderive::Renderable_Observation>()},
{"data", adminive::to_frontend_json<Json>(renderable->observation())},
{"target", entry ? entry->id : node_id(index)},
{"title", std::move(title)}
};
result.push_back(std::move(resource));
}
return result;
}
[[nodiscard]] Json task_graph(const Scene_Base& scene) const {
const auto topology = scene.topology_snapshot();
const auto paint_order = scene.paint_order_snapshot();
std::unordered_map<const Renderable_Base*, std::size_t> indices;
indices.reserve(topology.renderables.size());
for (std::size_t index = 0; index < topology.renderables.size(); ++index)
indices.emplace(topology.renderables[index].get(), index);
std::unordered_map<const Renderable_Base*, std::size_t> paint_indices;
paint_indices.reserve(paint_order.size());
for (std::size_t index = 0; index < paint_order.size(); ++index)
paint_indices.emplace(paint_order[index].get(), index + 1);
Json nodes = Json::array();
Json edges = Json::array();
Json ordered = Json::array();
for (std::size_t index = 0; index < topology.renderables.size(); ++index) {
const auto* base = topology.renderables[index].get();
const auto* renderable = dynamic_cast<const renderive::Renderable*>(base);
const Entry* entry = find(base);
std::string label = entry ? entry->title : std::string{};
if (label.empty() && renderable)
label = renderable->object_name();
if (label.empty())
label = "渲染节点 " + std::to_string(index + 1);
Json node{
{"id", node_id(index)},
{"label", std::move(label)},
{"kind", entry ? "control" : "layer"},
{"paint_order", paint_indices.contains(base) ? paint_indices.at(base) : 0}
};
if (entry)
node["target"] = entry->id;
nodes.push_back(std::move(node));
}
append_relationships(edges, topology.display, indices, "display");
append_relationships(edges, topology.dependency, indices, "dependency");
for (const auto& renderable : paint_order)
ordered.push_back(node_id(indices.at(renderable.get())));
Json control_graphs = Json::array();
for (const auto& entry : entries_) {
if (!entry.renderable)
continue;
control_graphs.push_back({
{"target", entry.id},
{"title", entry.title},
{"graph", renderable_task_graph(*entry.renderable)}
});
}
Json result{
{"nodes", std::move(nodes)},
{"edges", std::move(edges)},
{"paint_order", std::move(ordered)},
{"renderables", std::move(control_graphs)}
};
result["topology_id"] = topology_id(result);
return result;
}
[[nodiscard]] adminive::Update_Result apply(std::string_view target,
const Json& patch) const {
for (const auto& entry : entries_) {
@@ -79,8 +151,96 @@ private:
std::string title;
std::function<Json()> resource;
std::function<adminive::Update_Result(const Json&)> apply;
renderive::Renderable* renderable{};
};
template <class Object>
void add(std::string id, std::string title, Object& object) {
using Model = adminive::Object_Model_Type<Object>;
auto* target = &object;
renderive::Renderable* renderable{};
if constexpr (std::derived_from<Object, renderive::Renderable>)
renderable = target;
entries_.push_back({
std::move(id), std::move(title),
[target] {
return Json{
{"descriptor", adminive::to_descriptor_json<Json, Object>()},
{"view", adminive::to_view_json<Json, Model>(
adminive::describe_edit_view<Model>())},
{"data", adminive::to_frontend_json<Json>(*target)}
};
},
[target](const Json& patch) {
return adminive::apply_frontend_patch<Json>(*target, patch);
},
renderable
});
}
[[nodiscard]] const Entry* find(const Renderable_Base* renderable) const noexcept {
const auto found = std::find_if(entries_.begin(), entries_.end(),
[renderable](const Entry& entry) { return entry.renderable == renderable; });
return found == entries_.end() ? nullptr : &*found;
}
static std::string node_id(std::size_t index) {
return "renderable-" + std::to_string(index);
}
static void append_relationships(
Json& edges,
const std::vector<Scene_Base::Topology_Relationship>& relationships,
const std::unordered_map<const Renderable_Base*, std::size_t>& indices,
std::string_view kind) {
for (const auto& relationship : relationships) {
if (!relationship.parent)
continue;
edges.push_back({
{"from", node_id(indices.at(relationship.parent.get()))},
{"to", node_id(indices.at(relationship.child.get()))},
{"kind", kind}
});
}
}
static std::string topology_id(const Json& topology) {
std::uint64_t hash = 1469598103934665603ULL;
for (const unsigned char byte : topology.dump()) {
hash ^= byte;
hash *= 1099511628211ULL;
}
std::array<char, 17> text{};
const auto result = std::to_chars(text.data(), text.data() + text.size() - 1,
hash, 16);
return {text.data(), result.ptr};
}
static Json renderable_task_graph(Renderable_Base& renderable) {
const auto graph = renderable.task_graph();
Json nodes = Json::array();
Json edges = Json::array();
for (std::size_t index = 0; index < graph->nodes().size(); ++index) {
const auto& node = graph->nodes()[index];
nodes.push_back({
{"id", "task-" + std::to_string(index)},
{"label", node.name.empty() ? "绘制任务 " + std::to_string(index + 1)
: std::string(node.name)},
{"kind", "task"}
});
for (const std::size_t successor : node.successors) {
edges.push_back({
{"from", "task-" + std::to_string(index)},
{"to", "task-" + std::to_string(successor)},
{"kind", "dependency"}
});
}
}
Json result{{"nodes", std::move(nodes)}, {"edges", std::move(edges)}};
result["topology_id"] = topology_id(result);
return result;
}
std::vector<Entry> entries_;
};
+188 -11
View File
@@ -2,6 +2,7 @@
#include "Gallery_Enum.h"
#include "render_2D/plot/Plot_Core.h"
#include "render_2D/renderable/Renderable.h"
#include "adminive/adminive.hpp"
#include "adminive/adapters/magic_enum.hpp"
@@ -24,6 +25,63 @@ struct Gallery_Consumer_Feedback_Snapshot {
std::uint64_t manual_interval_ns{};
};
struct Gallery_Render_Performance {
std::uint64_t render_attempt_count{};
std::uint64_t successful_render_count{};
std::uint64_t failed_render_count{};
double measured_fps{};
double lifetime_average_fps{};
double last_render_ms{};
double average_render_ms{};
double maximum_render_ms{};
double render_deviation_ms{};
double render_p50_ms{};
double render_p95_ms{};
double render_p99_ms{};
std::uint64_t render_sample_count{};
double pixel_response_fps{};
double last_pixel_snapshot_ms{};
double last_pixel_encode_ms{};
double average_pixel_encode_ms{};
double maximum_pixel_encode_ms{};
double pixel_encode_deviation_ms{};
double pixel_encode_p50_ms{};
double pixel_encode_p95_ms{};
double pixel_encode_p99_ms{};
std::uint64_t pixel_encode_sample_count{};
double last_pixel_request_ms{};
double average_pixel_request_ms{};
double pixel_request_deviation_ms{};
double pixel_request_p95_ms{};
double pixel_request_p99_ms{};
std::uint64_t last_pixel_bytes{};
double pixel_payload_megabytes_per_second{};
bool automatic_low_latency_scheduler{};
};
struct Gallery_Client_Performance {
double transport_fps{};
double presentation_fps{};
std::uint64_t websocket_buffered_bytes{};
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 frame_round_trip_average_ms{};
double frame_round_trip_deviation_ms{};
double frame_round_trip_p95_ms{};
double frame_round_trip_p99_ms{};
double display_interval_ms{};
double display_interval_average_ms{};
double display_interval_latest_ms{};
double display_interval_p95_ms{};
double display_interval_p99_ms{};
double display_interval_deviation_ms{};
std::uint64_t overwritten_pixel_frames{};
double last_pixel_receive_age_ms{};
double last_pixel_change_age_ms{};
};
} // namespace renderive::web
namespace adminive {
@@ -49,7 +107,10 @@ struct Type_Descriptor<renderive::Frame_Observer_Snapshot> {
static auto get() {
using T = renderive::Frame_Observer_Snapshot;
return object<T>("kernel_observer",
ADMINIVE_FIELD_LABEL(T, mode, "模式"),
ADMINIVE_FIELD_LABEL(T, mode, "模式")
.enum_label<renderive::Frame_Control_Mode::Manual>("手动刷新")
.enum_label<renderive::Frame_Control_Mode::Low_Latency>("低延迟")
.enum_label<renderive::Frame_Control_Mode::Playback>("回放队列"),
ADMINIVE_FIELD_LABEL(T, last_event, "最近事件"),
ADMINIVE_FIELD_LABEL(T, limit_state, "当前瓶颈"),
ADMINIVE_FIELD_LABEL(T, frequency_hz, "配置频率"),
@@ -60,11 +121,11 @@ struct Type_Descriptor<renderive::Frame_Observer_Snapshot> {
ADMINIVE_FIELD_LABEL(T, failed_operation_count, "失败"),
ADMINIVE_FIELD_LABEL(T, pending_frame_count, "待处理"),
ADMINIVE_FIELD_LABEL(T, latest_sequence, "最新序号"),
ADMINIVE_FIELD_LABEL(T, paint_duration_ns, "PaintEvent"),
ADMINIVE_FIELD_LABEL(T, paint_duration_ns, "绘制事件耗时"),
ADMINIVE_FIELD_LABEL(T, render_duration_ns, "后台渲染"),
ADMINIVE_FIELD_LABEL(T, target_interval_ns, "目标间隔"),
ADMINIVE_FIELD_LABEL(T, frequency_limit_enabled, "频率限制"),
ADMINIVE_FIELD_LABEL(T, consumer_feedback_enabled, "Kernel 反馈有效"),
ADMINIVE_FIELD_LABEL(T, consumer_feedback_enabled, "内核反馈有效"),
ADMINIVE_FIELD_LABEL(T, bottleneck_duration_ns, "内部瓶颈"),
ADMINIVE_FIELD_LABEL(T, consumer_sample_interval_ns, "消费者原始采样"),
ADMINIVE_FIELD_LABEL(T, consumer_smoothed_interval_ns, "消费者平滑周期"),
@@ -72,14 +133,14 @@ struct Type_Descriptor<renderive::Frame_Observer_Snapshot> {
ADMINIVE_FIELD_LABEL(T, consumer_safety_interval_ns, "消费者安全期限"),
ADMINIVE_FIELD_LABEL(T, consumer_interval_ns, "消费者限速周期"),
ADMINIVE_FIELD_LABEL(T, next_refresh_interval_ns, "下次刷新"),
ADMINIVE_FIELD_LABEL(T, paint_lease_wait_ns, "Painter Lease 等待"),
ADMINIVE_FIELD_LABEL(T, paint_state_wait_ns, "Paint State 等待"),
ADMINIVE_FIELD_LABEL(T, publish_state_wait_ns, "Publish State 等待"),
ADMINIVE_FIELD_LABEL(T, ready_wait_ns, "Ready 等待"),
ADMINIVE_FIELD_LABEL(T, paint_lease_wait_ns, "绘制租约等待"),
ADMINIVE_FIELD_LABEL(T, paint_state_wait_ns, "绘制状态等待"),
ADMINIVE_FIELD_LABEL(T, publish_state_wait_ns, "发布状态等待"),
ADMINIVE_FIELD_LABEL(T, ready_wait_ns, "就绪等待"),
ADMINIVE_FIELD_LABEL(T, frame_age_at_render_ns, "开始渲染时帧龄"),
ADMINIVE_FIELD_LABEL(T, render_lease_wait_ns, "Render Lease 等待"),
ADMINIVE_FIELD_LABEL(T, render_state_wait_ns, "Render State 等待"),
ADMINIVE_FIELD_LABEL(T, render_finish_state_wait_ns, "Render Finish 等待"),
ADMINIVE_FIELD_LABEL(T, render_lease_wait_ns, "渲染租约等待"),
ADMINIVE_FIELD_LABEL(T, render_state_wait_ns, "渲染状态等待"),
ADMINIVE_FIELD_LABEL(T, render_finish_state_wait_ns, "渲染完成等待"),
ADMINIVE_FIELD_LABEL(T, queue_wait_ns, "回放队列等待"),
ADMINIVE_FIELD_LABEL(T, end_to_end_ns, "端到端延迟"),
ADMINIVE_FIELD_LABEL(T, task_execution_state, "任务执行状态")
@@ -88,7 +149,7 @@ struct Type_Descriptor<renderive::Frame_Observer_Snapshot> {
.enum_label<Task_Execution_State::Running>("执行中")
.enum_label<Task_Execution_State::Completed>("已完成")
.enum_label<Task_Execution_State::Failed>("失败"),
ADMINIVE_FIELD_LABEL(T, task_worker_count, "Taskflow 工作线程"),
ADMINIVE_FIELD_LABEL(T, task_worker_count, "任务工作线程"),
ADMINIVE_FIELD_LABEL(T, task_graph_node_count, "任务图节点"),
ADMINIVE_FIELD_LABEL(T, task_completed_node_count, "已完成节点"),
ADMINIVE_FIELD_LABEL(T, task_running_node_count, "运行中节点"),
@@ -118,7 +179,123 @@ struct Type_Descriptor<renderive::web::Gallery_Consumer_Feedback_Snapshot> {
}
};
template <>
struct Type_Descriptor<renderive::web::Gallery_Render_Performance> {
static auto get() {
using T = renderive::web::Gallery_Render_Performance;
return object<T>("render_performance",
ADMINIVE_FIELD_LABEL(T, render_attempt_count, "渲染尝试"),
ADMINIVE_FIELD_LABEL(T, successful_render_count, "渲染成功"),
ADMINIVE_FIELD_LABEL(T, failed_render_count, "渲染失败"),
ADMINIVE_FIELD_LABEL(T, measured_fps, "最近渲染帧率"),
ADMINIVE_FIELD_LABEL(T, lifetime_average_fps, "平均渲染帧率"),
ADMINIVE_FIELD_LABEL(T, last_render_ms, "最近渲染耗时"),
ADMINIVE_FIELD_LABEL(T, average_render_ms, "平均渲染耗时"),
ADMINIVE_FIELD_LABEL(T, maximum_render_ms, "最大渲染耗时"),
ADMINIVE_FIELD_LABEL(T, render_deviation_ms, "渲染标准差"),
ADMINIVE_FIELD_LABEL(T, render_p50_ms, "渲染 P50"),
ADMINIVE_FIELD_LABEL(T, render_p95_ms, "渲染 P95"),
ADMINIVE_FIELD_LABEL(T, render_p99_ms, "渲染 P99"),
ADMINIVE_FIELD_LABEL(T, render_sample_count, "渲染窗口样本"),
ADMINIVE_FIELD_LABEL(T, pixel_response_fps, "像素响应帧率"),
ADMINIVE_FIELD_LABEL(T, last_pixel_snapshot_ms, "最近像素快照耗时"),
ADMINIVE_FIELD_LABEL(T, last_pixel_encode_ms, "最近像素编码耗时"),
ADMINIVE_FIELD_LABEL(T, average_pixel_encode_ms, "平均像素编码耗时"),
ADMINIVE_FIELD_LABEL(T, maximum_pixel_encode_ms, "最大像素编码耗时"),
ADMINIVE_FIELD_LABEL(T, pixel_encode_deviation_ms, "像素编码标准差"),
ADMINIVE_FIELD_LABEL(T, pixel_encode_p50_ms, "像素编码 P50"),
ADMINIVE_FIELD_LABEL(T, pixel_encode_p95_ms, "像素编码 P95"),
ADMINIVE_FIELD_LABEL(T, pixel_encode_p99_ms, "像素编码 P99"),
ADMINIVE_FIELD_LABEL(T, pixel_encode_sample_count, "像素编码窗口样本"),
ADMINIVE_FIELD_LABEL(T, last_pixel_request_ms, "最近像素请求耗时"),
ADMINIVE_FIELD_LABEL(T, average_pixel_request_ms, "像素请求滑动平均"),
ADMINIVE_FIELD_LABEL(T, pixel_request_deviation_ms, "像素请求标准差"),
ADMINIVE_FIELD_LABEL(T, pixel_request_p95_ms, "像素请求 P95"),
ADMINIVE_FIELD_LABEL(T, pixel_request_p99_ms, "像素请求 P99"),
ADMINIVE_FIELD_LABEL(T, last_pixel_bytes, "最近像素负载"),
ADMINIVE_FIELD_LABEL(T, pixel_payload_megabytes_per_second, "像素负载吞吐"),
ADMINIVE_FIELD_LABEL(T, automatic_low_latency_scheduler, "自动低延迟调度"))
.label("渲染性能");
}
};
template <>
struct Type_Descriptor<renderive::web::Gallery_Client_Performance> {
static auto get() {
using T = renderive::web::Gallery_Client_Performance;
return object<T>("client_performance",
ADMINIVE_FIELD_LABEL(T, transport_fps, "像素响应帧率"),
ADMINIVE_FIELD_LABEL(T, presentation_fps, "浏览器呈现帧率"),
ADMINIVE_FIELD_LABEL(T, websocket_buffered_bytes, "WebSocket 缓冲"),
ADMINIVE_FIELD_LABEL(T, changed_pixel_frames, "变化像素帧"),
ADMINIVE_FIELD_LABEL(T, duplicate_pixel_frames, "重复像素帧"),
ADMINIVE_FIELD_LABEL(T, frame_request_timeout_count, "像素请求超时"),
ADMINIVE_FIELD_LABEL(T, frame_round_trip_ms, "WebSocket 往返耗时"),
ADMINIVE_FIELD_LABEL(T, frame_round_trip_average_ms, "WebSocket 往返滑动平均"),
ADMINIVE_FIELD_LABEL(T, frame_round_trip_deviation_ms, "WebSocket 往返标准差"),
ADMINIVE_FIELD_LABEL(T, frame_round_trip_p95_ms, "WebSocket 往返 P95"),
ADMINIVE_FIELD_LABEL(T, frame_round_trip_p99_ms, "WebSocket 往返 P99"),
ADMINIVE_FIELD_LABEL(T, display_interval_ms, "呈现中位周期"),
ADMINIVE_FIELD_LABEL(T, display_interval_average_ms, "呈现滑动平均周期"),
ADMINIVE_FIELD_LABEL(T, display_interval_latest_ms, "最近呈现周期"),
ADMINIVE_FIELD_LABEL(T, display_interval_p95_ms, "呈现 P95 周期"),
ADMINIVE_FIELD_LABEL(T, display_interval_p99_ms, "呈现 P99 周期"),
ADMINIVE_FIELD_LABEL(T, display_interval_deviation_ms, "呈现周期标准差"),
ADMINIVE_FIELD_LABEL(T, overwritten_pixel_frames, "未呈现覆盖帧"),
ADMINIVE_FIELD_LABEL(T, last_pixel_receive_age_ms, "最近像素龄"),
ADMINIVE_FIELD_LABEL(T, last_pixel_change_age_ms, "最近变化龄"))
.label("浏览器性能");
}
};
template <>
struct Type_Descriptor<renderive::Renderable_Observation> {
static auto get() {
using T = renderive::Renderable_Observation;
return object<T>("renderable_observer",
ADMINIVE_FIELD_LABEL(T, event, "最近状态事件")
.enum_label<renderive::Renderable_Observer_Event::None>("尚无事件")
.enum_label<renderive::Renderable_Observer_Event::Cache_Updated>("缓存状态已更新")
.enum_label<renderive::Renderable_Observer_Event::Published>("渲染状态已发布")
.enum_label<renderive::Renderable_Observer_Event::Render_Started>("绘制已开始")
.enum_label<renderive::Renderable_Observer_Event::Render_Completed>("绘制已完成")
.enum_label<renderive::Renderable_Observer_Event::Render_Failed>("绘制失败"),
ADMINIVE_FIELD_LABEL(T, event_time_ns, "事件时间"),
ADMINIVE_FIELD_LABEL(T, cache_update_count, "缓存更新次数"),
ADMINIVE_FIELD_LABEL(T, publish_count, "状态发布次数"),
ADMINIVE_FIELD_LABEL(T, successful_render_count, "绘制完成次数"),
ADMINIVE_FIELD_LABEL(T, failed_render_count, "绘制失败次数"),
ADMINIVE_FIELD_LABEL(T, last_render_sequence, "最近绘制序号"),
ADMINIVE_FIELD_LABEL(T, last_render_duration_ns, "最近绘制墙钟耗时"),
ADMINIVE_FIELD_LABEL(T, total_render_duration_ns, "累计绘制墙钟耗时"),
ADMINIVE_FIELD_LABEL(T, maximum_render_duration_ns, "最大绘制墙钟耗时"),
ADMINIVE_FIELD_LABEL(T, render_duration_sample_count, "绘制统计样本"),
ADMINIVE_FIELD_LABEL(T, render_duration_average_ns, "绘制滑动平均"),
ADMINIVE_FIELD_LABEL(T, render_duration_deviation_ns, "绘制标准差"),
ADMINIVE_FIELD_LABEL(T, render_duration_p50_ns, "绘制 P50"),
ADMINIVE_FIELD_LABEL(T, render_duration_p95_ns, "绘制 P95"),
ADMINIVE_FIELD_LABEL(T, render_duration_p99_ns, "绘制 P99"),
ADMINIVE_FIELD_LABEL(T, task_execution_count, "任务执行次数"),
ADMINIVE_FIELD_LABEL(T, failed_task_count, "任务失败次数"),
ADMINIVE_FIELD_LABEL(T, last_task_duration_ns, "最近任务耗时"),
ADMINIVE_FIELD_LABEL(T, total_task_duration_ns, "累计任务耗时"),
ADMINIVE_FIELD_LABEL(T, maximum_task_duration_ns, "最大任务耗时"),
ADMINIVE_FIELD_LABEL(T, task_duration_sample_count, "任务统计样本"),
ADMINIVE_FIELD_LABEL(T, task_duration_average_ns, "任务滑动平均"),
ADMINIVE_FIELD_LABEL(T, task_duration_deviation_ns, "任务标准差"),
ADMINIVE_FIELD_LABEL(T, task_duration_p50_ns, "任务 P50"),
ADMINIVE_FIELD_LABEL(T, task_duration_p95_ns, "任务 P95"),
ADMINIVE_FIELD_LABEL(T, task_duration_p99_ns, "任务 P99"),
ADMINIVE_FIELD_LABEL(T, last_task_count, "最近任务数"),
ADMINIVE_FIELD_LABEL(T, peak_parallelism, "峰值并行度"))
.label("渲染对象观察器");
}
};
static_assert(Described_Type<renderive::Frame_Observer_Snapshot>);
static_assert(Described_Type<renderive::web::Gallery_Consumer_Feedback_Snapshot>);
static_assert(Described_Type<renderive::web::Gallery_Render_Performance>);
static_assert(Described_Type<renderive::web::Gallery_Client_Performance>);
static_assert(Described_Type<renderive::Renderable_Observation>);
} // namespace adminive
+185 -129
View File
@@ -7,6 +7,7 @@
#include "Pixel_Frame.h"
#include "Web_Performance_Log.h"
#include "render_2D/export.h"
#include <renderive/base/statistics/Rolling_Statistics.hpp>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <array>
@@ -94,9 +95,10 @@ public:
plot_.init();
plot_.set_viewport_size({560, 320});
root_ = plot_.root_renderable();
axes_node_ = plot_.create_renderable_node(root_, "Gallery_Axes");
data_node_ = plot_.create_renderable_node(root_, "Gallery_Data");
overlay_node_ = plot_.create_renderable_node(root_, "Gallery_Overlay");
root_->set_object_name("画布根节点");
axes_node_ = plot_.create_renderable_node(root_, "坐标轴层");
data_node_ = plot_.create_renderable_node(root_, "数据绘制层");
overlay_node_ = plot_.create_renderable_node(root_, "交互覆盖层");
axes_node_->set_cache_mode(Renderable_Cache_Mode::Local_Pixel);
attach_performance_overlay(plot_);
build_axes();
@@ -121,11 +123,37 @@ public:
return case_id_;
}
[[nodiscard]] nlohmann::json controls() const {
return controls_.resources();
return {
{"resources", controls_.resources()},
{"observers", controls_.observers(root_->scene())},
{"task_graph", controls_.task_graph(root_->scene())}
};
}
[[nodiscard]] Gallery_Frame_Mode frame_mode() const noexcept {
return frame_mode_;
}
void reset_monitoring() {
root_->scene().reset_renderable_task_observations();
performance_started_ = std::chrono::steady_clock::now();
last_performance_log_ = performance_started_;
render_attempt_count_ = 0;
successful_render_count_ = 0;
last_render_ms_ = 0.0;
render_duration_statistics_.clear();
render_history_.clear();
pixel_frame_count_ = 0;
last_pixel_snapshot_ms_ = 0.0;
last_pixel_encode_ms_ = 0.0;
last_pixel_request_ms_ = 0.0;
last_pixel_bytes_ = 0;
pixel_encode_statistics_.clear();
pixel_request_statistics_.clear();
pixel_history_.clear();
client_performance_ = {};
consumer_pixel_interval_ns_ = 0;
consumer_presentation_interval_ns_ = 0;
apply_consumer_feedback();
}
[[nodiscard]] bool can_render_automatically() const noexcept {
return automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency &&
plot_.view_active();
@@ -133,33 +161,26 @@ public:
[[nodiscard]] std::uint64_t kernel_refresh_interval_ns() const {
return plot_.refresh_feedback_snapshot().next_refresh_interval_ns;
}
void set_client_metrics(double transport_fps, double presentation_fps,
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 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 {
client_transport_fps_ = std::isfinite(transport_fps) ? std::clamp(transport_fps, 0.0, 100000.0) : 0.0;
client_presentation_fps_ = std::isfinite(presentation_fps) ? std::clamp(presentation_fps, 0.0, 100000.0) : 0.0;
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_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_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;
client_last_pixel_change_age_ms_ = std::isfinite(last_pixel_change_age_ms) ? std::max(0.0, last_pixel_change_age_ms) : 0.0;
void set_client_metrics(Gallery_Client_Performance metrics) noexcept {
const auto finite = [](double value, double maximum = 100000.0) {
return std::isfinite(value) ? std::clamp(value, 0.0, maximum) : 0.0;
};
metrics.transport_fps = finite(metrics.transport_fps);
metrics.presentation_fps = finite(metrics.presentation_fps);
metrics.frame_round_trip_ms = finite(metrics.frame_round_trip_ms);
metrics.frame_round_trip_average_ms = finite(metrics.frame_round_trip_average_ms);
metrics.frame_round_trip_deviation_ms = finite(metrics.frame_round_trip_deviation_ms);
metrics.frame_round_trip_p95_ms = finite(metrics.frame_round_trip_p95_ms);
metrics.frame_round_trip_p99_ms = finite(metrics.frame_round_trip_p99_ms);
metrics.display_interval_ms = finite(metrics.display_interval_ms);
metrics.display_interval_average_ms = finite(metrics.display_interval_average_ms);
metrics.display_interval_latest_ms = finite(metrics.display_interval_latest_ms);
metrics.display_interval_p95_ms = finite(metrics.display_interval_p95_ms);
metrics.display_interval_p99_ms = finite(metrics.display_interval_p99_ms);
metrics.display_interval_deviation_ms = finite(metrics.display_interval_deviation_ms);
metrics.last_pixel_receive_age_ms = finite(metrics.last_pixel_receive_age_ms);
metrics.last_pixel_change_age_ms = finite(metrics.last_pixel_change_age_ms);
client_performance_ = metrics;
apply_consumer_feedback();
}
[[nodiscard]] adminive::Update_Result apply_patch(std::string_view target,
@@ -421,6 +442,9 @@ public:
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);
const auto render_window = render_duration_statistics_.snapshot();
const auto pixel_encode_window = pixel_encode_statistics_.snapshot();
const auto pixel_request_window = pixel_request_statistics_.snapshot();
const bool low_latency = frame_mode_ == Gallery_Frame_Mode::Low_Latency;
const Gallery_Consumer_Feedback_Snapshot consumer_feedback{
low_latency && feedback_policy_.enabled,
@@ -432,10 +456,51 @@ public:
consumer_pixel_interval_ns_,
consumer_presentation_interval_ns_,
consumer_manual_interval_ns_};
Gallery_Render_Performance render_performance;
render_performance.render_attempt_count = render_attempt_count_;
render_performance.successful_render_count = successful_render_count_;
render_performance.failed_render_count =
render_attempt_count_ - successful_render_count_;
render_performance.measured_fps = render_fps;
render_performance.lifetime_average_fps =
static_cast<double>(successful_render_count_) / elapsed_seconds;
render_performance.last_render_ms = last_render_ms_;
render_performance.average_render_ms = render_window.average;
render_performance.maximum_render_ms = render_window.maximum;
render_performance.render_deviation_ms = render_window.deviation;
render_performance.render_p50_ms = render_window.p50;
render_performance.render_p95_ms = render_window.p95;
render_performance.render_p99_ms = render_window.p99;
render_performance.render_sample_count = render_window.sample_count;
render_performance.pixel_response_fps = pixel_fps;
render_performance.last_pixel_snapshot_ms = last_pixel_snapshot_ms_;
render_performance.last_pixel_encode_ms = last_pixel_encode_ms_;
render_performance.average_pixel_encode_ms = pixel_encode_window.average;
render_performance.maximum_pixel_encode_ms = pixel_encode_window.maximum;
render_performance.pixel_encode_deviation_ms = pixel_encode_window.deviation;
render_performance.pixel_encode_p50_ms = pixel_encode_window.p50;
render_performance.pixel_encode_p95_ms = pixel_encode_window.p95;
render_performance.pixel_encode_p99_ms = pixel_encode_window.p99;
render_performance.pixel_encode_sample_count = pixel_encode_window.sample_count;
render_performance.last_pixel_request_ms = last_pixel_request_ms_;
render_performance.average_pixel_request_ms = pixel_request_window.average;
render_performance.pixel_request_deviation_ms = pixel_request_window.deviation;
render_performance.pixel_request_p95_ms = pixel_request_window.p95;
render_performance.pixel_request_p99_ms = pixel_request_window.p99;
render_performance.last_pixel_bytes = last_pixel_bytes_;
render_performance.pixel_payload_megabytes_per_second =
pixel_megabytes_per_second;
render_performance.automatic_low_latency_scheduler =
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency;
const Gallery_Client_Performance client_performance = client_performance_;
nlohmann::json observer_data =
adminive::to_frontend_json<nlohmann::json>(observer);
nlohmann::json consumer_feedback_data =
adminive::to_frontend_json<nlohmann::json>(consumer_feedback);
nlohmann::json render_performance_data =
adminive::to_frontend_json<nlohmann::json>(render_performance);
nlohmann::json client_performance_data =
adminive::to_frontend_json<nlohmann::json>(client_performance);
nlohmann::json telemetry{
{"case", case_id_},
{"frame_mode", gallery_enum_id(frame_mode_)},
@@ -448,50 +513,11 @@ public:
},
{"view_active", plot_.view_active()},
{"kernel_frame_count", plot_.diagnostics().refresh.frame_count},
{
"performance", {
{"render_attempt_count", render_attempt_count_},
{"successful_render_count", successful_render_count_},
{"failed_render_count", render_attempt_count_ - successful_render_count_},
{"measured_fps", render_fps},
{"lifetime_average_fps", static_cast<double>(successful_render_count_) / elapsed_seconds},
{"last_render_ms", last_render_ms_},
{"average_render_ms", successful_render_count_ == 0 ? 0.0 : total_render_ms_ / static_cast<double>(successful_render_count_)},
{"maximum_render_ms", maximum_render_ms_},
{"pixel_response_fps", pixel_fps},
{"last_pixel_snapshot_ms", last_pixel_snapshot_ms_},
{"last_pixel_encode_ms", last_pixel_encode_ms_},
{"average_pixel_encode_ms", pixel_frame_count_ == 0 ? 0.0 : total_pixel_encode_ms_ / static_cast<double>(pixel_frame_count_)},
{"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_megabytes_per_second},
{
"automatic_low_latency_scheduler",
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency
}
}
},
{"performance", std::move(render_performance_data)},
{"kernel_observer", std::move(observer_data)},
{"consumer_feedback", std::move(consumer_feedback_data)},
{
"client_performance", {
{"transport_fps", client_transport_fps_},
{"presentation_fps", client_presentation_fps_},
{"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_},
{"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_}
}
},
{"client_performance", std::move(client_performance_data)},
{"renderable_observers", controls_.observers(root_->scene())},
{"last_action_result", last_action_result_}
};
if (primary_) {
@@ -703,8 +729,9 @@ private:
void apply_consumer_feedback() {
if (frame_mode_ != Gallery_Frame_Mode::Low_Latency)
return;
consumer_pixel_interval_ns_ = frequency_to_ns(client_transport_fps_);
consumer_presentation_interval_ns_ = milliseconds_to_ns(client_display_interval_ms_);
consumer_pixel_interval_ns_ = frequency_to_ns(client_performance_.transport_fps);
consumer_presentation_interval_ns_ =
milliseconds_to_ns(client_performance_.display_interval_ms);
consumer_manual_interval_ns_ = frequency_to_ns(feedback_policy_.manual_fps);
consumer_feedback_source_ = "none";
if (!feedback_policy_.enabled) {
@@ -746,10 +773,9 @@ private:
const double duration_ms =
std::chrono::duration<double, std::milli>(finished - started).count();
last_render_ms_ = duration_ms;
maximum_render_ms_ = std::max(maximum_render_ms_, duration_ms);
if (rendered) {
++successful_render_count_;
total_render_ms_ += duration_ms;
render_duration_statistics_.add(duration_ms);
record_timestamp(render_history_, finished);
}
maybe_log_performance(finished);
@@ -764,8 +790,8 @@ private:
std::chrono::duration<double, std::milli>(encode_finished - encode_started).count();
last_pixel_request_ms_ =
std::chrono::duration<double, std::milli>(encode_finished - request_started).count();
maximum_pixel_encode_ms_ = std::max(maximum_pixel_encode_ms_, last_pixel_encode_ms_);
total_pixel_encode_ms_ += last_pixel_encode_ms_;
pixel_encode_statistics_.add(last_pixel_encode_ms_);
pixel_request_statistics_.add(last_pixel_request_ms_);
last_pixel_bytes_ = pixel_bytes;
++pixel_frame_count_;
record_pixel_sample(pixel_history_, encode_finished, pixel_bytes);
@@ -802,26 +828,27 @@ private:
{"consumer_feedback_interval_ns", low_latency ? observer.consumer_interval_ns : 0},
{"backend_render_fps", recent_rate(render_history_, now)},
{"pixel_response_fps", pixel_fps},
{"client_transport_fps", client_transport_fps_},
{"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_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_},
{"client_transport_fps", client_performance_.transport_fps},
{"client_presentation_fps", client_performance_.presentation_fps},
{"client_changed_pixel_frames", client_performance_.changed_pixel_frames},
{"client_duplicate_pixel_frames", client_performance_.duplicate_pixel_frames},
{"client_frame_request_timeout_count", client_performance_.frame_request_timeout_count},
{"client_frame_round_trip_ms", client_performance_.frame_round_trip_ms},
{"client_display_interval_ms", client_performance_.display_interval_ms},
{"client_display_interval_latest_ms", client_performance_.display_interval_latest_ms},
{"client_display_interval_p95_ms", client_performance_.display_interval_p95_ms},
{"client_display_interval_p99_ms", client_performance_.display_interval_p99_ms},
{"client_display_interval_deviation_ms", client_performance_.display_interval_deviation_ms},
{"client_overwritten_pixel_frames", client_performance_.overwritten_pixel_frames},
{"client_last_pixel_receive_age_ms", client_performance_.last_pixel_receive_age_ms},
{"client_last_pixel_change_age_ms", client_performance_.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_megabytes_per_second},
{"websocket_buffered_bytes", client_buffered_bytes_},
{"websocket_buffered_bytes", client_performance_.websocket_buffered_bytes},
{
"automatic_low_latency_scheduler",
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency
@@ -1212,35 +1239,21 @@ private:
std::uint64_t render_attempt_count_{};
std::uint64_t successful_render_count_{};
double last_render_ms_{};
double total_render_ms_{};
double maximum_render_ms_{};
Rolling_Statistics render_duration_statistics_{256};
std::deque<Performance_Clock::time_point> render_history_;
std::uint64_t pixel_frame_count_{};
double last_pixel_snapshot_ms_{};
double last_pixel_encode_ms_{};
double total_pixel_encode_ms_{};
double maximum_pixel_encode_ms_{};
double last_pixel_request_ms_{};
Rolling_Statistics pixel_encode_statistics_{256};
Rolling_Statistics pixel_request_statistics_{256};
std::size_t last_pixel_bytes_{};
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_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_{};
Gallery_Client_Performance client_performance_;
std::uint64_t consumer_pixel_interval_ns_{};
std::uint64_t consumer_presentation_interval_ns_{};
std::uint64_t consumer_manual_interval_ns_{};
std::string consumer_feedback_source_{"none"};
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_{};
std::string last_action_result_;
};
@@ -1291,19 +1304,37 @@ struct Gallery_Plot_Session::Impl {
const auto value = iterator->get<std::int64_t>();
return value > 0 ? static_cast<std::uint64_t>(value) : std::uint64_t{};
};
scene->set_client_metrics(finite_metric("transport_fps"),
finite_metric("presentation_fps"), buffered_bytes,
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"),
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"));
Gallery_Client_Performance performance;
performance.transport_fps = finite_metric("transport_fps");
performance.presentation_fps = finite_metric("presentation_fps");
performance.websocket_buffered_bytes = buffered_bytes;
performance.changed_pixel_frames = unsigned_metric("changed_pixel_frames");
performance.duplicate_pixel_frames = unsigned_metric("duplicate_pixel_frames");
performance.frame_request_timeout_count =
unsigned_metric("frame_request_timeout_count");
performance.frame_round_trip_ms = finite_metric("frame_round_trip_ms");
performance.frame_round_trip_average_ms =
finite_metric("frame_round_trip_average_ms");
performance.frame_round_trip_deviation_ms =
finite_metric("frame_round_trip_deviation_ms");
performance.frame_round_trip_p95_ms = finite_metric("frame_round_trip_p95_ms");
performance.frame_round_trip_p99_ms = finite_metric("frame_round_trip_p99_ms");
performance.display_interval_ms = finite_metric("display_interval_ms");
performance.display_interval_average_ms =
finite_metric("display_interval_average_ms");
performance.display_interval_latest_ms =
finite_metric("display_interval_latest_ms");
performance.display_interval_p95_ms = finite_metric("display_interval_p95_ms");
performance.display_interval_p99_ms = finite_metric("display_interval_p99_ms");
performance.display_interval_deviation_ms =
finite_metric("display_interval_deviation_ms");
performance.overwritten_pixel_frames =
unsigned_metric("overwritten_pixel_frames");
performance.last_pixel_receive_age_ms =
finite_metric("last_pixel_receive_age_ms");
performance.last_pixel_change_age_ms =
finite_metric("last_pixel_change_age_ms");
scene->set_client_metrics(performance);
return true;
}
[[nodiscard]] std::unique_lock<std::mutex> acquire_foreground_lock() {
@@ -1369,7 +1400,9 @@ struct Gallery_Plot_Session::Impl {
}
else if constexpr (std::is_same_v<T, Gallery_Request>) {
return value.kind != Gallery_Request_Kind::Catalog &&
value.kind != Gallery_Request_Kind::Observe;
value.kind != Gallery_Request_Kind::Observe &&
value.kind != Gallery_Request_Kind::Refresh &&
value.kind != Gallery_Request_Kind::Reset_Monitoring;
}
else {
return true;
@@ -1406,7 +1439,7 @@ struct Gallery_Plot_Session::Impl {
Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(),
scene->telemetry_json(),
"render_2D/Kernel 独立画布已创建",
scene->frame_mode())
scene->frame_mode(), false)
};
}
if (!scene)
@@ -1425,6 +1458,29 @@ struct Gallery_Plot_Session::Impl {
scene->case_id(), scene->frame_mode(), scene->telemetry_json())
};
}
if (request.kind == Gallery_Request_Kind::Refresh) {
if (update_client_metrics(request.message)) {
++scheduler_revision;
scheduler_condition.notify_all();
}
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(
scene->case_id(), scene->controls().dump(),
scene->telemetry_json(), "观察数据已手动刷新",
scene->frame_mode(), true)
};
}
if (request.kind == Gallery_Request_Kind::Reset_Monitoring) {
scene->reset_monitoring();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(
scene->case_id(), scene->controls().dump(),
scene->telemetry_json(), "监测滑动窗口已重置",
scene->frame_mode(), true)
};
}
if (request.kind == Gallery_Request_Kind::Patch) {
const auto patch = Gallery_Protocol::control_patch_request(request.message);
if (!patch)
@@ -1440,7 +1496,7 @@ struct Gallery_Plot_Session::Impl {
Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(),
scene->telemetry_json(),
"控件 API 已由后端应用",
scene->frame_mode())
scene->frame_mode(), false)
};
}
const auto action = Gallery_Protocol::action_request(request.message);
@@ -1466,7 +1522,7 @@ struct Gallery_Plot_Session::Impl {
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(id, scene->controls().dump(),
scene->telemetry_json(),
"本图已恢复后端默认值", mode)
"本图已恢复后端默认值", mode, false)
};
}
bool recognized{};
@@ -1482,7 +1538,7 @@ struct Gallery_Plot_Session::Impl {
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(),
scene->telemetry_json(), notice,
scene->frame_mode())
scene->frame_mode(), false)
};
}
std::optional<Web_Response> handle_frame_request() {
+78 -16
View File
@@ -290,7 +290,7 @@ Json dashboard_contract() {
{"presentation", "浏览器呈现"}, {"manual", "手动"}
}},
{"limit_state", {
{"frequency_limited", "Kernel 频率受限"}, {"paint_limited", "PaintEvent 受限"},
{"frequency_limited", "内核频率受限"}, {"paint_limited", "绘制事件受限"},
{"render_limited", "后台渲染受限"}, {"consumer_limited", "消费者反馈受限"},
{"unlimited", "无限制"}, {"not_applicable", "N/A"}
}}
@@ -303,7 +303,18 @@ Json dashboard_contract() {
dashboard_field("WS 往返 ms", "client_performance.frame_round_trip_ms", "fixed", 2),
dashboard_field("未呈现覆盖", "client_performance.overwritten_pixel_frames"),
dashboard_field("Core 渲染 ms", "performance.last_render_ms", "fixed", 2),
dashboard_field("Core 滑动平均 ms", "performance.average_render_ms", "fixed", 2),
dashboard_field("Core P95 ms", "performance.render_p95_ms", "fixed", 2),
dashboard_field("Core P99 ms", "performance.render_p99_ms", "fixed", 2),
dashboard_field("像素编码 ms", "performance.last_pixel_encode_ms", "fixed", 2),
dashboard_field("编码 P95 ms", "performance.pixel_encode_p95_ms", "fixed", 2),
dashboard_field("编码 P99 ms", "performance.pixel_encode_p99_ms", "fixed", 2),
dashboard_field("WS 平均 ms", "client_performance.frame_round_trip_average_ms", "fixed", 2),
dashboard_field("WS P95 ms", "client_performance.frame_round_trip_p95_ms", "fixed", 2),
dashboard_field("WS P99 ms", "client_performance.frame_round_trip_p99_ms", "fixed", 2),
dashboard_field("呈现平均 ms", "client_performance.display_interval_average_ms", "fixed", 2),
dashboard_field("呈现 P95 ms", "client_performance.display_interval_p95_ms", "fixed", 2),
dashboard_field("呈现 P99 ms", "client_performance.display_interval_p99_ms", "fixed", 2),
dashboard_field("响应负载 MB/s", "performance.pixel_payload_megabytes_per_second", "fixed", 1),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "pending_frame_count"),
@@ -331,8 +342,50 @@ Json dashboard_contract() {
})}
}},
{"menu_views", {
{"observer", {{"source", "kernel_observer"}}},
{"performance", Json::object()}
{"observer", {
{"kernel", {
{"title", "内核帧观察器"},
{"source", "kernel_observer"},
{"descriptor", adminive::to_descriptor_json<
Json, renderive::Frame_Observer_Snapshot>()}
}},
{"renderables_source", "renderable_observers"},
{"renderable_fields", Json::array({
"event", "event_time_ns", "cache_update_count", "publish_count"
})}
}},
{"performance", {
{"renderables_source", "renderable_observers"},
{"renderable_fields", Json::array({
"successful_render_count", "failed_render_count",
"last_render_sequence", "last_render_duration_ns",
"total_render_duration_ns", "maximum_render_duration_ns",
"render_duration_sample_count", "render_duration_average_ns",
"render_duration_deviation_ns", "render_duration_p50_ns",
"render_duration_p95_ns", "render_duration_p99_ns",
"task_execution_count", "failed_task_count",
"last_task_duration_ns", "total_task_duration_ns",
"maximum_task_duration_ns", "task_duration_sample_count",
"task_duration_average_ns", "task_duration_deviation_ns",
"task_duration_p50_ns", "task_duration_p95_ns",
"task_duration_p99_ns", "last_task_count",
"peak_parallelism"
})},
{"resources", Json::array({
{
{"title", "渲染性能"},
{"source", "performance"},
{"descriptor", adminive::to_descriptor_json<
Json, Gallery_Render_Performance>()}
},
{
{"title", "浏览器性能"},
{"source", "client_performance"},
{"descriptor", adminive::to_descriptor_json<
Json, Gallery_Client_Performance>()}
}
})}
}}
}},
{"limits", {
{"aria_label", "低延迟限速来源"}, {"title", "限速来源"},
@@ -341,15 +394,15 @@ Json dashboard_contract() {
{"active_label", "当前瓶颈"}, {"inactive_label", "未受限"},
{"disabled_label", "已关闭"},
{"fields", Json::array({
{{"label", "Kernel 用户频率"}, {"active_value", "frequency_limited"},
{{"label", "内核用户频率"}, {"active_value", "frequency_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "target_interval_ns")},
{"enabled_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "frequency_limit_enabled")}},
{{"label", "Kernel PaintEvent"}, {"active_value", "paint_limited"},
{{"label", "内核绘制事件"}, {"active_value", "paint_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "paint_duration_ns")}},
{{"label", "Kernel 后台渲染"}, {"active_value", "render_limited"},
{{"label", "内核后台渲染"}, {"active_value", "render_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "render_duration_ns")}},
{{"label", "消费者反馈"}, {"active_value", "consumer_limited"},
@@ -360,9 +413,9 @@ Json dashboard_contract() {
})}
}},
{"observer", {
{"aria_label", "Kernel 低延迟全量统计"},
{"aria_label", "内核低延迟全量统计"},
{"header", {
{"prefix", "KERNEL"}, {"suffix", "OBSERVER"}, {"event_label", "事件"},
{"prefix", "内核"}, {"suffix", "观察器"}, {"event_label", "事件"},
{"mode", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "mode")},
{"limit", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
@@ -379,9 +432,15 @@ Json dashboard_contract() {
{"fields", Json::array({
dashboard_field("RAF 最新周期", "client_performance.display_interval_latest_ms", "milliseconds", 3),
dashboard_field("RAF 中位周期", "client_performance.display_interval_ms", "milliseconds", 3),
dashboard_field("RAF 平均周期", "client_performance.display_interval_average_ms", "milliseconds", 3),
dashboard_field("RAF P95 周期", "client_performance.display_interval_p95_ms", "milliseconds", 3),
dashboard_field("RAF P95-P50 抖动", "client_performance.display_jitter_ms", "milliseconds", 3),
dashboard_field("RAF P99 周期", "client_performance.display_interval_p99_ms", "milliseconds", 3),
dashboard_field("RAF 周期标准差", "client_performance.display_interval_deviation_ms", "milliseconds", 3),
dashboard_field("WS 往返", "client_performance.frame_round_trip_ms", "milliseconds", 3),
dashboard_field("WS 往返平均", "client_performance.frame_round_trip_average_ms", "milliseconds", 3),
dashboard_field("WS 往返 P95", "client_performance.frame_round_trip_p95_ms", "milliseconds", 3),
dashboard_field("WS 往返 P99", "client_performance.frame_round_trip_p99_ms", "milliseconds", 3),
dashboard_field("WS 往返标准差", "client_performance.frame_round_trip_deviation_ms", "milliseconds", 3),
dashboard_field("像素响应 FPS", "client_performance.transport_fps", "fps", 2),
dashboard_field("浏览器呈现 FPS", "client_performance.presentation_fps", "fps", 2),
dashboard_field("WS 缓冲", "client_performance.websocket_buffered_bytes", "bytes"),
@@ -392,7 +451,7 @@ Json dashboard_contract() {
dashboard_field("最近像素龄", "client_performance.last_pixel_receive_age_ms", "milliseconds", 3),
dashboard_field("最近变化龄", "client_performance.last_pixel_change_age_ms", "milliseconds", 3)
})}},
{{"class_name", "latency-details"}, {"aria_label", "Kernel 各阶段等待耗时"},
{{"class_name", "latency-details"}, {"aria_label", "内核各阶段等待耗时"},
{"fields", observer_fields(observer_detail_field)}}
})}
}}
@@ -495,17 +554,20 @@ std::string Gallery_Protocol::case_json_from_controls(
std::string_view controls_json,
std::string_view telemetry_json,
std::string_view notice,
Gallery_Frame_Mode frame_mode) {
Gallery_Frame_Mode frame_mode,
bool manual_refresh) {
Json result = protocol_base();
result["type"] = "case_state";
result["type"] = manual_refresh ? "refresh_state" : "case_state";
result["case"] = case_contract(case_id);
result["frame_mode"] = frame_mode_contract(frame_mode);
try {
result["controls"] = {
{"resources", Json::parse(controls_json.begin(), controls_json.end())}
};
result["controls"] = Json::parse(controls_json.begin(), controls_json.end());
} catch (const std::exception&) {
result["controls"] = {{"resources", Json::array()}};
result["controls"] = {
{"resources", Json::array()},
{"observers", Json::array()},
{"task_graph", Json::object()}
};
}
result["actions"] = {
{"descriptor", adminive::to_descriptor_json<Json, gallery_detail::Action_Model>()},
+2 -1
View File
@@ -33,7 +33,8 @@ public:
std::string_view controls_json,
std::string_view telemetry_json,
std::string_view notice,
Gallery_Frame_Mode frame_mode);
Gallery_Frame_Mode frame_mode,
bool manual_refresh);
[[nodiscard]] static std::optional<Gallery_Open_Request> open_request(std::string_view message);
[[nodiscard]] static std::optional<Gallery_Action_Request> action_request(std::string_view message);
[[nodiscard]] static std::optional<Gallery_Control_Patch_Request> control_patch_request(
+9 -1
View File
@@ -27,7 +27,15 @@ struct Set_Smoothing {
bool enabled{};
};
struct Clear_Selection {};
enum class Gallery_Request_Kind : std::uint8_t { Catalog, Open, Patch, Action, Observe };
enum class Gallery_Request_Kind : std::uint8_t {
Catalog,
Open,
Patch,
Action,
Observe,
Refresh,
Reset_Monitoring
};
struct Gallery_Request {
Gallery_Request_Kind kind = Gallery_Request_Kind::Catalog;
std::string message;