deepstrike_core/benchmark/
mod.rs1use crate::orchestration::task_graph::TaskGraph;
4use crate::orchestration::workflow::{
5 DependencyPolicy, WorkflowNode, WorkflowNodeStatus, WorkflowRun, WorkflowSpec,
6};
7use crate::scheduler::policy::SchedulerPolicyConfig;
8use crate::types::agent::AgentRole;
9use crate::types::result::{LoopResult, TerminationReason};
10use crate::types::task::RuntimeTask;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct CriticalPathGate {
14 pub id_order_makespan: u64,
15 pub policy_makespan: u64,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct LoopFairnessGate {
20 pub waiting_rounds: u64,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct TerminationPolicyGate {
25 pub cases_checked: usize,
26}
27
28fn result(termination: TerminationReason, loop_continue: Option<bool>) -> LoopResult {
29 LoopResult {
30 termination,
31 final_message: None,
32 turns_used: 1,
33 total_tokens_used: 0,
34 loop_continue,
35 classify_branch: None,
36 pace_decision: None,
37 tournament_winner: None,
38 }
39}
40
41fn f1_graph() -> (TaskGraph, Vec<u64>) {
42 let mut graph = TaskGraph::new();
43 let wide_a = graph.add(RuntimeTask::new("wide-a"), vec![]);
44 let wide_b = graph.add(RuntimeTask::new("wide-b"), vec![]);
45 let wide_c = graph.add(RuntimeTask::new("wide-c"), vec![]);
46 let chain_1 = graph.add(RuntimeTask::new("critical-1"), vec![]);
47 graph.add(RuntimeTask::new("wide-a-1"), vec![wide_a]);
48 graph.add(RuntimeTask::new("wide-a-2"), vec![wide_a]);
49 graph.add(RuntimeTask::new("wide-b-1"), vec![wide_b]);
50 graph.add(RuntimeTask::new("wide-b-2"), vec![wide_b]);
51 graph.add(RuntimeTask::new("wide-c-1"), vec![wide_c]);
52 graph.add(RuntimeTask::new("wide-c-2"), vec![wide_c]);
53 let chain_2 = graph.add(RuntimeTask::new("critical-2"), vec![chain_1]);
54 let chain_3 = graph.add(RuntimeTask::new("critical-3"), vec![chain_2]);
55 let chain_4 = graph.add(RuntimeTask::new("critical-4"), vec![chain_3]);
56 graph.add(RuntimeTask::new("critical-5"), vec![chain_4]);
57
58 let mut durations = vec![1; graph.len()];
59 for node in [chain_1, chain_2, chain_3, chain_4, 13] {
60 durations[node] = 4;
61 }
62 (graph, durations)
63}
64
65fn simulate_f1(policy: SchedulerPolicyConfig) -> u64 {
66 let (mut graph, durations) = f1_graph();
67 graph.configure_scheduling(policy, &[]);
68 let mut now = 0u64;
69 let mut running: Vec<(usize, u64)> = Vec::new();
70 while !graph.all_done() {
71 for node in graph.ready_tasks() {
72 if running.len() == 2 {
73 break;
74 }
75 graph.start(node);
76 running.push((node, now + durations[node]));
77 }
78 let next = running
79 .iter()
80 .map(|(_, finish)| *finish)
81 .min()
82 .expect("unfinished graph must have runnable work");
83 now = next;
84 let completed: Vec<usize> = running
85 .iter()
86 .filter_map(|(node, finish)| (*finish == now).then_some(*node))
87 .collect();
88 running.retain(|(_, finish)| *finish != now);
89 for node in completed {
90 graph.complete(node, result(TerminationReason::Completed, None));
91 }
92 }
93 now
94}
95
96pub fn f1_critical_path_skew() -> CriticalPathGate {
98 let id_policy = SchedulerPolicyConfig {
99 critical_path_weight: 0,
100 fanout_weight: 0,
101 age_weight: 0,
102 token_cost_weight: 0,
103 ..SchedulerPolicyConfig::default()
104 };
105 CriticalPathGate {
106 id_order_makespan: simulate_f1(id_policy),
107 policy_makespan: simulate_f1(SchedulerPolicyConfig::default()),
108 }
109}
110
111pub fn f2_loop_fairness() -> LoopFairnessGate {
113 let spec = WorkflowSpec::new(vec![
114 WorkflowNode::new(RuntimeTask::new("loop"), AgentRole::Implement).with_loop(100),
115 WorkflowNode::new(RuntimeTask::new("peer"), AgentRole::Implement),
116 ]);
117 let mut run = WorkflowRun::new(&spec, "f2").expect("valid F2 workflow");
118 assert_eq!(run.ready_batch(), vec![0, 1]);
119 let loop_id = run.current_agent_id(0);
120 run.mark_spawned(0, &loop_id);
121 run.record_completion(&loop_id, result(TerminationReason::Completed, Some(true)));
122 let next = run.ready_batch();
123 LoopFairnessGate {
124 waiting_rounds: u64::from(next.first().copied() != Some(1)),
125 }
126}
127
128fn expected_status(
129 termination: TerminationReason,
130 policy: DependencyPolicy,
131) -> Option<WorkflowNodeStatus> {
132 match (termination, policy) {
133 (TerminationReason::Completed, _) => None,
134 (
135 TerminationReason::MaxTurns
136 | TerminationReason::TokenBudget
137 | TerminationReason::Timeout
138 | TerminationReason::MilestoneExceeded
139 | TerminationReason::ContextOverflow
140 | TerminationReason::NoProgress,
141 DependencyPolicy::AllSuccess,
142 ) => Some(WorkflowNodeStatus::SkippedUpstreamFailed),
143 (
144 TerminationReason::Error | TerminationReason::UserAbort,
145 DependencyPolicy::AllSuccess | DependencyPolicy::AcceptPartial,
146 ) => Some(WorkflowNodeStatus::SkippedUpstreamFailed),
147 _ => None,
148 }
149}
150
151pub fn f3_termination_dependency_matrix() -> TerminationPolicyGate {
154 let terminations = [
155 TerminationReason::Completed,
156 TerminationReason::MaxTurns,
157 TerminationReason::Error,
158 ];
159 let policies = [
160 DependencyPolicy::AllSuccess,
161 DependencyPolicy::AcceptPartial,
162 DependencyPolicy::AllTerminal,
163 DependencyPolicy::Optional,
164 ];
165 let mut cases_checked = 0;
166 for termination in terminations {
167 for policy in policies {
168 let spec = WorkflowSpec::new(vec![
169 WorkflowNode::new(RuntimeTask::new("upstream"), AgentRole::Implement),
170 WorkflowNode::new(RuntimeTask::new("dependent"), AgentRole::Implement)
171 .with_depends_on(vec![0])
172 .with_dependency_policy(policy),
173 ]);
174 let mut run = WorkflowRun::new(&spec, "f3").expect("valid F3 workflow");
175 let upstream = run.current_agent_id(0);
176 run.mark_spawned(0, &upstream);
177 run.record_completion(&upstream, result(termination, None));
178 match expected_status(termination, policy) {
179 None => assert!(run.ready_batch().contains(&1)),
180 Some(status) => assert_eq!(run.node_outcomes()[1].status, status),
181 }
182 cases_checked += 1;
183 }
184 }
185 TerminationPolicyGate { cases_checked }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn f1_gate_reduces_makespan() {
194 let gate = f1_critical_path_skew();
195 assert!(gate.policy_makespan < gate.id_order_makespan, "{gate:?}");
196 }
197
198 #[test]
199 fn f2_gate_bounds_peer_wait() {
200 assert_eq!(f2_loop_fairness().waiting_rounds, 0);
201 }
202
203 #[test]
204 fn f3_gate_closes_all_twelve_cases() {
205 assert_eq!(f3_termination_dependency_matrix().cases_checked, 12);
206 }
207}