1- mod sparkline;
21mod time_marker;
32mod timestamp_finder;
43
54use anyhow:: Result ;
65use std:: io:: { prelude:: * , BufReader } ;
76
8- use crate :: sparkline:: sparkline;
97use crate :: timestamp_finder:: TimestampFinder ;
108
11- pub fn build_sparkline ( timestamps : & [ i64 ] , length : usize ) -> Result < String > {
12- let timestamps_per_bucket = timestamp_frequency_distribution ( timestamps, length) ;
9+ const SPARKS : & [ & str ] = & [ "▁" , "▂" , "▃" , "▄" , "▅" , "▆" , "▇" , "█" ] ;
10+
11+ pub fn build_sparkline ( timestamps : & [ i64 ] , width : usize , height : usize ) -> String {
12+ let timestamp_frequencies = timestamp_frequency_distribution ( timestamps, width) ;
1313 let ( min, max) = (
14- * timestamps_per_bucket . iter ( ) . min ( ) . unwrap ( ) as f64 ,
15- * timestamps_per_bucket . iter ( ) . max ( ) . unwrap ( ) as f64 ,
14+ * timestamp_frequencies . iter ( ) . min ( ) . unwrap ( ) as f64 ,
15+ * timestamp_frequencies . iter ( ) . max ( ) . unwrap ( ) as f64 ,
1616 ) ;
17- let sparkline = timestamps_per_bucket
17+ let mut canvas = vec ! [ vec![ " " ; width] ; height] ;
18+ let slots_per_line = SPARKS . len ( ) ;
19+
20+ timestamp_frequencies
1821 . iter ( )
19- . map ( |count| sparkline ( min, max, * count as f64 ) . to_owned ( ) )
20- . collect ( ) ;
21- Ok ( sparkline)
22+ . enumerate ( )
23+ . for_each ( |( column, freq) | {
24+ let proportion = ( * freq as f64 - min) / ( max - min) ;
25+ let scaled_proportion = proportion * height as f64 ;
26+ let mut slots_left = ( scaled_proportion * slots_per_line as f64 ) . ceil ( ) as usize ;
27+ if slots_left == 0 {
28+ // Always fill at least one slot
29+ slots_left = 1 ;
30+ }
31+ ( 0 ..height) . for_each ( |row| {
32+ if slots_left > slots_per_line {
33+ canvas[ row] [ column] = * SPARKS . last ( ) . unwrap ( ) ;
34+ slots_left -= slots_per_line;
35+ } else if slots_left > 0 {
36+ canvas[ row] [ column] = SPARKS [ slots_left - 1 ] ;
37+ slots_left = 0 ;
38+ }
39+ } )
40+ } ) ;
41+
42+ canvas
43+ . iter ( )
44+ . rev ( )
45+ . map ( |chars| chars. join ( "" ) )
46+ . reduce ( |a, b| format ! ( "{}\n {}" , a, b) )
47+ . unwrap ( )
2248}
2349
2450pub fn scan_for_timestamps < R > ( reader : R , format : & str ) -> Result < Vec < i64 > >
@@ -139,7 +165,7 @@ mod tests {
139165 use super :: * ;
140166
141167 #[ test]
142- fn build_sparkline_ ( ) {
168+ fn build_sparkline_with_one_line ( ) {
143169 let log = "Nov 23 06:26:40 ip-10-1-1-1 haproxy[20128]: 10.1.1.10:57305 [23/Nov/2019:06:26:40.781] public myapp/i-05fa49c0e7db8c328 0/0/0/78/78 206 913/458 - - ---- 9/9/6/0/0 0/0 {bytes=0-0} {||1|bytes 0-0/499704} \" GET \
144170 /2518cb13a48bdf53b2f936f44e7042a3cc7baa06 HTTP/1.1\"
145171Nov 23 06:26:41 ip-10-1-1-1 haproxy[20128]: 10.1.1.11:51819 [23/Nov/2019:06:27:41.780] public myapp/i-059c225b48702964a 0/0/0/80/80 200 802/142190 - - ---- 8/8/5/0/0 0/0 {} {||141752|} \" GET /2043f2eb9e2691edcc0c8084d1ff\
@@ -163,13 +189,49 @@ e4ff7f1d9d80f HTTP/1.1\"
163189" ;
164190 let format = "%d/%b/%Y:%H:%M:%S%.f" ;
165191 let timestamps = scan_for_timestamps ( log. as_bytes ( ) , format) . unwrap ( ) ;
166- let sparkline = build_sparkline ( & timestamps, 80 ) . unwrap ( ) ;
192+ let sparkline = build_sparkline ( & timestamps, 80 , 1 ) ;
167193 assert_eq ! (
168194 sparkline,
169195 "█▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁█"
170196 ) ;
171197 }
172198
199+ #[ test]
200+ fn build_sparkline_with_multiple_lines ( ) {
201+ let log = "Nov 23 06:26:40 ip-10-1-1-1 haproxy[20128]: 10.1.1.10:57305 [23/Nov/2019:06:26:40.781] public myapp/i-05fa49c0e7db8c328 0/0/0/78/78 206 913/458 - - ---- 9/9/6/0/0 0/0 {bytes=0-0} {||1|bytes 0-0/499704} \" GET \
202+ /2518cb13a48bdf53b2f936f44e7042a3cc7baa06 HTTP/1.1\"
203+ Nov 23 06:26:41 ip-10-1-1-1 haproxy[20128]: 10.1.1.11:51819 [23/Nov/2019:06:27:41.780] public myapp/i-059c225b48702964a 0/0/0/80/80 200 802/142190 - - ---- 8/8/5/0/0 0/0 {} {||141752|} \" GET /2043f2eb9e2691edcc0c8084d1ff\
204+ ce8bd70bc6e7 HTTP/1.1\"
205+ Nov 23 06:26:42 ip-10-1-1-1 haproxy[20128]: 10.1.1.12:38870 [23/Nov/2019:06:28:42.773] public myapp/i-048088fd46abe7ed0 0/0/0/77/100 200 823/512174 - - ---- 8/8/5/0/0 0/0 {} {||511736|} \" GET /eb59c0b5dad36f080f3d261c625\
206+ 7ce0e21ef1a01 HTTP/1.1\"
207+ Nov 23 06:26:43 ip-10-1-1-1 haproxy[20128]: 10.1.1.13:35528 [23/Nov/2019:06:29:43.775] public myapp/i-05e9315b035d50f62 0/0/0/103/105 200 869/431481 - - ---- 8/8/1/0/0 0/0 {} {|||} \" GET /164672c9d75c76a8fa237c24f9cbfd22\
208+ 22554f6d HTTP/1.1\"
209+ Nov 23 06:26:44 ip-10-1-1-1 haproxy[20128]: 10.1.1.14:48553 [23/Nov/2019:06:30:44.808] public myapp/i-0008bfe6b1c98e964 0/0/0/72/73 200 840/265518 - - ---- 7/7/5/0/0 0/0 {} {||265080|} \" GET /e3b526928196d19ab3419d433f3d\
210+ e0ceb71e62b5 HTTP/1.1\"
211+ Nov 23 06:26:45 ip-10-1-1-1 haproxy[20128]: 10.1.1.15:60969 [23/Nov/2019:06:31:45.727] public myapp/i-005a2bfdba4c405a8 0/0/0/146/167 200 852/304622 - - ---- 7/7/5/0/0 0/0 {} {||304184|} \" GET /52f5edb4a46276defe54ead2fa\
212+ e3a19fb8cafdb6 HTTP/1.1\"
213+ Nov 23 06:26:46 ip-10-1-1-1 haproxy[20128]: 10.1.1.14:48539 [23/Nov/2019:06:32:46.730] public myapp/i-03b180605be4fa176 0/0/0/171/171 200 889/124142 - - ---- 6/6/4/0/0 0/0 {} {||123704|} \" GET /ef9e0c85cc1c76d7dc777f5b19\
214+ d7cb85478496e4 HTTP/1.1\"
215+ Nov 23 06:26:47 ip-10-1-1-1 haproxy[20128]: 10.1.1.11:51847 [23/Nov/2019:06:33:47.886] public myapp/i-0aa566420409956d6 0/0/0/28/28 206 867/458 - - ---- 6/6/4/0/0 0/0 {bytes=0-0} {} \" GET /3c7ace8c683adcad375a4d14995734a\
216+ c0db08bb3 HTTP/1.1\"
217+ Nov 23 06:26:48 ip-10-1-1-1 haproxy[20128]: 10.1.1.13:35554 [23/Nov/2019:06:34:48.866] public myapp/i-07f4205f35b4774b6 0/0/0/23/49 200 816/319662 - - ---- 5/5/3/0/0 0/0 {} {||319224|} \" GET /b95db0578977cd32658fa28b386c\
218+ 0db67ab23ee7 HTTP/1.1\"
219+ Nov 23 06:26:49 ip-10-1-1-1 haproxy[20128]: 10.1.1.12:38899 [23/Nov/2019:06:35:49.879] public myapp/i-08cb5309afd22e8c0 0/0/0/59/59 200 1000/112110 - - ---- 5/5/3/0/0 0/0 {} {||111672|} \" GET /5314ca870ed0f5e48a71adca185\
220+ e4ff7f1d9d80f HTTP/1.1\"
221+ " ;
222+ let format = "%d/%b/%Y:%H:%M:%S%.f" ;
223+ let timestamps = scan_for_timestamps ( log. as_bytes ( ) , format) . unwrap ( ) ;
224+ let sparkline = build_sparkline ( & timestamps, 80 , 5 ) ;
225+ assert_eq ! (
226+ sparkline,
227+ "█ █ █ █ █ █ █ █ █ █
228+ █ █ █ █ █ █ █ █ █ █
229+ █ █ █ █ █ █ █ █ █ █
230+ █ █ █ █ █ █ █ █ █ █
231+ █▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁█"
232+ ) ;
233+ }
234+
173235 #[ test]
174236 fn build_time_markers_even ( ) {
175237 let log = "Nov 23 06:26:40 ip-10-1-1-1 haproxy[20128]: 10.1.1.10:57305 [23/Nov/2019:06:26:40.781] public myapp/i-05fa49c0e7db8c328 0/0/0/78/78 206 913/458 - - ---- 9/9/6/0/0 0/0 {bytes=0-0} {||1|bytes 0-0/499704} \" GET \
0 commit comments