Skip to content

Commit 25dbdce

Browse files
authored
Fix capped large token totals in TUI stats (#130)
1 parent af02f13 commit 25dbdce

8 files changed

Lines changed: 78 additions & 51 deletions

File tree

src/contribution_cache/single_message.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ impl PackedStatsDate {
118118
#[inline]
119119
pub fn to_tui_stats(self) -> TuiStats {
120120
TuiStats {
121-
input_tokens: self.input_tokens(),
122-
output_tokens: self.output_tokens(),
123-
reasoning_tokens: self.reasoning_tokens(),
124-
cached_tokens: self.cached_tokens(),
125-
cost_cents: self.cost_cents() as u32,
121+
input_tokens: self.input_tokens() as u64,
122+
output_tokens: self.output_tokens() as u64,
123+
reasoning_tokens: self.reasoning_tokens() as u64,
124+
cached_tokens: self.cached_tokens() as u64,
125+
cost_cents: self.cost_cents() as u64,
126126
tool_calls: self.tool_calls() as u32,
127127
}
128128
}

src/mcp/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ impl SplitrailMcpServer {
349349
.sum();
350350
let total_tokens: u64 = filtered_stats
351351
.iter()
352-
.map(|(_, ds)| (ds.stats.input_tokens as u64) + (ds.stats.output_tokens as u64))
352+
.map(|(_, ds)| ds.stats.input_tokens + ds.stats.output_tokens)
353353
.sum();
354354
let total_tool_calls: u32 = filtered_stats
355355
.iter()

src/mcp/types.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ impl DailySummary {
158158
ai_messages: ds.ai_messages,
159159
conversations: ds.conversations,
160160
total_cost: ds.stats.cost(),
161-
input_tokens: ds.stats.input_tokens as u64,
162-
output_tokens: ds.stats.output_tokens as u64,
163-
cache_read_tokens: ds.stats.cached_tokens as u64,
161+
input_tokens: ds.stats.input_tokens,
162+
output_tokens: ds.stats.output_tokens,
163+
cache_read_tokens: ds.stats.cached_tokens,
164164
tool_calls: ds.stats.tool_calls,
165165
files_read: file_ops.files_read,
166166
files_edited: file_ops.files_edited,

src/tui.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,15 +1021,15 @@ fn draw_daily_stats_table(
10211021
// Find best values for highlighting
10221022
// TODO: Let's refactor this.
10231023

1024-
let mut best_cost_cents: u32 = 0;
1024+
let mut best_cost_cents: u64 = 0;
10251025
let mut best_cost_i = 0;
1026-
let mut best_cached_tokens: u32 = 0;
1026+
let mut best_cached_tokens: u64 = 0;
10271027
let mut best_cached_tokens_i = 0;
1028-
let mut best_input_tokens: u32 = 0;
1028+
let mut best_input_tokens: u64 = 0;
10291029
let mut best_input_tokens_i = 0;
1030-
let mut best_output_tokens: u32 = 0;
1030+
let mut best_output_tokens: u64 = 0;
10311031
let mut best_output_tokens_i = 0;
1032-
let mut best_reasoning_tokens: u32 = 0;
1032+
let mut best_reasoning_tokens: u64 = 0;
10331033
let mut best_reasoning_tokens_i = 0;
10341034
let mut best_conversations = 0;
10351035
let mut best_conversations_i = 0;
@@ -1089,11 +1089,11 @@ fn draw_daily_stats_table(
10891089
continue;
10901090
}
10911091

1092-
total_cost_cents += day_stats.stats.cost_cents as u64;
1093-
total_cached += day_stats.stats.cached_tokens as u64;
1094-
total_input += day_stats.stats.input_tokens as u64;
1095-
total_output += day_stats.stats.output_tokens as u64;
1096-
total_reasoning += day_stats.stats.reasoning_tokens as u64;
1092+
total_cost_cents += day_stats.stats.cost_cents;
1093+
total_cached += day_stats.stats.cached_tokens;
1094+
total_input += day_stats.stats.input_tokens;
1095+
total_output += day_stats.stats.output_tokens;
1096+
total_reasoning += day_stats.stats.reasoning_tokens;
10971097
total_tool_calls += day_stats.stats.tool_calls as u64;
10981098
total_conversations += day_stats.conversations as u64;
10991099

@@ -1607,11 +1607,11 @@ fn draw_session_stats_table(
16071607
best_tool_calls_i = Some(idx);
16081608
}
16091609

1610-
total_cost_cents += session.stats.cost_cents as u64;
1611-
total_input_tokens += session.stats.input_tokens as u64;
1612-
total_output_tokens += session.stats.output_tokens as u64;
1613-
total_cached_tokens += session.stats.cached_tokens as u64;
1614-
total_reasoning_tokens += session.stats.reasoning_tokens as u64;
1610+
total_cost_cents += session.stats.cost_cents;
1611+
total_input_tokens += session.stats.input_tokens;
1612+
total_output_tokens += session.stats.output_tokens;
1613+
total_cached_tokens += session.stats.cached_tokens;
1614+
total_reasoning_tokens += session.stats.reasoning_tokens;
16151615
total_tool_calls += session.stats.tool_calls as u64;
16161616

16171617
for &(model, _) in session.models.iter() {
@@ -1921,11 +1921,11 @@ fn draw_summary_stats(
19211921
continue;
19221922
}
19231923

1924-
total_cost_cents += day_stats.stats.cost_cents as u64;
1925-
total_cached += day_stats.stats.cached_tokens as u64;
1926-
total_input += day_stats.stats.input_tokens as u64;
1927-
total_output += day_stats.stats.output_tokens as u64;
1928-
total_reasoning += day_stats.stats.reasoning_tokens as u64;
1924+
total_cost_cents += day_stats.stats.cost_cents;
1925+
total_cached += day_stats.stats.cached_tokens;
1926+
total_input += day_stats.stats.input_tokens;
1927+
total_output += day_stats.stats.output_tokens;
1928+
total_reasoning += day_stats.stats.reasoning_tokens;
19291929
total_tool_calls += day_stats.stats.tool_calls as u64;
19301930

19311931
// Collect unique days across all tools that have actual data

src/tui/logic.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@ pub use crate::types::SessionAggregate;
88
/// Accumulate TUI-relevant stats from a full Stats into a TuiStats.
99
/// Only copies the 6 fields displayed in the TUI.
1010
pub fn accumulate_tui_stats(dst: &mut TuiStats, src: &Stats) {
11-
dst.input_tokens = dst.input_tokens.saturating_add(src.input_tokens as u32);
12-
dst.output_tokens = dst.output_tokens.saturating_add(src.output_tokens as u32);
13-
dst.reasoning_tokens = dst
14-
.reasoning_tokens
15-
.saturating_add(src.reasoning_tokens as u32);
16-
dst.cached_tokens = dst.cached_tokens.saturating_add(src.cached_tokens as u32);
11+
dst.input_tokens = dst.input_tokens.saturating_add(src.input_tokens);
12+
dst.output_tokens = dst.output_tokens.saturating_add(src.output_tokens);
13+
dst.reasoning_tokens = dst.reasoning_tokens.saturating_add(src.reasoning_tokens);
14+
dst.cached_tokens = dst.cached_tokens.saturating_add(src.cached_tokens);
1715
dst.add_cost(src.cost);
1816
dst.tool_calls = dst.tool_calls.saturating_add(src.tool_calls);
1917
}

src/tui/tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,35 @@ fn test_large_tui_stats_accumulation() {
325325
assert!((dst.cost() - 10.0).abs() < 0.01);
326326
}
327327

328+
#[test]
329+
fn test_tui_stats_accumulation_exceeds_u32_max() {
330+
let mut dst = TuiStats::default();
331+
let src = Stats {
332+
input_tokens: u32::MAX as u64,
333+
output_tokens: 1,
334+
reasoning_tokens: 2,
335+
cached_tokens: 3,
336+
..Stats::default()
337+
};
338+
339+
accumulate_tui_stats(&mut dst, &src);
340+
accumulate_tui_stats(
341+
&mut dst,
342+
&Stats {
343+
input_tokens: 1,
344+
output_tokens: u32::MAX as u64,
345+
reasoning_tokens: u32::MAX as u64,
346+
cached_tokens: u32::MAX as u64,
347+
..Stats::default()
348+
},
349+
);
350+
351+
assert_eq!(dst.input_tokens, u32::MAX as u64 + 1);
352+
assert_eq!(dst.output_tokens, u32::MAX as u64 + 1);
353+
assert_eq!(dst.reasoning_tokens, u32::MAX as u64 + 2);
354+
assert_eq!(dst.cached_tokens, u32::MAX as u64 + 3);
355+
}
356+
328357
// ============================================================================
329358
// COMPREHENSIVE DATA INTEGRITY TESTS
330359
// ============================================================================

src/types.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,11 @@ impl std::ops::SubAssign for Stats {
459459
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
460460
#[serde(rename_all = "camelCase")]
461461
pub struct TuiStats {
462-
pub input_tokens: u32,
463-
pub output_tokens: u32,
464-
pub reasoning_tokens: u32,
465-
pub cached_tokens: u32,
466-
pub cost_cents: u32, // Store as cents to avoid f32 precision issues
462+
pub input_tokens: u64,
463+
pub output_tokens: u64,
464+
pub reasoning_tokens: u64,
465+
pub cached_tokens: u64,
466+
pub cost_cents: u64, // Store as cents to avoid f32 precision issues
467467
pub tool_calls: u32,
468468
}
469469

@@ -477,26 +477,26 @@ impl TuiStats {
477477
/// Set cost from f64 dollars
478478
#[inline]
479479
pub fn set_cost(&mut self, dollars: f64) {
480-
self.cost_cents = (dollars * 100.0).round() as u32;
480+
self.cost_cents = (dollars * 100.0).round() as u64;
481481
}
482482

483483
/// Add cost from f64 dollars
484484
#[inline]
485485
pub fn add_cost(&mut self, dollars: f64) {
486486
self.cost_cents = self
487487
.cost_cents
488-
.saturating_add((dollars * 100.0).round() as u32);
488+
.saturating_add((dollars * 100.0).round() as u64);
489489
}
490490
}
491491

492492
impl From<&Stats> for TuiStats {
493493
fn from(s: &Stats) -> Self {
494494
TuiStats {
495-
input_tokens: s.input_tokens as u32,
496-
output_tokens: s.output_tokens as u32,
497-
reasoning_tokens: s.reasoning_tokens as u32,
498-
cached_tokens: s.cached_tokens as u32,
499-
cost_cents: (s.cost * 100.0).round() as u32,
495+
input_tokens: s.input_tokens,
496+
output_tokens: s.output_tokens,
497+
reasoning_tokens: s.reasoning_tokens,
498+
cached_tokens: s.cached_tokens,
499+
cost_cents: (s.cost * 100.0).round() as u64,
500500
tool_calls: s.tool_calls,
501501
}
502502
}

src/utils.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,19 +223,19 @@ pub fn aggregate_by_date(entries: &[ConversationMessage]) -> BTreeMap<String, Da
223223
daily_stats_entry.stats.input_tokens = daily_stats_entry
224224
.stats
225225
.input_tokens
226-
.saturating_add(entry.stats.input_tokens as u32);
226+
.saturating_add(entry.stats.input_tokens);
227227
daily_stats_entry.stats.output_tokens = daily_stats_entry
228228
.stats
229229
.output_tokens
230-
.saturating_add(entry.stats.output_tokens as u32);
230+
.saturating_add(entry.stats.output_tokens);
231231
daily_stats_entry.stats.reasoning_tokens = daily_stats_entry
232232
.stats
233233
.reasoning_tokens
234-
.saturating_add(entry.stats.reasoning_tokens as u32);
234+
.saturating_add(entry.stats.reasoning_tokens);
235235
daily_stats_entry.stats.cached_tokens = daily_stats_entry
236236
.stats
237237
.cached_tokens
238-
.saturating_add(entry.stats.cached_tokens as u32);
238+
.saturating_add(entry.stats.cached_tokens);
239239
daily_stats_entry.stats.tool_calls = daily_stats_entry
240240
.stats
241241
.tool_calls

0 commit comments

Comments
 (0)