strange_loop/nano_agent/
budget.rs1use super::rdtsc;
4use std::time::Instant;
5
6#[derive(Debug, Clone, Copy)]
8pub struct Budget {
9 pub per_tick_ns: u128,
10 pub max_jitter_ns: u128,
11 pub max_violations: u32,
12}
13
14impl Budget {
15 pub fn new(per_tick_ns: u128) -> Self {
16 Self {
17 per_tick_ns,
18 max_jitter_ns: per_tick_ns / 10, max_violations: 3,
20 }
21 }
22
23 pub fn with_jitter(mut self, max_jitter_ns: u128) -> Self {
24 self.max_jitter_ns = max_jitter_ns;
25 self
26 }
27
28 pub fn with_max_violations(mut self, max: u32) -> Self {
29 self.max_violations = max;
30 self
31 }
32}
33
34pub struct BudgetGuard {
36 start_tsc: u64,
37 start_ns: u128,
38 budget_ns: u128,
39 jitter_threshold: u128,
40 violations: u32,
41 max_violations: u32,
42}
43
44impl BudgetGuard {
45 pub fn new(budget: Budget) -> Self {
46 let start_instant = Instant::now();
47 Self {
48 start_tsc: rdtsc(),
49 start_ns: 0, budget_ns: budget.per_tick_ns,
51 jitter_threshold: budget.max_jitter_ns,
52 violations: 0,
53 max_violations: budget.max_violations,
54 }
55 }
56
57 #[inline(always)]
59 pub fn is_exhausted(&self, now_ns: u128) -> bool {
60 now_ns - self.start_ns >= self.budget_ns
61 }
62
63 #[inline(always)]
65 pub fn check_violation(&mut self, elapsed_ns: u128) -> bool {
66 if elapsed_ns > self.budget_ns + self.jitter_threshold {
67 self.violations += 1;
68 return self.violations > self.max_violations;
69 }
70 false
71 }
72
73 #[inline(always)]
75 pub fn reset(&mut self, now_ns: u128) {
76 self.start_tsc = rdtsc();
77 self.start_ns = now_ns;
78 }
79
80 #[inline(always)]
82 pub fn elapsed_cycles(&self) -> u64 {
83 rdtsc() - self.start_tsc
84 }
85}
86
87pub struct KillSwitch {
89 enabled: bool,
90 max_runtime_ns: u128,
91 max_ticks: u64,
92 max_messages: u64,
93 start_time: Instant,
94 tick_count: u64,
95 message_count: u64,
96}
97
98impl KillSwitch {
99 pub fn new(max_runtime_ns: u128) -> Self {
100 Self {
101 enabled: true,
102 max_runtime_ns,
103 max_ticks: 1_000_000_000, max_messages: 10_000_000, start_time: Instant::now(),
106 tick_count: 0,
107 message_count: 0,
108 }
109 }
110
111 pub fn should_kill(&self) -> bool {
113 if !self.enabled {
114 return false;
115 }
116
117 if self.start_time.elapsed().as_nanos() > self.max_runtime_ns {
119 return true;
120 }
121
122 if self.tick_count > self.max_ticks {
124 return true;
125 }
126
127 if self.message_count > self.max_messages {
129 return true;
130 }
131
132 false
133 }
134
135 pub fn record_tick(&mut self) {
137 self.tick_count += 1;
138 }
139
140 pub fn record_messages(&mut self, count: u64) {
142 self.message_count += count;
143 }
144
145 pub fn disable(&mut self) {
147 self.enabled = false;
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::thread::sleep;
155 use std::time::Duration;
156
157 #[test]
158 fn test_budget() {
159 let budget = Budget::new(1000)
160 .with_jitter(100)
161 .with_max_violations(5);
162
163 assert_eq!(budget.per_tick_ns, 1000);
164 assert_eq!(budget.max_jitter_ns, 100);
165 assert_eq!(budget.max_violations, 5);
166 }
167
168 #[test]
169 fn test_budget_guard() {
170 let budget = Budget::new(1_000_000); let mut guard = BudgetGuard::new(budget);
172
173 guard.reset(0);
174 assert!(!guard.is_exhausted(500_000)); assert!(guard.is_exhausted(1_500_000)); }
177
178 #[test]
179 fn test_kill_switch() {
180 let mut kill = KillSwitch::new(100_000_000); assert!(!kill.should_kill());
183
184 for _ in 0..1000 {
186 kill.record_tick();
187 }
188 assert!(!kill.should_kill());
189
190 kill.tick_count = 2_000_000_000;
192 assert!(kill.should_kill());
193 }
194}