@@ -236,7 +236,8 @@ impl LiveUI {
236236 }
237237
238238 /// Run the Live-UI main loop
239- pub async fn run ( & mut self ) -> Result < ( ) > {
239+ /// Returns true if interrupted by user (Ctrl+C), false if completed normally
240+ pub async fn run ( & mut self ) -> Result < bool > {
240241 // Setup terminal
241242 enable_raw_mode ( ) ?;
242243 let mut stdout = std:: io:: stdout ( ) ;
@@ -259,9 +260,13 @@ impl LiveUI {
259260 let mut last_request_count = 0u64 ;
260261 let mut last_rps_update = Instant :: now ( ) ;
261262
263+ // Track if interrupted by user
264+ let mut interrupted = false ;
265+
262266 loop {
263267 // Check if we should stop
264268 if self . should_stop {
269+ interrupted = true ;
265270 break ;
266271 }
267272
@@ -335,7 +340,7 @@ impl LiveUI {
335340 disable_raw_mode ( ) ?;
336341 execute ! ( terminal. backend_mut( ) , LeaveAlternateScreen ) ?;
337342
338- Ok ( ( ) )
343+ Ok ( interrupted )
339344 }
340345
341346 /// Render the UI
@@ -594,36 +599,20 @@ impl LiveUI {
594599
595600 let max_value = history. iter ( ) . copied ( ) . fold ( 0.0f64 , f64:: max) . max ( 1.0 ) ;
596601
597- // Build title with max value indicator
598- let formatted_max = if max_value >= 1_000_000.0 {
599- format ! ( "{:.1}M" , max_value / 1_000_000.0 )
600- } else if max_value >= 1_000.0 {
601- format ! ( "{:.1}K" , max_value / 1_000.0 )
602- } else {
603- format ! ( "{:.1}" , max_value)
604- } ;
602+ // Build title with max value indicator using formatNumber rules
603+ let formatted_max = format_number_f64 ( max_value) ;
605604 let title = format ! ( "Request Rate (Last 10s) - Max: {} req/s" , formatted_max) ;
606605
607- // Create bar chart data - use actual TPS values, not normalized percentages
606+ // Create bar chart data - label only shows time (1s, 2s, etc.)
608607 let bar_data: Vec < ( String , u64 ) > = history
609608 . iter ( )
610609 . enumerate ( )
611610 . map ( |( i, & value) | {
612- // Format TPS value with K/M suffix
613- let tps_str = if value >= 1_000_000.0 {
614- format ! ( "{:.0}M" , value / 1_000_000.0 )
615- } else if value >= 1_000.0 {
616- format ! ( "{:.0}K" , value / 1_000.0 )
617- } else {
618- format ! ( "{:.0}" , value)
619- } ;
620-
621- // Label with time and formatted TPS: "1s\n92K"
622- let label = format ! ( "{}s\n {}" , i + 1 , tps_str) ;
611+ // Label only shows time
612+ let label = format ! ( "{}s" , i + 1 ) ;
623613
624- // Use actual TPS value (as integer) for bar height
625- let tps_value = value as u64 ;
626- ( label, tps_value)
614+ // Use actual TPS value for bar height
615+ ( label, value as u64 )
627616 } )
628617 . collect ( ) ;
629618
@@ -641,17 +630,56 @@ impl LiveUI {
641630 . title ( title) ,
642631 )
643632 . data ( & bar_data_refs)
644- . bar_width ( 3 )
633+ . bar_width ( 6 )
645634 . bar_gap ( 1 )
646635 . bar_style ( Style :: default ( ) . fg ( self . theme . highlight_color ( ) ) )
647- . value_style (
648- Style :: default ( )
649- . fg ( self . theme . text_color ( ) )
650- . add_modifier ( Modifier :: BOLD ) ,
651- )
652- . label_style ( Style :: default ( ) . fg ( self . theme . text_color ( ) ) ) ;
636+ . label_style ( Style :: default ( ) . fg ( self . theme . text_color ( ) ) )
637+ . value_style ( Style :: default ( ) . fg ( Color :: Black ) . bg ( Color :: Black ) ) ; // Hide raw values
653638
654639 f. render_widget ( bar_chart, area) ;
640+
641+ // Manually render formatted numbers on top of bars
642+ let bar_width = 6u16 ;
643+ let bar_gap = 1u16 ;
644+ let total_bar_unit = bar_width + bar_gap;
645+
646+ // Calculate starting position for bars
647+ let inner_area = Rect {
648+ x : area. x + 1 ,
649+ y : area. y + 1 ,
650+ width : area. width . saturating_sub ( 2 ) ,
651+ height : area. height . saturating_sub ( 2 ) ,
652+ } ;
653+
654+ // Render formatted numbers above each bar
655+ for ( i, & value) in history. iter ( ) . enumerate ( ) {
656+ let formatted = format_number_f64 ( value) ;
657+ let bar_x = inner_area. x + 2 + ( i as u16 * total_bar_unit) ;
658+
659+ // Calculate bar height as percentage of available height
660+ let chart_height = inner_area. height . saturating_sub ( 2 ) ; // Leave space for labels
661+ let bar_height_ratio = ( value / max_value) . min ( 1.0 ) ;
662+ let bar_height = ( chart_height as f64 * bar_height_ratio) as u16 ;
663+
664+ // Position text on top of bar
665+ let text_y = inner_area. y + chart_height. saturating_sub ( bar_height) . saturating_sub ( 1 ) ;
666+
667+ if bar_x < inner_area. x + inner_area. width && text_y < inner_area. y + inner_area. height
668+ {
669+ let text_area = Rect {
670+ x : bar_x,
671+ y : text_y,
672+ width : bar_width,
673+ height : 1 ,
674+ } ;
675+
676+ let text = Paragraph :: new ( formatted)
677+ . style ( Style :: default ( ) . fg ( self . theme . text_color ( ) ) )
678+ . alignment ( Alignment :: Center ) ;
679+
680+ f. render_widget ( text, text_area) ;
681+ }
682+ }
655683 }
656684
657685 /// Render latency histogram (percentiles)
@@ -816,14 +844,23 @@ impl LiveUI {
816844 }
817845}
818846
819- /// Format large numbers with K/M suffix
847+ /// Format large numbers with K/M suffix (for u64)
820848fn format_number ( n : u64 ) -> String {
821- if n >= 1_000_000 {
822- format ! ( "{:.1}M" , n as f64 / 1_000_000.0 )
823- } else if n >= 1_000 {
824- format ! ( "{:.1}K" , n as f64 / 1_000.0 )
849+ format_number_f64 ( n as f64 )
850+ }
851+
852+ /// Format large numbers with K/M suffix (for f64)
853+ /// Follows the formatting rules:
854+ /// - >= 1M: show with 1 decimal place (e.g., "1.5M")
855+ /// - >= 1K: show with no decimal places (e.g., "24K")
856+ /// - < 1K: show as integer (e.g., "500")
857+ fn format_number_f64 ( n : f64 ) -> String {
858+ if n >= 1_000_000.0 {
859+ format ! ( "{:.1}M" , n / 1_000_000.0 )
860+ } else if n >= 1_000.0 {
861+ format ! ( "{:.0}K" , n / 1_000.0 )
825862 } else {
826- n . to_string ( )
863+ format ! ( "{:.0}" , n )
827864 }
828865}
829866
0 commit comments