@@ -31,6 +31,7 @@ cfg_if::cfg_if! {
31
31
}
32
32
33
33
use crate :: {
34
+ clock:: Clock ,
34
35
collector:: ReportCollector ,
35
36
duration:: DurationExt ,
36
37
histogram:: { LatencyHistogram , PERCENTAGES } ,
@@ -120,17 +121,31 @@ impl Drop for Terminal {
120
121
#[ async_trait]
121
122
impl ReportCollector for TuiCollector {
122
123
async fn run ( & mut self ) -> Result < BenchReport > {
123
- let mut terminal = Terminal :: new ( ) ?;
124
-
124
+ let clock = self . bench_opts . clock . clone ( ) ;
125
125
let mut hist = LatencyHistogram :: new ( ) ;
126
126
let mut stats = IterStats :: new ( ) ;
127
127
let mut status_dist = HashMap :: new ( ) ;
128
128
let mut error_dist = HashMap :: new ( ) ;
129
129
130
- let mut current_tw = TimeWindow :: Second ;
131
- let mut auto_tw = true ;
130
+ self . collect ( clock . clone ( ) , & mut hist , & mut stats , & mut status_dist , & mut error_dist )
131
+ . await ? ;
132
132
133
- let mut clock = self . bench_opts . clock . clone ( ) ;
133
+ let elapsed = clock. elapsed ( ) ;
134
+ let concurrency = self . bench_opts . concurrency ;
135
+ Ok ( BenchReport { concurrency, hist, stats, status_dist, error_dist, elapsed } )
136
+ }
137
+ }
138
+
139
+ impl TuiCollector {
140
+ async fn collect (
141
+ & mut self ,
142
+ mut clock : Clock ,
143
+ hist : & mut LatencyHistogram ,
144
+ stats : & mut IterStats ,
145
+ status_dist : & mut HashMap < Status , u64 > ,
146
+ error_dist : & mut HashMap < String , u64 > ,
147
+ ) -> Result < ( ) > {
148
+ let mut terminal = Terminal :: new ( ) ?;
134
149
135
150
let mut latest_iters = RotateWindowGroup :: new ( nonzero ! ( 60usize ) ) ;
136
151
let mut latest_iters_ticker = clock. ticker ( SECOND ) ;
@@ -141,75 +156,17 @@ impl ReportCollector for TuiCollector {
141
156
let mut ui_ticker = tokio:: time:: interval ( SECOND / self . fps . get ( ) as u32 ) ;
142
157
ui_ticker. set_missed_tick_behavior ( MissedTickBehavior :: Burst ) ;
143
158
159
+ let mut tm_win = TimeWindow :: Second ;
144
160
#[ cfg( feature = "log" ) ]
145
161
let mut show_logs = false ;
146
162
147
- let mut elapsed;
148
- ' outer: loop {
163
+ loop {
149
164
loop {
150
165
tokio:: select! {
151
166
biased;
152
- _ = ui_ticker. tick( ) => {
153
- while crossterm:: event:: poll( Duration :: from_secs( 0 ) ) ? {
154
- use KeyCode :: * ;
155
- if let Event :: Key ( KeyEvent { code, modifiers, .. } ) = crossterm:: event:: read( ) ? {
156
- match ( code, modifiers) {
157
- ( Char ( '+' ) , _) => {
158
- current_tw = current_tw. prev( ) ;
159
- auto_tw = false ;
160
- }
161
- ( Char ( '-' ) , _) => {
162
- current_tw = current_tw. next( ) ;
163
- auto_tw = false ;
164
- }
165
- ( Char ( 'a' ) , _) => auto_tw = true ,
166
- ( Char ( 'q' ) , _) | ( Char ( 'c' ) , KeyModifiers :: CONTROL ) => {
167
- self . cancel. cancel( ) ;
168
- break ' outer;
169
- }
170
- ( Char ( 'p' ) | Pause , _) => {
171
- let pause = !* self . pause. borrow( ) ;
172
- if pause {
173
- clock. pause( ) ;
174
- } else {
175
- clock. resume( ) ;
176
- }
177
- self . pause. send_replace( pause) ;
178
- }
179
- #[ cfg( feature = "log" ) ]
180
- ( Char ( 'l' ) , _) => show_logs = !show_logs,
181
- #[ cfg( feature = "log" ) ]
182
- ( code, _) if show_logs => {
183
- use TuiWidgetEvent :: * ;
184
- let mut txn = |e| self . log_state. transition( e) ;
185
- match code {
186
- Char ( ' ' ) => txn( HideKey ) ,
187
- PageDown | Char ( 'f' ) => txn( NextPageKey ) ,
188
- PageUp | Char ( 'b' ) => txn( PrevPageKey ) ,
189
- Up => txn( UpKey ) ,
190
- Down => txn( DownKey ) ,
191
- Left => txn( LeftKey ) ,
192
- Right => txn( RightKey ) ,
193
- Enter => txn( FocusKey ) ,
194
- Esc => txn( EscapeKey ) ,
195
- _ => ( ) ,
196
- }
197
- }
198
- _ => ( ) ,
199
- }
200
- }
201
- }
202
-
203
- elapsed = clock. elapsed( ) ;
204
- current_tw = if auto_tw {
205
- * TimeWindow :: variants( ) . iter( ) . rfind( |&&ts| elapsed > ts. into( ) ) . unwrap_or( & TimeWindow :: Second )
206
- } else {
207
- current_tw
208
- } ;
209
- break ;
210
- }
167
+ _ = ui_ticker. tick( ) => break ,
211
168
_ = latest_stats_ticker. tick( ) => {
212
- latest_stats. rotate( & stats) ;
169
+ latest_stats. rotate( stats) ;
213
170
continue ;
214
171
}
215
172
_ = latest_iters_ticker. tick( ) => {
@@ -221,14 +178,28 @@ impl ReportCollector for TuiCollector {
221
178
* status_dist. entry( report. status) . or_default( ) += 1 ;
222
179
hist. record( report. duration) ?;
223
180
latest_iters. push( & report) ;
224
- stats += & report;
181
+ * stats += & report;
225
182
}
226
183
Some ( Err ( e) ) => * error_dist. entry( e. to_string( ) ) . or_default( ) += 1 ,
227
- None => break ' outer ,
184
+ None => return Ok ( ( ) ) ,
228
185
}
229
186
} ;
230
187
}
231
188
189
+ let elapsed = clock. elapsed ( ) ;
190
+ let exit = self
191
+ . handle_event (
192
+ elapsed,
193
+ & mut tm_win,
194
+ & mut clock,
195
+ #[ cfg( feature = "log" ) ]
196
+ & mut show_logs,
197
+ )
198
+ . await ?;
199
+ if exit {
200
+ return Ok ( ( ) ) ;
201
+ }
202
+
232
203
terminal. draw ( |f| {
233
204
let progress_height = 3 ;
234
205
let stats_height = 5 ;
@@ -266,23 +237,79 @@ impl ReportCollector for TuiCollector {
266
237
let paused = * self . pause . borrow ( ) ;
267
238
render_process_gauge ( f, rows[ 3 ] , & stats. counter , elapsed, & self . bench_opts , paused) ;
268
239
render_stats_overall ( f, mid[ 1 ] , & stats. counter , elapsed) ;
269
- render_stats_timewin ( f, mid[ 0 ] , & latest_stats, current_tw ) ;
270
- render_status_dist ( f, mid[ 2 ] , & status_dist) ;
271
- render_error_dist ( f, rows[ 1 ] , & error_dist) ;
272
- render_iter_hist ( f, bot[ 0 ] , & latest_iters, current_tw ) ;
273
- render_latency_hist ( f, bot[ 1 ] , & hist, 7 ) ;
240
+ render_stats_timewin ( f, mid[ 0 ] , & latest_stats, tm_win ) ;
241
+ render_status_dist ( f, mid[ 2 ] , status_dist) ;
242
+ render_error_dist ( f, rows[ 1 ] , error_dist) ;
243
+ render_iter_hist ( f, bot[ 0 ] , & latest_iters, tm_win ) ;
244
+ render_latency_hist ( f, bot[ 1 ] , hist, 7 ) ;
274
245
render_tips ( f, rows[ 4 ] ) ;
275
246
276
247
#[ cfg( feature = "log" ) ]
277
- if show_logs {
278
- tui_log:: render_logs ( f, & self . log_state ) ;
279
- }
248
+ render_logs ( f, & self . log_state , show_logs) ;
280
249
} ) ?;
281
250
}
251
+ }
282
252
283
- let elapsed = clock. elapsed ( ) ;
284
- let concurrency = self . bench_opts . concurrency ;
285
- Ok ( BenchReport { concurrency, hist, stats, status_dist, error_dist, elapsed } )
253
+ async fn handle_event (
254
+ & mut self ,
255
+ elapsed : Duration ,
256
+ tm_win : & mut TimeWindow ,
257
+ clock : & mut Clock ,
258
+ #[ cfg( feature = "log" ) ] show_logs : & mut bool ,
259
+ ) -> Result < bool > {
260
+ while crossterm:: event:: poll ( Duration :: from_secs ( 0 ) ) ? {
261
+ use KeyCode :: * ;
262
+ if let Event :: Key ( KeyEvent { code, modifiers, .. } ) = crossterm:: event:: read ( ) ? {
263
+ match ( code, modifiers) {
264
+ ( Char ( '+' ) , _) => {
265
+ * tm_win = tm_win. prev ( ) ;
266
+ }
267
+ ( Char ( '-' ) , _) => {
268
+ * tm_win = tm_win. next ( ) ;
269
+ }
270
+ ( Char ( 'a' ) , _) => {
271
+ * tm_win = * TimeWindow :: variants ( )
272
+ . iter ( )
273
+ . rfind ( |& & ts| elapsed > ts. into ( ) )
274
+ . unwrap_or ( & TimeWindow :: Second )
275
+ }
276
+ ( Char ( 'q' ) , _) | ( Char ( 'c' ) , KeyModifiers :: CONTROL ) => {
277
+ self . cancel . cancel ( ) ;
278
+ return Ok ( true ) ;
279
+ }
280
+ ( Char ( 'p' ) | Pause , _) => {
281
+ let pause = !* self . pause . borrow ( ) ;
282
+ if pause {
283
+ clock. pause ( ) ;
284
+ } else {
285
+ clock. resume ( ) ;
286
+ }
287
+ self . pause . send_replace ( pause) ;
288
+ }
289
+ #[ cfg( feature = "log" ) ]
290
+ ( Char ( 'l' ) , _) => * show_logs = !* show_logs,
291
+ #[ cfg( feature = "log" ) ]
292
+ ( code, _) if * show_logs => {
293
+ use TuiWidgetEvent :: * ;
294
+ let mut txn = |e| self . log_state . transition ( e) ;
295
+ match code {
296
+ Char ( ' ' ) => txn ( HideKey ) ,
297
+ PageDown | Char ( 'f' ) => txn ( NextPageKey ) ,
298
+ PageUp | Char ( 'b' ) => txn ( PrevPageKey ) ,
299
+ Up => txn ( UpKey ) ,
300
+ Down => txn ( DownKey ) ,
301
+ Left => txn ( LeftKey ) ,
302
+ Right => txn ( RightKey ) ,
303
+ Enter => txn ( FocusKey ) ,
304
+ Esc => txn ( EscapeKey ) ,
305
+ _ => ( ) ,
306
+ }
307
+ }
308
+ _ => ( ) ,
309
+ }
310
+ }
311
+ }
312
+ Ok ( false )
286
313
}
287
314
}
288
315
@@ -625,61 +652,60 @@ impl TimeWindow {
625
652
}
626
653
627
654
#[ cfg( feature = "log" ) ]
628
- mod tui_log {
629
- use super :: * ;
655
+ pub ( crate ) fn render_logs ( frame : & mut Frame , log_state : & TuiWidgetState , show_logs : bool ) {
656
+ if !show_logs {
657
+ return ;
658
+ }
630
659
631
- #[ cfg( feature = "log" ) ]
632
- pub ( crate ) fn render_logs ( frame : & mut Frame , log_state : & TuiWidgetState ) {
633
- let log_widget = TuiLoggerSmartWidget :: default ( )
634
- . style_error ( Style :: default ( ) . fg ( Color :: Red ) )
635
- . style_debug ( Style :: default ( ) . fg ( Color :: Green ) )
636
- . style_warn ( Style :: default ( ) . fg ( Color :: Yellow ) )
637
- . style_trace ( Style :: default ( ) . fg ( Color :: Magenta ) )
638
- . style_info ( Style :: default ( ) . fg ( Color :: Cyan ) )
639
- . border_type ( ratatui:: widgets:: BorderType :: Rounded )
640
- . output_separator ( '|' )
641
- . output_level ( Some ( TuiLoggerLevelOutput :: Abbreviated ) )
642
- . output_target ( true )
643
- . output_file ( true )
644
- . output_line ( true )
645
- . title_log ( "Logs" )
646
- . title_target ( "Selector" )
647
- . state ( log_state) ;
648
-
649
- let area = centered_rect ( 80 , 80 , frame. size ( ) ) ;
650
- let rows = Layout :: default ( )
651
- . direction ( Direction :: Vertical )
652
- . constraints ( [ Constraint :: Percentage ( 100 ) , Constraint :: Min ( 1 ) ] )
653
- . split ( area. inner ( & Margin :: new ( 1 , 1 ) ) ) ;
654
- let tips = gen_tips ( [
655
- ( "Enter" , "Focus target" ) ,
656
- ( "↑/↓" , "Select target" ) ,
657
- ( "←/→" , "Display level" ) ,
658
- ( "f/b" , "Scroll" ) ,
659
- ( "Esc" , "Cancel scroll" ) ,
660
- ( "Space" , "Hide selector" ) ,
661
- ] )
662
- . right_aligned ( ) ;
660
+ let log_widget = TuiLoggerSmartWidget :: default ( )
661
+ . style_error ( Style :: default ( ) . fg ( Color :: Red ) )
662
+ . style_debug ( Style :: default ( ) . fg ( Color :: Green ) )
663
+ . style_warn ( Style :: default ( ) . fg ( Color :: Yellow ) )
664
+ . style_trace ( Style :: default ( ) . fg ( Color :: Magenta ) )
665
+ . style_info ( Style :: default ( ) . fg ( Color :: Cyan ) )
666
+ . border_type ( ratatui:: widgets:: BorderType :: Rounded )
667
+ . output_separator ( '|' )
668
+ . output_level ( Some ( TuiLoggerLevelOutput :: Abbreviated ) )
669
+ . output_target ( true )
670
+ . output_file ( true )
671
+ . output_line ( true )
672
+ . title_log ( "Logs" )
673
+ . title_target ( "Selector" )
674
+ . state ( log_state) ;
675
+
676
+ let area = centered_rect ( 80 , 80 , frame. size ( ) ) ;
677
+ let rows = Layout :: default ( )
678
+ . direction ( Direction :: Vertical )
679
+ . constraints ( [ Constraint :: Percentage ( 100 ) , Constraint :: Min ( 1 ) ] )
680
+ . split ( area. inner ( & Margin :: new ( 1 , 1 ) ) ) ;
681
+ let tips = gen_tips ( [
682
+ ( "Enter" , "Focus target" ) ,
683
+ ( "↑/↓" , "Select target" ) ,
684
+ ( "←/→" , "Display level" ) ,
685
+ ( "f/b" , "Scroll" ) ,
686
+ ( "Esc" , "Cancel scroll" ) ,
687
+ ( "Space" , "Hide selector" ) ,
688
+ ] )
689
+ . right_aligned ( ) ;
663
690
664
- frame. render_widget ( Clear , area) ;
665
- frame. render_widget ( log_widget, rows[ 0 ] ) ;
666
- frame. render_widget ( tips, rows[ 1 ] . inner ( & Margin :: new ( 1 , 0 ) ) ) ;
667
- }
691
+ frame. render_widget ( Clear , area) ;
692
+ frame. render_widget ( log_widget, rows[ 0 ] ) ;
693
+ frame. render_widget ( tips, rows[ 1 ] . inner ( & Margin :: new ( 1 , 0 ) ) ) ;
694
+ }
668
695
669
- #[ cfg( feature = "log" ) ]
670
- pub ( crate ) fn centered_rect ( percent_x : u16 , percent_y : u16 , r : Rect ) -> Rect {
671
- let popup_layout = Layout :: vertical ( [
672
- Constraint :: Percentage ( ( 100 - percent_y) / 2 ) ,
673
- Constraint :: Percentage ( percent_y) ,
674
- Constraint :: Percentage ( ( 100 - percent_y) / 2 ) ,
675
- ] )
676
- . split ( r) ;
696
+ #[ cfg( feature = "log" ) ]
697
+ pub ( crate ) fn centered_rect ( percent_x : u16 , percent_y : u16 , r : Rect ) -> Rect {
698
+ let popup_layout = Layout :: vertical ( [
699
+ Constraint :: Percentage ( ( 100 - percent_y) / 2 ) ,
700
+ Constraint :: Percentage ( percent_y) ,
701
+ Constraint :: Percentage ( ( 100 - percent_y) / 2 ) ,
702
+ ] )
703
+ . split ( r) ;
677
704
678
- Layout :: horizontal ( [
679
- Constraint :: Percentage ( ( 100 - percent_x) / 2 ) ,
680
- Constraint :: Percentage ( percent_x) ,
681
- Constraint :: Percentage ( ( 100 - percent_x) / 2 ) ,
682
- ] )
683
- . split ( popup_layout[ 1 ] ) [ 1 ]
684
- }
705
+ Layout :: horizontal ( [
706
+ Constraint :: Percentage ( ( 100 - percent_x) / 2 ) ,
707
+ Constraint :: Percentage ( percent_x) ,
708
+ Constraint :: Percentage ( ( 100 - percent_x) / 2 ) ,
709
+ ] )
710
+ . split ( popup_layout[ 1 ] ) [ 1 ]
685
711
}
0 commit comments