观察者效应优化

This commit is contained in:
2026-08-27 20:58:04 +08:00
parent 41b81c5c87
commit b9f19de776
8 changed files with 85 additions and 290 deletions
+2 -6
View File
@@ -23,14 +23,10 @@ struct Taskflow_Frame_Access {
Render_Frame& frame, std::size_t worker, std::uint64_t native_id,
std::size_t queue_size, std::size_t queue_capacity,
Clock::time_point entered, Clock::time_point started,
Clock::time_point finished, std::uint64_t cpu_entered_ns,
std::uint64_t cpu_started_ns, std::uint64_t cpu_duration_ns,
std::uint64_t cpu_cycles, std::uint64_t cooperative_wait_ns,
bool cpu_time_coarse);
Clock::time_point finished, std::uint64_t cooperative_wait_ns);
static void finish_task_observer(
Render_Frame& frame, std::size_t worker, std::size_t task,
Clock::time_point completed, std::uint64_t cpu_finished_ns,
std::uint64_t cpu_completed_ns) noexcept;
Clock::time_point completed) noexcept;
[[nodiscard]] static Taskflow_Graph_Token begin_graph(
Render_Frame& frame, Task_Graph& graph, std::string_view stage);
static void finish_graph(Taskflow_Graph_Token token,
+15 -19
View File
@@ -51,7 +51,7 @@ void Render_Frame::begin(Frame_Identity identity,
for (auto& measurement : d->measurements)
measurement.store(0, std::memory_order_relaxed);
d->taskflow_trace_requested.store(false, std::memory_order_relaxed);
d->taskflow_workers.clear();
for (auto& worker : d->taskflow_workers) worker.tasks.clear();
d->taskflow_graphs.clear();
d->markers[static_cast<std::size_t>(Frame_Trace_Marker::created)].store(encode_present_value(0), std::memory_order_relaxed);
}
@@ -177,7 +177,10 @@ Taskflow_Frame_Trace Render_Frame::take_taskflow_trace() {
static_cast<Frame_Trace_Measurement>(index),
decode_present_value(encoded)});
}
result.graphs = std::move(d->taskflow_graphs);
result.graphs.reserve(d->taskflow_graphs.size());
for (auto& graph : d->taskflow_graphs)
result.graphs.push_back(std::move(graph));
d->taskflow_graphs.clear();
for (auto& worker : d->taskflow_workers) {
for (auto& task : worker.tasks)
result.tasks.push_back(std::move(task));
@@ -341,9 +344,11 @@ Taskflow_Frame_Trace Render_Frame::take_taskflow_trace() {
void detail::Taskflow_Frame_Access::begin_capture(Render_Frame& frame, std::size_t workers) {
auto& data = *frame.d;
data.taskflow_workers.clear();
data.taskflow_workers.resize(workers);
for (auto& worker : data.taskflow_workers) worker.tasks.reserve(64);
for (auto& worker : data.taskflow_workers) {
worker.tasks.clear();
if (worker.tasks.capacity() < 64) worker.tasks.reserve(64);
}
data.taskflow_graphs.clear();
}
@@ -356,10 +361,7 @@ std::size_t detail::Taskflow_Frame_Access::append_task(
Render_Frame& frame, std::size_t worker, std::uint64_t native_id,
std::size_t queue_size, std::size_t queue_capacity,
Clock::time_point entered, Clock::time_point started,
Clock::time_point finished, std::uint64_t cpu_entered_ns,
std::uint64_t cpu_started_ns, std::uint64_t cpu_duration_ns,
std::uint64_t cpu_cycles, std::uint64_t cooperative_wait_ns,
bool cpu_time_coarse) {
Clock::time_point finished, std::uint64_t cooperative_wait_ns) {
auto& data = *frame.d;
if (worker >= data.taskflow_workers.size())
return std::numeric_limits<std::size_t>::max();
@@ -376,22 +378,15 @@ std::size_t detail::Taskflow_Frame_Access::append_task(
trace.finished_ms = elapsed_ms(finished);
trace.completed_ms = trace.finished_ms;
trace.duration_ms = std::max(0.0, trace.finished_ms - trace.started_ms);
trace.cpu_duration_ms = static_cast<double>(cpu_duration_ns) / 1'000'000.0;
trace.cpu_cycles = cpu_cycles;
trace.cooperative_wait_ms = static_cast<double>(cooperative_wait_ns) / 1'000'000.0;
trace.cpu_time_coarse = cpu_time_coarse;
trace.observer_entry_ms = std::max(0.0, trace.started_ms - trace.entered_ms);
trace.observer_entry_cpu_ms = cpu_started_ns >= cpu_entered_ns
? static_cast<double>(cpu_started_ns - cpu_entered_ns) / 1'000'000.0
: 0.0;
data.taskflow_workers[worker].tasks.push_back(std::move(trace));
return data.taskflow_workers[worker].tasks.size() - 1;
}
void detail::Taskflow_Frame_Access::finish_task_observer(
Render_Frame& frame, std::size_t worker, std::size_t task,
Clock::time_point completed, std::uint64_t cpu_finished_ns,
std::uint64_t cpu_completed_ns) noexcept {
Clock::time_point completed) noexcept {
auto& data = *frame.d;
if (worker >= data.taskflow_workers.size() ||
task >= data.taskflow_workers[worker].tasks.size()) return;
@@ -400,9 +395,6 @@ void detail::Taskflow_Frame_Access::finish_task_observer(
completed - data.created_at).count();
trace.observer_exit_ms = std::max(
0.0, trace.completed_ms - trace.finished_ms);
trace.observer_exit_cpu_ms = cpu_completed_ns >= cpu_finished_ns
? static_cast<double>(cpu_completed_ns - cpu_finished_ns) / 1'000'000.0
: 0.0;
}
detail::Taskflow_Graph_Token detail::Taskflow_Frame_Access::begin_graph(
@@ -411,6 +403,10 @@ detail::Taskflow_Graph_Token detail::Taskflow_Frame_Access::begin_graph(
graph.stage = stage;
graph.taskflow_name = taskflow.name();
graph.nodes = detail::Task_Graph_Access::nodes(taskflow);
for (auto& worker : frame.d->taskflow_workers) {
const auto required = worker.tasks.size() + graph.nodes.size();
if (worker.tasks.capacity() < required) worker.tasks.reserve(required);
}
graph.submitted_ms = std::chrono::duration<double, std::milli>(
Clock::now() - frame.d->created_at).count();
frame.d->taskflow_graphs.push_back(std::move(graph));
-5
View File
@@ -109,14 +109,9 @@ struct Taskflow_Task_Trace {
double finished_ms{}; /* Observer on_exit 进入,即任务体已经结束的时间。 */
double completed_ms{}; /* Observer on_exit 与按帧追踪写入全部结束的时间。 */
double duration_ms{}; /* 仅任务体 started 到 finished 的持续时间。 */
double cpu_duration_ms{}; /* 任务体独占当前 worker 片段的线程 CPU 时间;cooperative corun 期间不计入。 */
std::uint64_t cpu_cycles{}; /* Windows QueryThreadCycleTime 的独占 CPU 周期;用于短任务 CPU 活动判定,不直接换算秒。 */
double cooperative_wait_ms{}; /* Task_Graph::corun/corun_until 主动让出 Worker 的墙钟时间。 */
bool cpu_time_coarse{}; /* 当前平台的线程 CPU 时间源是否为低分辨率计费时钟(Windows GetThreadTimes)。 */
double observer_entry_ms{}; /* on_entry 诊断本身的耗时。 */
double observer_exit_ms{}; /* on_exit 诊断与按帧追踪写入的耗时。 */
double observer_entry_cpu_ms{}; /* on_entry 诊断实际消耗的 worker CPU 时间。 */
double observer_exit_cpu_ms{}; /* on_exit 诊断实际消耗的 worker CPU 时间。 */
double ready_ms{}; /* 前驱完成或根 run 提交后的估算就绪时间。 */
double queue_wait_ms{}; /* ready 到 entered 的估算 Executor 排队时间。 */
};
+36 -178
View File
@@ -72,32 +72,6 @@ std::uint64_t thread_cpu_cycles(HANDLE thread) noexcept {
: 0;
}
#endif
std::uint64_t current_thread_cpu_ns() noexcept {
#if defined(_WIN32)
return thread_cpu_ns(GetCurrentThread());
#elif defined(CLOCK_THREAD_CPUTIME_ID)
timespec value{};
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &value) != 0) return 0;
return static_cast<std::uint64_t>(value.tv_sec) * 1'000'000'000ULL +
static_cast<std::uint64_t>(value.tv_nsec);
#else
return 0;
#endif
}
std::uint64_t current_thread_cpu_cycles() noexcept {
#if defined(_WIN32)
return thread_cpu_cycles(GetCurrentThread());
#else
return 0;
#endif
}
constexpr bool thread_cpu_time_is_coarse() noexcept {
#if defined(_WIN32)
return true;
#else
return false;
#endif
}
struct Task_Observer : public tf::ObserverInterface {
private:
using Clock = std::chrono::steady_clock;
@@ -120,9 +94,9 @@ private:
std::atomic_uint64_t active_task_hash{};
std::atomic_uint64_t active_task_started_ns{};
std::atomic_uint64_t active_segment_started_ns{};
std::atomic_uint64_t active_segment_cpu_started_ns{};
#if defined(_WIN32)
std::atomic_uintptr_t native_thread_handle{}; /* Watchdog 只读的真实 Worker 线程句柄。 */
std::atomic_uint64_t cpu_time_origin_ns{}; /* 首次进入 Worker 时的累计线程 CPU,读取侧据此计算观测期增量。 */
std::atomic_uint64_t active_cpu_cycles{}; /* 连续片段最近一次采样的 Worker CPU 周期。 */
std::atomic_uint64_t active_cpu_progress_ns{}; /* CPU 周期最后前进的墙钟时刻。 */
#endif
@@ -130,32 +104,19 @@ private:
std::atomic_bool active_task_reported{};
std::atomic_uint64_t task_time_ns{};
std::atomic_uint64_t busy_time_ns{};
std::atomic_uint64_t cpu_time_ns{};
std::atomic_uint64_t min_task_time_ns{std::numeric_limits<std::uint64_t>::max()};
std::atomic_uint64_t max_task_time_ns{};
std::atomic_size_t max_predecessors{};
std::atomic_size_t max_successors{};
std::atomic_size_t max_strong_dependencies{};
std::atomic_size_t max_weak_dependencies{};
std::atomic_uint64_t first_task_time_ns{};
std::atomic_uint64_t last_task_time_ns{};
std::atomic_uint64_t longest_task_time_ns{};
std::atomic_size_t longest_task_hash{};
std::atomic<tf::TaskType> longest_task_type{tf::TaskType::UNDEFINED};
std::atomic<std::shared_ptr<const std::string>> longest_task_name{};
};
struct Start_Record {
Clock::time_point entered{}; /* Observer on_entry 进入时间。 */
Clock::time_point started{}; /* on_entry 完成、任务体即将执行的时间。 */
Clock::time_point segment_started{}; /* 当前连续独占 Worker 片段的起点。 */
std::uint64_t maximum_segment_ns{}; /* 已结束连续独占片段的最大墙钟。 */
std::uint64_t cpu_entered_ns{}; /* on_entry 进入时的 worker CPU 时间。 */
std::uint64_t cpu_started_ns{}; /* 任务体开始前的 worker CPU 时间,仅用于 Observer entry 统计。 */
std::uint64_t cpu_segment_started_ns{}; /* 当前任务独占 Worker 片段的线程 CPU 起点。 */
bool cpu_segment_active{}; /* CPU 累计值本身允许为 0,不能拿 0 当未启动哨兵。 */
std::uint64_t cpu_duration_ns{}; /* 已累计的任务独占 Worker CPU;嵌套 corun/子任务不计入。 */
std::uint64_t cpu_cycle_segment_started{}; /* Windows 当前独占片段的 QueryThreadCycleTime 起点。 */
std::uint64_t cpu_cycles{}; /* Windows 已累计的任务独占 CPU 周期。 */
Clock::time_point cooperative_wait_started{}; /* 主动 corun 让出 Worker 的墙钟起点。 */
std::uint64_t cooperative_wait_ns{}; /* 已累计 cooperative wait 墙钟。 */
Render_Frame* frame{}; /* 进入任务时唯一活动的按帧捕获。 */
@@ -167,7 +128,6 @@ private:
};
std::vector<std::vector<Start_Record>> starts;
std::vector<Clock::time_point> worker_busy_starts;
std::vector<std::uint64_t> worker_cpu_starts;
std::unique_ptr<Worker_Statistics[]> worker_statistics;
std::size_t worker_statistics_count{};
std::uint64_t worker_occupation_limit_ns{}; /* 单节点连续非 CPU 等待 Worker 的上限。 */
@@ -278,41 +238,6 @@ private:
}
}
}
static void close_cpu_segment(
Start_Record& active, std::uint64_t cpu_now_ns,
std::uint64_t cycle_now) noexcept {
if (!active.frame || !active.cpu_segment_active) return;
if (cpu_now_ns >= active.cpu_segment_started_ns)
active.cpu_duration_ns +=
cpu_now_ns - active.cpu_segment_started_ns;
#if defined(_WIN32)
if (cycle_now >= active.cpu_cycle_segment_started)
active.cpu_cycles += cycle_now - active.cpu_cycle_segment_started;
#else
static_cast<void>(cycle_now);
#endif
active.cpu_segment_started_ns = 0;
active.cpu_cycle_segment_started = 0;
active.cpu_segment_active = false;
}
static void close_cpu_segment(Start_Record& active) noexcept {
if (!active.frame || !active.cpu_segment_active) return;
close_cpu_segment(active, current_thread_cpu_ns(),
current_thread_cpu_cycles());
}
static void open_cpu_segment(
Start_Record& active, std::uint64_t cpu_now_ns,
std::uint64_t cycle_now) noexcept {
if (!active.frame) return;
active.cpu_segment_started_ns = cpu_now_ns;
active.cpu_cycle_segment_started = cycle_now;
active.cpu_segment_active = true;
}
static void open_cpu_segment(Start_Record& active) noexcept {
if (!active.frame) return;
open_cpu_segment(active, current_thread_cpu_ns(),
current_thread_cpu_cycles());
}
void pause_worker(std::size_t worker) noexcept {
if (worker >= starts.size() || starts[worker].empty()) return;
const auto now = Clock::now();
@@ -324,15 +249,12 @@ private:
active.maximum_segment_ns = std::max(
active.maximum_segment_ns, elapsed);
}
close_cpu_segment(active);
active.segment_started = {};
if (active.cooperative_wait_started == Clock::time_point{})
active.cooperative_wait_started = now;
active.cooperatively_suspended = true;
worker_statistics[worker].active_segment_started_ns.store(
0, std::memory_order_release);
worker_statistics[worker].active_segment_cpu_started_ns.store(
0, std::memory_order_release);
#if defined(_WIN32)
worker_statistics[worker].active_cpu_cycles.store(
0, std::memory_order_release);
@@ -352,17 +274,11 @@ private:
}
active.cooperatively_suspended = false;
active.segment_started = now;
open_cpu_segment(active);
worker_statistics[worker].active_segment_started_ns.store(
clock_ns(now), std::memory_order_release);
worker_statistics[worker].active_segment_cpu_started_ns.store(
current_thread_cpu_ns(), std::memory_order_release);
#if defined(_WIN32)
const auto handle = worker_statistics[worker].native_thread_handle.load(
std::memory_order_acquire);
worker_statistics[worker].active_cpu_cycles.store(
thread_cpu_cycles(reinterpret_cast<HANDLE>(handle)),
std::memory_order_release);
worker_statistics[worker].active_cpu_cycles.store(0,
std::memory_order_release);
worker_statistics[worker].active_cpu_progress_ns.store(
clock_ns(now), std::memory_order_release);
#endif
@@ -392,10 +308,9 @@ public:
void set_up(std::size_t workers) override {
starts.resize(workers);
worker_busy_starts.resize(workers);
worker_cpu_starts.resize(workers);
worker_statistics = std::make_unique<Worker_Statistics[]>(workers);
worker_statistics_count = workers;
for (auto& worker : starts) worker.reserve(8);
for (auto& worker : starts) worker.reserve(32);
watchdog_thread = std::thread([this] {
run_watchdog();
});
@@ -412,12 +327,11 @@ public:
GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &duplicated, 0, FALSE,
DUPLICATE_SAME_ACCESS)) {
std::uintptr_t expected{};
if (!worker_state.native_thread_handle.compare_exchange_strong(
expected, reinterpret_cast<std::uintptr_t>(duplicated),
std::memory_order_release,
std::memory_order_relaxed))
CloseHandle(duplicated);
worker_state.cpu_time_origin_ns.store(
thread_cpu_ns(duplicated), std::memory_order_relaxed);
worker_state.native_thread_handle.store(
reinterpret_cast<std::uintptr_t>(duplicated),
std::memory_order_release);
}
}
#endif
@@ -429,19 +343,16 @@ public:
now - parent.segment_started).count());
parent.maximum_segment_ns = std::max(
parent.maximum_segment_ns, elapsed);
close_cpu_segment(parent);
parent.segment_started = {};
}
}
if (worker_starts.empty()) {
worker_busy_starts[worker.id()] = now;
worker_cpu_starts[worker.id()] = current_thread_cpu_ns();
}
if (worker_starts.empty()) worker_busy_starts[worker.id()] = now;
const auto queue_size = worker.queue_size();
const auto queue_capacity = worker.queue_capacity();
Start_Record record{};
record.entered = now;
record.cpu_entered_ns = current_thread_cpu_ns();
record.queue_size = worker.queue_size();
record.queue_capacity = worker.queue_capacity();
record.queue_size = queue_size;
record.queue_capacity = queue_capacity;
record.native_id = static_cast<std::uint64_t>(task.hash_value());
record.type = task.type();
worker_starts.push_back(std::move(record));
@@ -450,55 +361,30 @@ public:
const auto active_depth = worker_state.active_depth.fetch_add(
1, std::memory_order_relaxed) + 1;
update_max(worker_state.peak_active_depth, active_depth);
worker_state.current_queue_size.store(worker.queue_size(), std::memory_order_relaxed);
worker_state.current_queue_capacity.store(worker.queue_capacity(), std::memory_order_relaxed);
worker_state.current_queue_size.store(queue_size, std::memory_order_relaxed);
worker_state.current_queue_capacity.store(queue_capacity, std::memory_order_relaxed);
worker_state.active_task_hash.store(task.hash_value(), std::memory_order_relaxed);
worker_state.active_task_started_ns.store(clock_ns(now), std::memory_order_relaxed);
worker_state.active_segment_started_ns.store(clock_ns(now),
std::memory_order_release);
worker_state.active_segment_cpu_started_ns.store(
current_thread_cpu_ns(), std::memory_order_release);
#if defined(_WIN32)
const auto native_handle = worker_state.native_thread_handle.load(
std::memory_order_acquire);
worker_state.active_cpu_cycles.store(
thread_cpu_cycles(reinterpret_cast<HANDLE>(native_handle)),
std::memory_order_release);
worker_state.active_cpu_cycles.store(0, std::memory_order_release);
worker_state.active_cpu_progress_ns.store(
clock_ns(now), std::memory_order_release);
#endif
worker_state.active_task_type.store(task.type(), std::memory_order_relaxed);
worker_state.active_task_reported.store(false, std::memory_order_relaxed);
update_max(worker_state.peak_queue_size, worker.queue_size());
update_max(worker_state.max_queue_capacity, worker.queue_capacity());
update_max(worker_state.max_predecessors, task.num_predecessors());
update_max(worker_state.max_successors, task.num_successors());
update_max(worker_state.max_strong_dependencies,
task.num_strong_dependencies());
update_max(worker_state.max_weak_dependencies,
task.num_weak_dependencies());
update_max(worker_state.peak_queue_size, queue_size);
update_max(worker_state.max_queue_capacity, queue_capacity);
if (!task.name().empty()) worker_state.named_task_count.fetch_add(1, std::memory_order_relaxed);
update_first(worker_state.first_task_time_ns, clock_ns(now));
const auto task_cpu_started_ns = current_thread_cpu_ns();
const auto task_cpu_cycle_started = worker_starts.back().frame
? current_thread_cpu_cycles() : 0;
worker_starts.back().cpu_started_ns = task_cpu_started_ns;
worker_starts.back().cpu_segment_started_ns = task_cpu_started_ns;
worker_starts.back().cpu_cycle_segment_started =
task_cpu_cycle_started;
worker_starts.back().cpu_segment_active =
worker_starts.back().frame != nullptr;
worker_starts.back().started = Clock::now();
worker_starts.back().segment_started = worker_starts.back().started;
}
void on_exit(tf::WorkerView worker, tf::TaskView task) override {
const auto finished = Clock::now();
const auto cpu_finished_ns = current_thread_cpu_ns();
auto& worker_starts = starts[worker.id()];
auto& active = worker_starts.back();
const auto cpu_finished_cycles = active.frame
? current_thread_cpu_cycles() : 0;
close_cpu_segment(active, cpu_finished_ns, cpu_finished_cycles);
if (active.cooperative_wait_started != Clock::time_point{}) {
active.cooperative_wait_ns += static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
@@ -538,9 +424,6 @@ public:
longest, elapsed, std::memory_order_relaxed)) {
worker_state.longest_task_hash.store(task.hash_value(), std::memory_order_relaxed);
worker_state.longest_task_type.store(task.type(), std::memory_order_relaxed);
worker_state.longest_task_name.store(
std::make_shared<const std::string>(task.name()),
std::memory_order_release);
}
worker_state.active_depth.fetch_sub(1, std::memory_order_relaxed);
std::optional<std::size_t> trace_task;
@@ -550,39 +433,27 @@ public:
*start.frame, worker.id(),
static_cast<std::uint64_t>(task.hash_value()),
start.queue_size, start.queue_capacity, start.entered,
start.started, finished, start.cpu_entered_ns,
start.cpu_started_ns, start.cpu_duration_ns,
start.cpu_cycles, start.cooperative_wait_ns,
thread_cpu_time_is_coarse());
start.started, finished, start.cooperative_wait_ns);
}
catch (...) {
/* Observer 不能让按需诊断分配失败改变渲染任务的完成语义。 */
}
}
const auto cpu_completed_ns = current_thread_cpu_ns();
const auto completed = Clock::now();
if (start.frame) {
if (trace_task)
detail::Taskflow_Frame_Access::finish_task_observer(
*start.frame, worker.id(), *trace_task, completed,
cpu_finished_ns, cpu_completed_ns);
*start.frame, worker.id(), *trace_task, completed);
}
if (worker_starts.empty()) {
auto busy = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
completed - worker_busy_starts[worker.id()]).count());
worker_state.busy_time_ns.fetch_add(busy, std::memory_order_relaxed);
const auto cpu_started = worker_cpu_starts[worker.id()];
const auto cpu = cpu_completed_ns >= cpu_started
? cpu_completed_ns - cpu_started
: 0;
worker_state.cpu_time_ns.fetch_add(cpu, std::memory_order_relaxed);
worker_state.active_task_hash.store(0, std::memory_order_relaxed);
worker_state.active_task_started_ns.store(0, std::memory_order_relaxed);
worker_state.active_segment_started_ns.store(0,
std::memory_order_release);
worker_state.active_segment_cpu_started_ns.store(
0, std::memory_order_release);
#if defined(_WIN32)
worker_state.active_cpu_cycles.store(0, std::memory_order_release);
worker_state.active_cpu_progress_ns.store(0,
@@ -601,8 +472,6 @@ public:
if (parent.cooperatively_suspended) {
worker_state.active_segment_started_ns.store(
0, std::memory_order_release);
worker_state.active_segment_cpu_started_ns.store(
0, std::memory_order_release);
#if defined(_WIN32)
worker_state.active_cpu_cycles.store(0,
std::memory_order_release);
@@ -611,20 +480,13 @@ public:
#endif
}
else {
const auto parent_cpu_started_ns = current_thread_cpu_ns();
const auto parent_cpu_cycle_started = parent.frame
? current_thread_cpu_cycles() : 0;
const auto parent_resumed = Clock::now();
parent.segment_started = parent_resumed;
open_cpu_segment(parent, parent_cpu_started_ns,
parent_cpu_cycle_started);
worker_state.active_segment_started_ns.store(
clock_ns(parent_resumed), std::memory_order_release);
worker_state.active_segment_cpu_started_ns.store(
parent_cpu_started_ns, std::memory_order_release);
#if defined(_WIN32)
worker_state.active_cpu_cycles.store(
parent_cpu_cycle_started, std::memory_order_release);
worker_state.active_cpu_cycles.store(0,
std::memory_order_release);
worker_state.active_cpu_progress_ns.store(
clock_ns(parent_resumed), std::memory_order_release);
#endif
@@ -657,10 +519,6 @@ public:
state.named_task_count = 0;
state.peak_observed_worker_queue_size = 0;
state.max_observed_worker_queue_capacity = 0;
state.max_predecessors = 0;
state.max_successors = 0;
state.max_strong_dependencies = 0;
state.max_weak_dependencies = 0;
state.total_task_time_ns = 0;
state.worker_busy_time_ns = 0;
state.worker_cpu_time_ns = 0;
@@ -696,7 +554,17 @@ public:
: std::string{};
target.task_time_ns = source.task_time_ns.load(std::memory_order_relaxed);
target.busy_time_ns = source.busy_time_ns.load(std::memory_order_relaxed);
target.cpu_time_ns = source.cpu_time_ns.load(std::memory_order_relaxed);
#if defined(_WIN32)
const auto native_handle = source.native_thread_handle.load(
std::memory_order_acquire);
const auto cpu_origin = source.cpu_time_origin_ns.load(
std::memory_order_acquire);
const auto cpu_now = thread_cpu_ns(
reinterpret_cast<HANDLE>(native_handle));
target.cpu_time_ns = cpu_now >= cpu_origin ? cpu_now - cpu_origin : 0;
#else
target.cpu_time_ns = 0;
#endif
target.non_cpu_time_ns = target.busy_time_ns > target.cpu_time_ns
? target.busy_time_ns - target.cpu_time_ns
: 0;
@@ -724,16 +592,6 @@ public:
state.max_observed_worker_queue_capacity = std::max(
state.max_observed_worker_queue_capacity,
target.max_observed_queue_capacity);
state.max_predecessors = std::max(state.max_predecessors,
source.max_predecessors.load(std::memory_order_relaxed));
state.max_successors = std::max(state.max_successors,
source.max_successors.load(std::memory_order_relaxed));
state.max_strong_dependencies = std::max(
state.max_strong_dependencies,
source.max_strong_dependencies.load(std::memory_order_relaxed));
state.max_weak_dependencies = std::max(
state.max_weak_dependencies,
source.max_weak_dependencies.load(std::memory_order_relaxed));
state.total_task_time_ns += target.task_time_ns;
state.worker_busy_time_ns += target.busy_time_ns;
state.worker_cpu_time_ns += target.cpu_time_ns;
@@ -765,8 +623,8 @@ public:
state.longest_task_time_ns = longest;
state.longest_task_hash = source.longest_task_hash.load(
std::memory_order_relaxed);
const auto name = source.longest_task_name.load(
std::memory_order_acquire);
const auto name = detail::taskflow_node_name(
state.longest_task_hash);
state.longest_task_name = name ? *name : std::string{};
state.longest_task_type = std::string(tf::to_string(
source.longest_task_type.load(std::memory_order_relaxed)));
-4
View File
@@ -80,10 +80,6 @@ struct Task_Runtime_State : State_Type<Task_Runtime_State_Tag> {
std::size_t named_task_count{};
std::size_t peak_observed_worker_queue_size{};
std::size_t max_observed_worker_queue_capacity{};
std::size_t max_predecessors{};
std::size_t max_successors{};
std::size_t max_strong_dependencies{};
std::size_t max_weak_dependencies{};
std::size_t longest_task_hash{};
std::string longest_task_name;
std::string longest_task_type;
-5
View File
@@ -341,14 +341,9 @@ nlohmann::json taskflow_trace_json(
{"started_ms", task.started_ms}, {"finished_ms", task.finished_ms},
{"completed_ms", task.completed_ms},
{"duration_ms", task.duration_ms},
{"cpu_duration_ms", task.cpu_duration_ms},
{"cpu_cycles", task.cpu_cycles},
{"cooperative_wait_ms", task.cooperative_wait_ms},
{"cpu_time_coarse", task.cpu_time_coarse},
{"observer_entry_ms", task.observer_entry_ms},
{"observer_exit_ms", task.observer_exit_ms},
{"observer_entry_cpu_ms", task.observer_entry_cpu_ms},
{"observer_exit_cpu_ms", task.observer_exit_cpu_ms},
{"queue_wait_ms", task.queue_wait_ms}});
}
return {
-4
View File
@@ -84,10 +84,6 @@ nlohmann::json taskflow_runtime_json() {
{"named_tasks", state.named_task_count},
{"peak_worker_queue_size", state.peak_observed_worker_queue_size},
{"max_worker_queue_capacity", state.max_observed_worker_queue_capacity},
{"max_predecessors", state.max_predecessors},
{"max_successors", state.max_successors},
{"max_strong_dependencies", state.max_strong_dependencies},
{"max_weak_dependencies", state.max_weak_dependencies},
{"longest_task", {{"native_id", std::to_string(state.longest_task_hash)},
{"name", state.longest_task_name},
{"type", state.longest_task_type},
+32 -69
View File
@@ -81,9 +81,8 @@ type Taskflow_Graph_Trace = {stage: string; name: string; submitted_ms: number;
completed: boolean; nodes: Taskflow_Node_Trace[]};
type Taskflow_Execution_Trace = {native_id: string; node_id: string; worker_id: number; worker_queue_size: number;
worker_queue_capacity: number; ready_ms: number; entered_ms: number; started_ms: number; finished_ms: number;
completed_ms: number; duration_ms: number; cpu_duration_ms: number; cpu_cycles: number; cooperative_wait_ms: number;
cpu_time_coarse: boolean; observer_entry_ms: number; observer_exit_ms: number;
observer_entry_cpu_ms: number; observer_exit_cpu_ms: number; queue_wait_ms: number};
completed_ms: number; duration_ms: number; cooperative_wait_ms: number;
observer_entry_ms: number; observer_exit_ms: number; queue_wait_ms: number};
type Frame_Policy_State = {
generation: number;
configuration: {mode: Frame_Pacing_Mode; render_enabled: boolean; video_enabled: boolean; fixed_rate_fps: number};
@@ -113,8 +112,7 @@ type Taskflow_Type_State = {name: string; count: number; total_time_ns: number;
type Taskflow_Runtime_State = {protocol: "aethera.taskflow.runtime"; version: 1; worker_count: number; active_topologies: number;
active_taskflows: number; peak_active_taskflows: number; completed_taskflows: number; failed_taskflows: number;
active_tasks: number; peak_active_tasks: number; active_workers: number; peak_active_workers: number; observed_tasks: number;
named_tasks: number; peak_worker_queue_size: number; max_worker_queue_capacity: number; max_predecessors: number;
max_successors: number; max_strong_dependencies: number; max_weak_dependencies: number;
named_tasks: number; peak_worker_queue_size: number; max_worker_queue_capacity: number;
longest_task: {native_id: string; name: string; type: string; time_ns: number}; total_task_time_ns: number;
worker_busy_time_ns: number; worker_cpu_time_ns: number; observed_wall_time_ns: number;
worker_utilization: number; worker_cpu_utilization: number;
@@ -1203,20 +1201,6 @@ function nanoseconds(value: number) {
return milliseconds(value / 1_000_000);
}
function cpu_cycles(value: number) {
if (!Number.isFinite(value) || value <= 0) return "--";
if (value >= 1_000_000_000) return `${(value / 1_000_000_000).toFixed(2)} Gcy`;
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)} Mcy`;
if (value >= 1_000) return `${(value / 1_000).toFixed(1)} Kcy`;
return `${Math.round(value)} cy`;
}
function task_cpu_label(sample: Taskflow_Execution_Trace) {
if (sample.cpu_time_coarse && sample.cpu_duration_ms === 0 && sample.cpu_cycles > 0)
return "低于系统 CPU 计时分辨率";
return milliseconds(sample.cpu_duration_ms);
}
type Taskflow_Node_Phase = "prepare" | "paint" | "control" | "completion" | "other";
function taskflow_node_phase(name: string): Taskflow_Node_Phase {
@@ -1340,7 +1324,7 @@ function Taskflow_Node_Label({node, sample, summary, state, level}: {
node, execution: sample ?? null,
aggregate: summary ? {
present: summary.present, executed: summary.executed, frames: summary.frames,
duration: summary.duration, queue: summary.queue, cpu: summary.cpu, observer: summary.observer,
duration: summary.duration, queue: summary.queue, observer: summary.observer,
workers: summary.workers, stability: summary.stability
} : null,
state: state ?? null
@@ -1375,8 +1359,7 @@ function Taskflow_Node_Label({node, sample, summary, state, level}: {
<span>{node.type} · {summary.executed}/{summary.frames} · W {summary.workers.join(", ") || "--"}</span>
<span> {milliseconds(summary.duration.average)} ± {milliseconds(summary.duration.variability)} · P95 {milliseconds(summary.duration.p95)}</span>
<span> {milliseconds(summary.queue.average)} ± {milliseconds(summary.queue.variability)} · P99 {milliseconds(summary.queue.p99)}</span>
<span>线 CPU{summary.cpu_time_coarse ? "(低分辨率)" : ""} {milliseconds(summary.cpu.average)} · CPU {cpu_cycles(summary.cycles.average)}</span>
<span> {milliseconds(summary.cooperative_wait.average)} · Observer {milliseconds(summary.observer.average)}</span>
<span> {milliseconds(summary.cooperative_wait.average)} · Observer {milliseconds(summary.observer.average)}</span>
</> : node.type === "diagnostic" ? <>
<span></span>
<span> {sample ? `+${sample.started_ms.toFixed(3)} ms` : "--"} · {sample ? `+${sample.finished_ms.toFixed(3)} ms` : "--"}</span>
@@ -1385,8 +1368,7 @@ function Taskflow_Node_Label({node, sample, summary, state, level}: {
<span>{node.type} · W{sample?.worker_id ?? "--"}</span>
<span> {sample ? `+${sample.ready_ms.toFixed(3)} ms` : "--"} · {sample ? `+${sample.started_ms.toFixed(3)} ms` : "--"}</span>
<span> {sample ? milliseconds(duration) : "未执行"} · {sample ? milliseconds(wait) : "--"}</span>
<span>线 CPU {sample ? task_cpu_label(sample) : "--"}{sample?.cpu_time_coarse ? "(低分辨率)" : ""} · CPU {sample ? cpu_cycles(sample.cpu_cycles) : "--"}</span>
<span> {sample ? milliseconds(sample.cooperative_wait_ms) : "--"} · {sample ? `${sample.cpu_time_coarse ? "≈ " : ""}${milliseconds(Math.max(0, duration - sample.cooperative_wait_ms - sample.cpu_duration_ms))}` : "--"}</span>
<span> {sample ? milliseconds(sample.cooperative_wait_ms) : "--"} · {sample ? milliseconds(Math.max(0, duration - sample.cooperative_wait_ms)) : "--"}</span>
<span>Observer {sample ? milliseconds(sample.observer_entry_ms + sample.observer_exit_ms) : "--"}</span></>}
{Object.entries(node.attributes ?? {}).map(([key, value]) => <span key={key}>{key}{value}</span>)}
</div>;
@@ -1434,23 +1416,19 @@ function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskfl
const execution_time = leaf_rows.reduce((sum, row) => sum + row.duration_ms, 0);
const module_envelope_time = module_rows.reduce((sum, row) => sum + row.duration_ms, 0);
const diagnostic_time = diagnostic_rows.reduce((sum, row) => sum + row.duration_ms, 0);
const cpu_execution_time = leaf_rows.reduce((sum, row) => sum + row.cpu_duration_ms, 0);
const cooperative_wait_time = leaf_rows.reduce((sum, row) => sum + row.cooperative_wait_ms, 0);
const cpu_cycles_total = leaf_rows.reduce((sum, row) => sum + row.cpu_cycles, 0);
const cpu_time_coarse = leaf_rows.some(row => row.cpu_time_coarse);
const unattributed_wall_time = Math.max(0, execution_time - cooperative_wait_time - cpu_execution_time);
const exclusive_body_time = Math.max(0, execution_time - cooperative_wait_time);
const observer_entry_time = task_rows.reduce((sum, row) => sum + row.observer_entry_ms, 0);
const observer_exit_time = task_rows.reduce((sum, row) => sum + row.observer_exit_ms, 0);
const observer_cpu_time = task_rows.reduce((sum, row) => sum + row.observer_entry_cpu_ms + row.observer_exit_cpu_ms, 0);
const queue_time = task_rows.reduce((sum, row) => sum + row.queue_wait_ms, 0);
const first_entered = task_rows.length ? Math.min(...task_rows.map(row => row.entered_ms)) : graph.submitted_ms;
const last_completed = task_rows.length ? Math.max(...task_rows.map(row => row.completed_ms)) : graph.finished_ms;
const longest = task_rows.reduce<Taskflow_Execution_Trace | null>(
(result, row) => !result || row.duration_ms > result.duration_ms ? row : result, null);
const longest_unattributed = task_rows.reduce<Taskflow_Execution_Trace | null>((result, row) => {
const value = Math.max(0, row.duration_ms - row.cooperative_wait_ms - row.cpu_duration_ms);
const longest_exclusive = task_rows.reduce<Taskflow_Execution_Trace | null>((result, row) => {
const value = Math.max(0, row.duration_ms - row.cooperative_wait_ms);
const previous = result
? Math.max(0, result.duration_ms - result.cooperative_wait_ms - result.cpu_duration_ms)
? Math.max(0, result.duration_ms - result.cooperative_wait_ms)
: -1;
return value > previous ? row : result;
}, null);
@@ -1510,8 +1488,8 @@ function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskfl
for (const level of levels.values()) width_by_level.set(level, (width_by_level.get(level) ?? 0) + 1);
return {
rows, task_rows, leaf_rows, module_rows, diagnostic_rows, levels, wall_time, execution_time,
module_envelope_time, diagnostic_time, diagnostic_wall_time, cpu_execution_time, cooperative_wait_time,
cpu_cycles_total, cpu_time_coarse, unattributed_wall_time, observer_entry_time, observer_cpu_time,
module_envelope_time, diagnostic_time, diagnostic_wall_time, cooperative_wait_time,
exclusive_body_time, observer_entry_time,
observer_exit_time, queue_time, body_wall_time, observer_wall_time, idle_wall_time, initial_wait,
completion_tail, unattributed_completion_tail,
internal_idle_time: Math.max(0, idle_wall_time - initial_wait - unattributed_completion_tail),
@@ -1521,12 +1499,11 @@ function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskfl
diagnostic_node_count: graph.nodes.filter(node => node.type.toLowerCase() === "diagnostic").length,
layer_count: width_by_level.size,
parallel_layer_count: [...width_by_level.values()].filter(width => width > 1).length,
longest, longest_unattributed,
longest, longest_exclusive,
longest_duration: longest?.duration_ms ?? 0,
longest_unattributed_duration: longest_unattributed
? Math.max(0, longest_unattributed.duration_ms -
longest_unattributed.cooperative_wait_ms -
longest_unattributed.cpu_duration_ms)
longest_exclusive_duration: longest_exclusive
? Math.max(0, longest_exclusive.duration_ms -
longest_exclusive.cooperative_wait_ms)
: 0
};
}
@@ -1539,15 +1516,12 @@ const taskflow_graph_metric_definitions = [
{key: "module_envelope_time", label: "Module 包络总和", unit: "milliseconds", description: "每帧所有 Taskflow Module Observer 包络之和。"},
{key: "diagnostic_time", label: "收尾诊断总和", unit: "milliseconds", description: "Executor 返回后的运行计数、状态发布、Observer graph 退役与完成记录墙钟之和。"},
{key: "diagnostic_wall_time", label: "收尾诊断并集", unit: "milliseconds", description: "Topology 尾部被明确归因到诊断收尾节点的时间区间并集。"},
{key: "cpu_execution_time", label: "叶子任务线程 CPU", unit: "milliseconds", description: "每帧叶子任务独占 Worker 的线程 CPU 时间之和。"},
{key: "cpu_cycles_total", label: "叶子任务 CPU 活动", unit: "cycles", description: "每帧叶子任务 QueryThreadCycleTime 活动量之和。"},
{key: "cooperative_wait_time", label: "累计协作等待", unit: "milliseconds", description: "每帧 Task_Graph 协作等待墙钟之和。"},
{key: "unattributed_wall_time", label: "未归因墙时", unit: "milliseconds", description: "任务体墙钟扣除协作等待和线程 CPU 后的守恒余量。"},
{key: "exclusive_body_time", label: "独占任务体墙钟", unit: "milliseconds", description: "任务体墙钟扣除显式协作让出后的总和。"},
{key: "body_wall_time", label: "任务体墙钟并集", unit: "milliseconds", description: "每帧至少有一个任务体执行的时间区间并集。"},
{key: "observer_wall_time", label: "Observer 独占墙钟", unit: "milliseconds", description: "只有 Observer 活动、没有任务体执行的时间区间。"},
{key: "observer_entry_time", label: "Observer entry 墙钟", unit: "milliseconds", description: "每帧所有 Observer entry 墙钟之和。"},
{key: "observer_exit_time", label: "Observer exit 墙钟", unit: "milliseconds", description: "每帧所有 Observer exit 墙钟之和。"},
{key: "observer_cpu_time", label: "Observer 实际 CPU", unit: "milliseconds", description: "每帧 Observer entry/exit 真正占用的 Worker CPU 时间。"},
{key: "idle_wall_time", label: "无任务墙钟", unit: "milliseconds", description: "Topology 中没有捕获任务体或 Observer 活动的区间。"},
{key: "queue_time", label: "累计节点排队", unit: "milliseconds", description: "各节点从前驱完成到进入 Worker 的估算等待之和。"},
{key: "initial_wait", label: "首次准入等待", unit: "milliseconds", description: "Topology 提交后首节点进入 Worker 前的等待。"},
@@ -1559,20 +1533,20 @@ const taskflow_graph_metric_definitions = [
{key: "layer_count", label: "依赖层", unit: "count", description: "每帧 DAG 的依赖层数量。"},
{key: "parallel_layer_count", label: "并行层", unit: "count", description: "每帧包含多个可并行节点的依赖层数量。"},
{key: "longest_duration", label: "最长节点执行", unit: "milliseconds", description: "每帧最长执行节点的任务体墙钟。"},
{key: "longest_unattributed_duration", label: "最长节点未归因墙时", unit: "milliseconds", description: "每帧单节点最大的未归因墙钟。"}
{key: "longest_exclusive_duration", label: "最长独占任务体", unit: "milliseconds", description: "每帧单节点扣除显式协作让出后的最大任务体墙钟。"}
] as const;
type Taskflow_Graph_Metric_Key = typeof taskflow_graph_metric_definitions[number]["key"];
type Taskflow_Node_Stability = "stable" | "variable" | "missing" | "long_tail";
type Taskflow_Node_Aggregate = {
node: Taskflow_Node_Trace; present: number; executed: number; frames: number;
duration: Distribution_Statistics; queue: Distribution_Statistics; cpu: Distribution_Statistics;
cycles: Distribution_Statistics; cooperative_wait: Distribution_Statistics; cpu_time_coarse: boolean;
duration: Distribution_Statistics; queue: Distribution_Statistics;
cooperative_wait: Distribution_Statistics;
observer: Distribution_Statistics; workers: number[]; stability: Taskflow_Node_Stability;
};
type Taskflow_Graph_Aggregate = {
key: string; graph: Taskflow_Graph_Trace; frames: number; samples: number; topology_variants: number;
metrics: Record<Taskflow_Graph_Metric_Key, Distribution_Statistics>;
completed_frames: number; cpu_time_coarse_frames: number;
completed_frames: number;
nodes: Map<string, Taskflow_Node_Aggregate>; edge_presence: Map<string, number>;
};
@@ -1602,7 +1576,7 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string):
.map(graph => ({frame, graph})));
if (!samples.length) return null;
type Mutable_Node = {template: Taskflow_Node_Trace; present: number; executed: number; duration: number[];
queue: number[]; cpu: number[]; cycles: number[]; cooperative_wait: number[]; cpu_time_coarse: boolean;
queue: number[]; cooperative_wait: number[];
observer: number[]; workers: Set<number>};
const accumulated = new Map<string, Mutable_Node>();
const edges = new Map<string, number>();
@@ -1610,7 +1584,6 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string):
const metric_samples = Object.fromEntries(taskflow_graph_metric_definitions.map(
definition => [definition.key, [] as number[]])) as Record<Taskflow_Graph_Metric_Key, number[]>;
let completed_frames = 0;
let cpu_time_coarse_frames = 0;
for (const {frame, graph} of samples) {
const native_to_id = new Map(graph.nodes.map(node => [node.native_id, node.id]));
const execution = new Map(frame.executions.map(value => [value.native_id, value]));
@@ -1618,11 +1591,10 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string):
const analysis = taskflow_graph_analysis(graph, frame.executions);
for (const definition of taskflow_graph_metric_definitions)
metric_samples[definition.key].push(analysis[definition.key]);
if (analysis.cpu_time_coarse) ++cpu_time_coarse_frames;
if (graph.completed) ++completed_frames;
for (const node of graph.nodes) {
const current = accumulated.get(node.id) ?? {template: node, present: 0, executed: 0,
duration: [], queue: [], cpu: [], cycles: [], cooperative_wait: [], cpu_time_coarse: false,
duration: [], queue: [], cooperative_wait: [],
observer: [], workers: new Set<number>()};
++current.present;
const sample = execution.get(node.native_id);
@@ -1630,10 +1602,7 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string):
++current.executed;
current.duration.push(sample.duration_ms);
current.queue.push(sample.queue_wait_ms);
current.cpu.push(sample.cpu_duration_ms);
current.cycles.push(sample.cpu_cycles);
current.cooperative_wait.push(sample.cooperative_wait_ms);
current.cpu_time_coarse = current.cpu_time_coarse || sample.cpu_time_coarse;
current.observer.push(sample.observer_entry_ms + sample.observer_exit_ms);
current.workers.add(sample.worker_id);
}
@@ -1671,8 +1640,7 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string):
const node = {...value.template, native_id: id, id,
predecessors: predecessors.get(id) ?? [], successors: successors.get(id) ?? []};
nodes.set(id, {node, present: value.present, executed: value.executed, frames: frames.length,
duration, queue, cpu: distribution_statistics(value.cpu), cycles: distribution_statistics(value.cycles),
cooperative_wait: distribution_statistics(value.cooperative_wait), cpu_time_coarse: value.cpu_time_coarse,
duration, queue, cooperative_wait: distribution_statistics(value.cooperative_wait),
observer: distribution_statistics(value.observer),
workers: [...value.workers].sort((left, right) => left - right), stability});
return node;
@@ -1682,24 +1650,23 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string):
definition => [definition.key, distribution_statistics(metric_samples[definition.key])])) as
Record<Taskflow_Graph_Metric_Key, Distribution_Statistics>;
return {key, frames: frames.length, samples: samples.length, topology_variants: topology_signatures.size,
metrics, completed_frames, cpu_time_coarse_frames, nodes, edge_presence: edges,
metrics, completed_frames, nodes, edge_presence: edges,
graph: {...first, submitted_ms: 0, finished_ms: metrics.wall_time.average, nodes: graph_nodes}};
}
function taskflow_metric_value(value: number, unit: "milliseconds" | "cycles" | "count") {
function taskflow_metric_value(value: number, unit: "milliseconds" | "count") {
if (unit === "milliseconds") return milliseconds(value);
if (unit === "cycles") return cpu_cycles(value);
return value.toLocaleString("zh-CN", {maximumFractionDigits: 3});
}
function taskflow_variance_value(value: number, unit: "milliseconds" | "cycles" | "count") {
function taskflow_variance_value(value: number, unit: "milliseconds" | "count") {
if (!Number.isFinite(value)) return "--";
const suffix = unit === "milliseconds" ? " ms²" : unit === "cycles" ? " cy²" : "";
const suffix = unit === "milliseconds" ? " ms²" : "";
return `${value.toLocaleString("zh-CN", {maximumFractionDigits: 6})}${suffix}`;
}
function taskflow_distribution_value(statistic: Distribution_Statistics,
unit: "milliseconds" | "cycles" | "count") {
unit: "milliseconds" | "count") {
return <> {taskflow_metric_value(statistic.average, unit)} · {taskflow_variance_value(statistic.variance, unit)} · [{taskflow_metric_value(statistic.minimum, unit)}, {taskflow_metric_value(statistic.maximum, unit)}]</>;
}
@@ -1935,7 +1902,6 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
topology_variants: aggregate.topology_variants,
metrics: aggregate.metrics,
completed_frames: aggregate.completed_frames,
cpu_time_coarse_frames: aggregate.cpu_time_coarse_frames,
nodes: Object.fromEntries(aggregate.nodes)} : undefined,
executions: executions.filter(value => native_ids.has(value.native_id)),
all_executions: frame?.executions ?? executions
@@ -2414,7 +2380,7 @@ function Taskflow_Frame_Pane({plot, components}: {plot: Plot; components: Compon
<div><dt></dt><dd>{[...aggregate.nodes.values()].filter(node => node.stability === "variable").length}</dd></div>
<div><dt></dt><dd>{[...aggregate.nodes.values()].filter(node => node.stability === "missing").length}</dd></div>
<div><dt></dt><dd>{[...aggregate.nodes.values()].filter(node => node.stability === "long_tail").length}</dd></div>
<div><dt> / CPU </dt><dd>{aggregate.completed_frames} / {aggregate.cpu_time_coarse_frames}</dd></div>
<div><dt></dt><dd>{aggregate.completed_frames}</dd></div>
{taskflow_graph_metric_definitions.map(definition => {
const statistic = aggregate.metrics[definition.key];
return <div key={definition.key} title={`${definition.description} 样本 ${statistic.count};标准差 ${taskflow_metric_value(statistic.variability, definition.unit)}P95 ${taskflow_metric_value(statistic.p95, definition.unit)}P99 ${taskflow_metric_value(statistic.p99, definition.unit)}`}>
@@ -2427,15 +2393,12 @@ function Taskflow_Frame_Pane({plot, components}: {plot: Plot; components: Compon
<div title="提交 Taskflow 到整个 Topology 完成的墙钟时间,包含 Executor 排队和当前既有的完成尾部。"><dt>Topology </dt><dd>{milliseconds(graph_analysis.wall_time)}</dd></div>
<div title="只累计叶子任务;Taskflow Module 是包住子图的区间,不能与子节点重复相加。"><dt></dt><dd>{milliseconds(graph_analysis.execution_time)}</dd></div>
<div title="所有 Module Observer 区间之和,仅显示子图包络;不计入叶子任务总和。"><dt>Module </dt><dd>{milliseconds(graph_analysis.module_envelope_time)}</dd></div>
<div title="叶子任务独占 Taskflow Worker 片段的线程 CPU 时间;corun/corun_until 期间执行的其他任务不会归到外层节点。Windows GetThreadTimes 仍是低分辨率计费时钟。"><dt>线 CPU{graph_analysis.cpu_time_coarse ? "(低分辨率)" : ""}</dt><dd>{milliseconds(graph_analysis.cpu_execution_time)}</dd></div>
<div title="Windows 使用 QueryThreadCycleTime 记录独占片段 CPU 活动量。周期数不直接换算为秒,但短任务即使 GetThreadTimes 显示 0 也能确认确实消耗过 CPU。"><dt> CPU </dt><dd>{cpu_cycles(graph_analysis.cpu_cycles_total)}</dd></div>
<div title="Task_Graph::corun/corun_until 主动让出当前 Worker、由同一 Worker 协作执行其他 Taskflow 工作的墙钟时间。"><dt></dt><dd>{milliseconds(graph_analysis.cooperative_wait_time)}</dd></div>
<div title="任务体墙钟减去显式 cooperative wait 和线程 CPU 时间。它可能包含锁/驱动/系统调用等待以及 Windows CPU 计时量化误差,因此不是 OS 抢占时间。"><dt>{graph_analysis.cpu_time_coarse ? "(估算)" : ""}</dt><dd>{milliseconds(graph_analysis.unattributed_wall_time)}</dd></div>
<div title="任务体墙钟减去显式 cooperative wait;不在任务热路径调用线程计时系统 API。"><dt></dt><dd>{milliseconds(graph_analysis.exclusive_body_time)}</dd></div>
<div title="Topology 墙钟中至少有一个节点任务体正在执行的区间并集。"><dt></dt><dd>{milliseconds(graph_analysis.body_wall_time)}</dd></div>
<div title="Topology 墙钟中只有 Observer on_entry/on_exit 在执行、没有任务体执行的区间。"><dt>Observer </dt><dd>{milliseconds(graph_analysis.observer_wall_time)}</dd></div>
<div title="所有节点 Observer entry 墙钟耗时之和,包含并行重叠。"><dt>Observer entry </dt><dd>{milliseconds(graph_analysis.observer_entry_time)}</dd></div>
<div title="所有节点 Observer exit 和按帧追踪写入墙钟耗时之和,包含并行重叠。"><dt>Observer exit </dt><dd>{milliseconds(graph_analysis.observer_exit_time)}</dd></div>
<div title="Observer entry 和 exit 真正占用的 worker CPU 时间之和。"><dt>Observer CPU</dt><dd>{milliseconds(graph_analysis.observer_cpu_time)}</dd></div>
<div title="Topology 墙钟中没有捕获节点任务体或 Observer 活动的守恒余量。"><dt></dt><dd>{milliseconds(graph_analysis.idle_wall_time)}</dd></div>
<div title="各节点从前驱完成到 on_entry 的估算等待之和,用于定位 Executor 调度长尾。"><dt></dt><dd>{milliseconds(graph_analysis.queue_time)}</dd></div>
<div title="Topology 提交后,首个节点真正进入 Worker 前的等待。"><dt></dt><dd>{milliseconds(graph_analysis.initial_wait)}</dd></div>
@@ -2451,7 +2414,7 @@ function Taskflow_Frame_Pane({plot, components}: {plot: Plot; components: Compon
<div title="Observer 时间区间内同时执行的最大节点数量。"><dt></dt><dd>{graph_analysis.maximum_parallelism}</dd></div>
<div title="同一层节点没有相互依赖,可以并行;串行图的并行层数量为零。"><dt> / </dt><dd>{graph_analysis.layer_count} / {graph_analysis.parallel_layer_count}</dd></div>
<div title={graph_analysis.longest?.node_id}><dt></dt><dd>{graph_analysis.longest ? milliseconds(graph_analysis.longest.duration_ms) : "--"}</dd></div>
<div title={graph_analysis.longest_unattributed?.node_id}><dt></dt><dd>{graph_analysis.longest_unattributed ? `${taskflow_node_name(graph_analysis.longest_unattributed.node_id.split("/").at(-1) ?? graph_analysis.longest_unattributed.node_id)} · ${graph_analysis.longest_unattributed.cpu_time_coarse ? "≈ " : ""}${milliseconds(Math.max(0, graph_analysis.longest_unattributed.duration_ms - graph_analysis.longest_unattributed.cooperative_wait_ms - graph_analysis.longest_unattributed.cpu_duration_ms))}` : "--"}</dd></div>
<div title={graph_analysis.longest_exclusive?.node_id}><dt></dt><dd>{graph_analysis.longest_exclusive ? `${taskflow_node_name(graph_analysis.longest_exclusive.node_id.split("/").at(-1) ?? graph_analysis.longest_exclusive.node_id)} · ${milliseconds(Math.max(0, graph_analysis.longest_exclusive.duration_ms - graph_analysis.longest_exclusive.cooperative_wait_ms))}` : "--"}</dd></div>
<div><dt></dt><dd>{graph.completed ? "完成" : "未完成"}</dd></div>
</dl><Taskflow_Dag graph={graph} executions={frame.executions} frame={frame} components={components} gallery_state={gallery_state}
fullscreen={fullscreen_view === "dag"} on_fullscreen_change={value => set_fullscreen_view(value ? "dag" : null)}