升级
This commit is contained in:
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>()},
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user