Skip to main content

codetether_agent/tui/app/state/
timing.rs

1//! Request timing / latency tracking methods.
2//!
3//! First-token and last-token timestamps per request; completed timings
4//! are promoted to `last_request_*` fields for the UI.
5
6use std::time::Instant;
7
8impl super::AppState {
9    pub fn begin_request_timing(&mut self) {
10        self.processing_started_at = Some(Instant::now());
11        self.current_request_first_token_ms = None;
12        self.current_request_last_token_ms = None;
13    }
14
15    pub fn current_request_elapsed_ms(&self) -> Option<u64> {
16        self.processing_started_at
17            .map(|started| started.elapsed().as_millis() as u64)
18    }
19
20    pub fn note_text_token(&mut self) {
21        let Some(elapsed_ms) = self.current_request_elapsed_ms() else {
22            return;
23        };
24        if self.current_request_first_token_ms.is_none() {
25            self.current_request_first_token_ms = Some(elapsed_ms);
26        }
27        self.current_request_last_token_ms = Some(elapsed_ms);
28    }
29
30    pub fn complete_request_timing(&mut self) {
31        self.last_request_first_token_ms = self.current_request_first_token_ms;
32        self.last_request_last_token_ms = self.current_request_last_token_ms;
33        self.clear_request_timing();
34    }
35
36    pub fn clear_request_timing(&mut self) {
37        self.processing_started_at = None;
38        self.current_request_first_token_ms = None;
39        self.current_request_last_token_ms = None;
40    }
41
42    /// Finalize the current turn and record e2e latency stats.
43    ///
44    /// Called when `SessionEvent::Done` fires — this is the full
45    /// agentic loop (Enter → model calls → tool calls → done).
46    pub fn complete_turn_timing(&mut self) {
47        let e2e_ms = self.current_request_elapsed_ms();
48        let ttft_ms = self.current_request_first_token_ms;
49        if let Some(e2e) = e2e_ms {
50            self.chat_latency.record(e2e, ttft_ms);
51        }
52        self.complete_request_timing();
53    }
54
55    pub fn begin_streaming(&mut self) {
56        self.streaming_start = Some(Instant::now());
57        self.streaming_chars = 0;
58    }
59
60    pub fn record_streaming_chars(&mut self, len: usize) {
61        self.streaming_chars = self.streaming_chars.saturating_add(len);
62    }
63
64    pub fn streaming_tok_per_sec(&self) -> Option<f64> {
65        let start = self.streaming_start?;
66        let elapsed = start.elapsed().as_secs_f64();
67        if elapsed < 0.1 {
68            return None;
69        }
70        let tokens = self.streaming_chars as f64 / 4.0;
71        Some(tokens / elapsed)
72    }
73}