1mod time_marker;
2mod timestamp_finder;
3
4use anyhow::Result;
5use std::io::{prelude::*, BufReader};
6
7use crate::timestamp_finder::TimestampFinder;
8
9const SPARKS: &[&str] = &["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
10
11pub fn build_sparkline(timestamps: &[i64], width: usize, height: usize) -> String {
12 let timestamp_frequencies = timestamp_frequency_distribution(timestamps, width);
13 let (min, max) = (
14 *timestamp_frequencies.iter().min().unwrap() as f64,
15 *timestamp_frequencies.iter().max().unwrap() as f64,
16 );
17 let mut canvas = vec![vec![" "; width]; height];
18 let slots_per_line = SPARKS.len();
19
20 timestamp_frequencies
21 .iter()
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 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()
48}
49
50pub fn scan_for_timestamps<R>(reader: R, format: &str) -> Result<Vec<i64>>
51where
52 R: Read,
53{
54 let date_finder = TimestampFinder::new(format)?;
55 let timestamps = BufReader::new(reader)
56 .lines()
57 .map_while(Result::ok)
58 .filter_map(|line| date_finder.find_timestamp(&line))
59 .collect();
60 Ok(timestamps)
61}
62
63pub fn build_time_markers(
64 timestamps: &[i64],
65 marker_count: usize,
66 terminal_width: usize,
67) -> (String, String) {
68 if marker_count < 2 || timestamps.len() < 2 {
69 return (String::from(""), String::from(""));
70 }
71
72 let mut footer_marker_count = marker_count / 2;
73 if footer_marker_count % 2 != 0 {
74 footer_marker_count += 1;
75 }
76
77 let marker_timestamp_offsets: Vec<usize> = (0..marker_count)
78 .map(|i| (i as f64 * timestamps.len() as f64 / (marker_count - 1) as f64).ceil() as usize)
79 .collect();
80 let header_timestamp_offsets = marker_timestamp_offsets[footer_marker_count..].to_vec();
81 let footer_timestamp_offsets = marker_timestamp_offsets[..footer_marker_count].to_vec();
82
83 let marker_terminal_offsets = marker_offsets(marker_count, terminal_width);
84 let header_terminal_offsets = marker_terminal_offsets[footer_marker_count..].to_vec();
85 let footer_terminal_offsets = marker_terminal_offsets[..footer_marker_count].to_vec();
86
87 let mut header_canvas =
88 time_marker::Canvas::new(terminal_width, header_timestamp_offsets.len() + 1);
89 let mut footer_canvas =
90 time_marker::Canvas::new(terminal_width, footer_timestamp_offsets.len() + 1);
91
92 header_timestamp_offsets
93 .iter()
94 .enumerate()
95 .map(|(index, timestamp_index)| time_marker::TimeMarker {
96 horizontal_offset: header_terminal_offsets[index],
97 timestamp: timestamps[*timestamp_index - 1],
98 timestamp_location: time_marker::TimestampLocation::Top,
99 vertical_offset: index + 1,
100 })
101 .for_each(|time_marker| {
102 if let Err(e) = time_marker.render(&mut header_canvas) {
103 eprintln!("couldn't render time marker: {}", e);
104 }
105 });
106
107 footer_timestamp_offsets
108 .iter()
109 .enumerate()
110 .map(|(index, timestamp_index)| time_marker::TimeMarker {
111 horizontal_offset: footer_terminal_offsets[index],
112 timestamp: timestamps[*timestamp_index],
113 timestamp_location: time_marker::TimestampLocation::Bottom,
114 vertical_offset: footer_timestamp_offsets.len() - index,
115 })
116 .for_each(|time_marker| {
117 if let Err(e) = time_marker.render(&mut footer_canvas) {
118 eprintln!("couldn't render time marker: {}", e);
119 }
120 });
121
122 (format!("{}", header_canvas), format!("{}", footer_canvas))
123}
124
125fn marker_offsets(count: usize, terminal_width: usize) -> Vec<usize> {
126 let mut offsets = vec![0];
128
129 let skip = (terminal_width - 2) as f64 / (count - 1) as f64;
131 let mut current_offset = skip;
132 (0..(count - 2)).for_each(|_| {
133 offsets.push(current_offset.ceil() as usize % terminal_width);
134 current_offset += skip;
135 });
136
137 offsets.push(terminal_width - 1);
139
140 offsets
141}
142
143fn timestamp_frequency_distribution(timestamps: &[i64], bucket_count: usize) -> Vec<usize> {
144 let first_timestamp = timestamps.iter().min().unwrap();
145 let last_timestamp = timestamps.iter().max().unwrap();
146 let duration_seconds = last_timestamp - first_timestamp;
147 let seconds_per_bucket = duration_seconds as f64 / bucket_count as f64;
148
149 let mut timestamps_per_bucket = vec![0; bucket_count];
150 for timestamp in timestamps {
151 let bucket_index = usize::min(
152 ((timestamp - first_timestamp) as f64 / seconds_per_bucket) as usize,
153 bucket_count - 1,
154 );
155 timestamps_per_bucket[bucket_index] += 1;
156 }
157
158 timestamps_per_bucket
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn build_sparkline_with_one_line() {
167 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 \
168 /2518cb13a48bdf53b2f936f44e7042a3cc7baa06 HTTP/1.1\"
169Nov 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\
170ce8bd70bc6e7 HTTP/1.1\"
171Nov 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\
1727ce0e21ef1a01 HTTP/1.1\"
173Nov 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\
17422554f6d HTTP/1.1\"
175Nov 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\
176e0ceb71e62b5 HTTP/1.1\"
177Nov 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\
178e3a19fb8cafdb6 HTTP/1.1\"
179Nov 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\
180d7cb85478496e4 HTTP/1.1\"
181Nov 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\
182c0db08bb3 HTTP/1.1\"
183Nov 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\
1840db67ab23ee7 HTTP/1.1\"
185Nov 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\
186e4ff7f1d9d80f HTTP/1.1\"
187";
188 let format = "%d/%b/%Y:%H:%M:%S%.f";
189 let timestamps = scan_for_timestamps(log.as_bytes(), format).unwrap();
190 let sparkline = build_sparkline(×tamps, 80, 1);
191 assert_eq!(
192 sparkline,
193 "█▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁█"
194 );
195 }
196
197 #[test]
198 fn build_sparkline_with_multiple_lines() {
199 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 \
200 /2518cb13a48bdf53b2f936f44e7042a3cc7baa06 HTTP/1.1\"
201Nov 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\
202ce8bd70bc6e7 HTTP/1.1\"
203Nov 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\
2047ce0e21ef1a01 HTTP/1.1\"
205Nov 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\
20622554f6d HTTP/1.1\"
207Nov 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\
208e0ceb71e62b5 HTTP/1.1\"
209Nov 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\
210e3a19fb8cafdb6 HTTP/1.1\"
211Nov 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\
212d7cb85478496e4 HTTP/1.1\"
213Nov 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\
214c0db08bb3 HTTP/1.1\"
215Nov 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\
2160db67ab23ee7 HTTP/1.1\"
217Nov 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\
218e4ff7f1d9d80f HTTP/1.1\"
219";
220 let format = "%d/%b/%Y:%H:%M:%S%.f";
221 let timestamps = scan_for_timestamps(log.as_bytes(), format).unwrap();
222 let sparkline = build_sparkline(×tamps, 80, 5);
223 assert_eq!(
224 sparkline,
225 "█ █ █ █ █ █ █ █ █ █
226█ █ █ █ █ █ █ █ █ █
227█ █ █ █ █ █ █ █ █ █
228█ █ █ █ █ █ █ █ █ █
229█▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁█"
230 );
231 }
232
233 #[test]
234 fn build_time_markers_even() {
235 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 \
236 /2518cb13a48bdf53b2f936f44e7042a3cc7baa06 HTTP/1.1\"
237Nov 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\
238ce8bd70bc6e7 HTTP/1.1\"
239Nov 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\
2407ce0e21ef1a01 HTTP/1.1\"
241Nov 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\
24222554f6d HTTP/1.1\"
243Nov 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\
244e0ceb71e62b5 HTTP/1.1\"
245Nov 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\
246e3a19fb8cafdb6 HTTP/1.1\"
247Nov 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\
248d7cb85478496e4 HTTP/1.1\"
249Nov 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\
250c0db08bb3 HTTP/1.1\"
251Nov 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\
2520db67ab23ee7 HTTP/1.1\"
253Nov 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\
254e4ff7f1d9d80f HTTP/1.1\"
255";
256 let format = "%d/%b/%Y:%H:%M:%S%.f";
257 let timestamps = scan_for_timestamps(log.as_bytes(), format).unwrap();
258 let (header, footer) = build_time_markers(×tamps, 8, 80);
259 assert_eq!(
260 header,
261 " 2019-11-23 06:35:49
262 2019-11-23 06:34:48 |
263 2019-11-23 06:33:47 | |
264 2019-11-23 06:31:45 | | |
265 | | | |
266"
267 );
268 assert_eq!(
269 footer,
270 "| | | |
271| | | 2019-11-23 06:31:45
272| | 2019-11-23 06:29:43
273| 2019-11-23 06:28:42
2742019-11-23 06:26:40
275"
276 );
277 }
278
279 #[test]
280 fn build_time_markers_odd() {
281 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 \
282 /2518cb13a48bdf53b2f936f44e7042a3cc7baa06 HTTP/1.1\"
283Nov 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\
284ce8bd70bc6e7 HTTP/1.1\"
285Nov 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\
2867ce0e21ef1a01 HTTP/1.1\"
287Nov 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\
28822554f6d HTTP/1.1\"
289Nov 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\
290e0ceb71e62b5 HTTP/1.1\"
291Nov 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\
292e3a19fb8cafdb6 HTTP/1.1\"
293Nov 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\
294d7cb85478496e4 HTTP/1.1\"
295Nov 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\
296c0db08bb3 HTTP/1.1\"
297Nov 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\
2980db67ab23ee7 HTTP/1.1\"
299Nov 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\
300e4ff7f1d9d80f HTTP/1.1\"
301";
302 let format = "%d/%b/%Y:%H:%M:%S%.f";
303 let timestamps = scan_for_timestamps(log.as_bytes(), format).unwrap();
304 let (header, footer) = build_time_markers(×tamps, 15, 80);
305 assert_eq!(
306 header,
307 " 2019-11-23 06:35:49
308 2019-11-23 06:35:49 |
309 2019-11-23 06:34:48 | |
310 2019-11-23 06:33:47 | | |
311 2019-11-23 06:33:47 | | | |
312 2019-11-23 06:32:46 | | | | |
313 2019-11-23 06:31:45 | | | | | |
314 | | | | | | |
315"
316 );
317 assert_eq!(
318 footer,
319 "| | | | | | | |
320| | | | | | | 2019-11-23 06:31:45
321| | | | | | 2019-11-23 06:31:45
322| | | | | 2019-11-23 06:30:44
323| | | | 2019-11-23 06:29:43
324| | | 2019-11-23 06:29:43
325| | 2019-11-23 06:28:42
326| 2019-11-23 06:27:41
3272019-11-23 06:26:40
328"
329 );
330 }
331
332 #[test]
333 fn build_time_markers_too_few_timestamps() {
334 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 \
335 /2518cb13a48bdf53b2f936f44e7042a3cc7baa06 HTTP/1.1\"\n";
336 let format = "%d/%b/%Y:%H:%M:%S%.f";
337 let timestamps = scan_for_timestamps(log.as_bytes(), format).unwrap();
338 let (header, footer) = build_time_markers(×tamps, 8, 80);
339 assert_eq!(header, "");
340 assert_eq!(footer, "");
341 }
342
343 #[test]
344 fn scan_for_timestamps_() {
345 let log = "Nov 23 06:26:40 ip-10-1-26-81 haproxy[20128]: 54.242.135.245:57305 [23/Nov/2019:06:26:40.781] public repackager/i-05fa49c0e7db8c328 0/0/0/78/78 206 913/458 - - ---- 9/9/6/0/0 0/0 {1.1 v1-akamaitech.net(ghost) (AkamaiGHost), 1.1 v1-akamaitech.net(ghost) (AkamaiGHost), 1.1 akamai.n|bytes=0-0} {||1|bytes 0-0/499704} \"GET /deliveries/2518cb13a48bdf53b2f936f44e7042a3cc7baa06.m3u8/seg-88-v1-a1.ts HTTP/1.1\"
346Nov 23 14:21:53 ip-10-1-26-81 haproxy[20128]: 54.209.125.72:58030 [23/Nov/2019:14:21:53.241] public repackager/i-0728dc03214405429 0/0/0/246/246 200 810/8324 - - ---- 17/17/12/0/0 0/0 {1.1 v1-akamaitech.net(ghost) (AkamaiGHost), 1.1 v1-akamaitech.net(ghost) (AkamaiGHost), 1.1 akamai.n|} {||7870|} \"GET /deliveries/4fb7b6ff75f8a13da4ac482e25e29790105ba075.m3u8?origin_v2=1 HTTP/1.1\"
347";
348 let format = "%d/%b/%Y:%H:%M:%S%.f";
349 let timestamps = scan_for_timestamps(log.as_bytes(), format).unwrap();
350 assert_eq!(timestamps, [1574490400, 1574518913]);
351 }
352
353 #[test]
354 fn timestamp_frequency_distribution_() {
355 let timestamps = vec![1, 2, 3, 4, 5];
356 let bins = timestamp_frequency_distribution(×tamps, 5);
357 assert_eq!(bins, [1, 1, 1, 1, 1]);
358
359 let timestamps = vec![1, 2, 3, 4, 5, 6];
360 let bins = timestamp_frequency_distribution(×tamps, 3);
361 assert_eq!(bins, [2, 2, 2]);
362 }
363
364 #[test]
365 fn marker_offsets_() {
366 assert_eq![marker_offsets(2, 2), vec![0, 1]];
367 assert_eq![marker_offsets(2, 5), vec![0, 4]];
368 assert_eq!(marker_offsets(5, 10), vec![0, 2, 4, 6, 9]);
369 assert_eq!(marker_offsets(3, 5), vec![0, 2, 4]);
370 }
371}