1 /**
2  * Copyright (c) 2020, Timothy Stack
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * * Redistributions of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  * * Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  * * Neither the name of Timothy Stack nor the names of its contributors
15  * may be used to endorse or promote products derived from this software
16  * without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * @file spectro_source.cc
30  */
31 
32 #include "config.h"
33 
34 #include "base/math_util.hh"
35 #include "spectro_source.hh"
36 
list_input_handle_key(listview_curses & lv,int ch)37 bool spectrogram_source::list_input_handle_key(listview_curses &lv, int ch)
38 {
39     switch (ch) {
40         case 'm': {
41             if (this->ss_cursor_top < 0 ||
42                 (size_t) this->ss_cursor_top >= this->text_line_count() ||
43                 this->ss_cursor_column == -1 ||
44                 this->ss_value_source == nullptr) {
45                 alerter::singleton().chime();
46                 return true;
47             }
48 
49             unsigned long width;
50             vis_line_t height;
51 
52             lv.get_dimensions(height, width);
53 
54             spectrogram_bounds &sb = this->ss_cached_bounds;
55             auto begin_time_opt = this->time_for_row(this->ss_cursor_top);
56             if (!begin_time_opt) {
57                 return true;
58             }
59             auto begin_time = begin_time_opt.value();
60             struct timeval end_time = begin_time;
61 
62             end_time.tv_sec += this->ss_granularity;
63             double range_min, range_max, column_size;
64 
65             column_size = (sb.sb_max_value_out - sb.sb_min_value_out) /
66                           (double) (width - 1);
67             range_min = sb.sb_min_value_out + this->ss_cursor_column * column_size;
68             range_max = range_min + column_size + column_size * 0.01;
69             this->ss_value_source->spectro_mark((textview_curses &) lv,
70                                                 begin_time.tv_sec, end_time.tv_sec,
71                                                 range_min, range_max);
72             this->invalidate();
73             lv.reload_data();
74             return true;
75         }
76         case KEY_LEFT:
77         case KEY_RIGHT: {
78             unsigned long width;
79             vis_line_t height;
80             string_attrs_t sa;
81 
82             this->ss_cursor_top = lv.get_top();
83             lv.get_dimensions(height, width);
84 
85             this->text_attrs_for_line((textview_curses &) lv, this->ss_cursor_top, sa);
86 
87             if (sa.empty()) {
88                 this->ss_cursor_column = -1;
89                 return true;
90             }
91 
92             string_attrs_t::iterator current;
93 
94             struct line_range lr(this->ss_cursor_column, this->ss_cursor_column + 1);
95 
96             current = find_string_attr(sa, lr);
97 
98             if (current != sa.end()) {
99                 if (ch == KEY_LEFT) {
100                     if (current == sa.begin()) {
101                         current = sa.end();
102                     }
103                     else {
104                         --current;
105                     }
106                 }
107                 else {
108                     ++current;
109                 }
110             }
111 
112             if (current == sa.end()) {
113                 if (ch == KEY_LEFT) {
114                     current = sa.end();
115                     --current;
116                 }
117                 else {
118                     current = sa.begin();
119                 }
120             }
121             this->ss_cursor_column = current->sa_range.lr_start;
122 
123             lv.reload_data();
124 
125             return true;
126         }
127         default:
128             return false;
129     }
130 }
131 
132 bool
list_value_for_overlay(const listview_curses & lv,int y,int bottom,vis_line_t row,attr_line_t & value_out)133 spectrogram_source::list_value_for_overlay(const listview_curses &lv, int y,
134                                            int bottom, vis_line_t row,
135                                            attr_line_t &value_out)
136 {
137     if (y != 0) {
138         return false;
139     }
140 
141     std::string &line = value_out.get_string();
142     char buf[128];
143     vis_line_t height;
144     unsigned long width;
145 
146     lv.get_dimensions(height, width);
147 
148     this->cache_bounds();
149 
150     if (this->ss_cached_line_count == 0) {
151         value_out.with_ansi_string(
152             ANSI_ROLE("error: no log data"),
153             view_colors::VCR_ERROR);
154         return true;
155     }
156 
157     spectrogram_bounds &sb = this->ss_cached_bounds;
158     spectrogram_thresholds &st = this->ss_cached_thresholds;
159 
160     snprintf(buf, sizeof(buf), "Min: %'.10lg", sb.sb_min_value_out);
161     line = buf;
162 
163     snprintf(buf, sizeof(buf),
164              ANSI_ROLE("  ") " 1-%'d "
165              ANSI_ROLE("  ") " %'d-%'d "
166              ANSI_ROLE("  ") " %'d+",
167              view_colors::VCR_LOW_THRESHOLD,
168              st.st_green_threshold - 1,
169              view_colors::VCR_MED_THRESHOLD,
170              st.st_green_threshold,
171              st.st_yellow_threshold - 1,
172              view_colors::VCR_HIGH_THRESHOLD,
173              st.st_yellow_threshold);
174     line.append(width / 2 - strlen(buf) / 3 - line.length(), ' ');
175     line.append(buf);
176     scrub_ansi_string(line, value_out.get_attrs());
177 
178     snprintf(buf, sizeof(buf), "Max: %'.10lg", sb.sb_max_value_out);
179     line.append(width - strlen(buf) - line.length() - 2, ' ');
180     line.append(buf);
181 
182     value_out.with_attr(string_attr(
183         line_range(0, -1),
184         &view_curses::VC_STYLE,
185         A_UNDERLINE));
186 
187     return true;
188 }
189 
text_line_count()190 size_t spectrogram_source::text_line_count()
191 {
192     if (this->ss_value_source == nullptr) {
193         return 0;
194     }
195 
196     this->cache_bounds();
197 
198     return this->ss_cached_line_count;
199 }
200 
text_line_width(textview_curses & tc)201 size_t spectrogram_source::text_line_width(textview_curses &tc)
202 {
203     if (tc.get_window() == nullptr) {
204         return 80;
205     }
206 
207     unsigned long width;
208     vis_line_t height;
209 
210     tc.get_dimensions(height, width);
211     return width;
212 }
213 
time_for_row(vis_line_t row)214 nonstd::optional<struct timeval> spectrogram_source::time_for_row(vis_line_t row)
215 {
216     struct timeval retval { 0, 0 };
217 
218     this->cache_bounds();
219     retval.tv_sec =
220         rounddown(this->ss_cached_bounds.sb_begin_time, this->ss_granularity) +
221         row * this->ss_granularity;
222 
223     return retval;
224 }
225 
row_for_time(struct timeval time_bucket)226 nonstd::optional<vis_line_t> spectrogram_source::row_for_time(struct timeval time_bucket)
227 {
228     if (this->ss_value_source == nullptr) {
229         return nonstd::nullopt;
230     }
231 
232     time_t diff;
233     int retval;
234 
235     this->cache_bounds();
236     if (time_bucket.tv_sec < this->ss_cached_bounds.sb_begin_time) {
237         return 0_vl;
238     }
239 
240     diff = time_bucket.tv_sec - this->ss_cached_bounds.sb_begin_time;
241     retval = diff / this->ss_granularity;
242 
243     return vis_line_t(retval);
244 }
245 
text_value_for_line(textview_curses & tc,int row,std::string & value_out,text_sub_source::line_flags_t flags)246 void spectrogram_source::text_value_for_line(textview_curses &tc, int row,
247                                              std::string &value_out,
248                                              text_sub_source::line_flags_t flags)
249 {
250     spectrogram_row &s_row = this->load_row(tc, row);
251 
252     char tm_buffer[128];
253     struct tm tm;
254 
255     auto row_time_opt = this->time_for_row(vis_line_t(row));
256     if (!row_time_opt) {
257         value_out.clear();
258         return;
259     }
260     auto row_time = row_time_opt.value();
261 
262     gmtime_r(&row_time.tv_sec, &tm);
263     strftime(tm_buffer, sizeof(tm_buffer), " %a %b %d %H:%M:%S", &tm);
264 
265     value_out = tm_buffer;
266     value_out.resize(s_row.sr_width, ' ');
267 
268     for (size_t lpc = 0; lpc <= s_row.sr_width; lpc++) {
269         if (s_row.sr_values[lpc].rb_marks) {
270             value_out[lpc] = 'x';
271         }
272     }
273 
274     if (this->ss_cursor_top == row && this->ss_cursor_column != -1) {
275         if (value_out[this->ss_cursor_column] == 'x') {
276             value_out[this->ss_cursor_column] = '*';
277         }
278         else {
279             value_out[this->ss_cursor_column] = '+';
280         }
281     }
282 }
283 
text_attrs_for_line(textview_curses & tc,int row,string_attrs_t & value_out)284 void spectrogram_source::text_attrs_for_line(textview_curses &tc, int row,
285                                              string_attrs_t &value_out)
286 {
287     if (this->ss_value_source == nullptr) {
288         return;
289     }
290 
291     view_colors &vc = view_colors::singleton();
292     spectrogram_thresholds &st = this->ss_cached_thresholds;
293     spectrogram_row &s_row = this->load_row(tc, row);
294 
295     for (int lpc = 0; lpc <= (int) s_row.sr_width; lpc++) {
296         int col_value = s_row.sr_values[lpc].rb_counter;
297 
298         if (col_value == 0) {
299             continue;
300         }
301 
302         int color;
303 
304         if (col_value < st.st_green_threshold) {
305             color = COLOR_GREEN;
306         }
307         else if (col_value < st.st_yellow_threshold) {
308             color = COLOR_YELLOW;
309         }
310         else {
311             color = COLOR_RED;
312         }
313         value_out.emplace_back(
314             line_range(lpc, lpc + 1),
315             &view_curses::VC_STYLE,
316             vc.ansi_color_pair(COLOR_BLACK, color)
317         );
318     }
319 }
320 
cache_bounds()321 void spectrogram_source::cache_bounds()
322 {
323     if (this->ss_value_source == nullptr) {
324         this->ss_cached_bounds.sb_count = 0;
325         this->ss_cached_bounds.sb_begin_time = 0;
326         return;
327     }
328 
329     spectrogram_bounds sb;
330 
331     this->ss_value_source->spectro_bounds(sb);
332 
333     if (sb.sb_count == this->ss_cached_bounds.sb_count) {
334         return;
335     }
336 
337     this->ss_cached_bounds = sb;
338 
339     if (sb.sb_count == 0) {
340         this->ss_cached_line_count = 0;
341         return;
342     }
343 
344     time_t grain_begin_time = rounddown(sb.sb_begin_time, this->ss_granularity);
345     time_t grain_end_time = roundup_size(sb.sb_end_time, this->ss_granularity);
346 
347     time_t diff = std::max((time_t) 1, grain_end_time - grain_begin_time);
348     this->ss_cached_line_count =
349         (diff + this->ss_granularity - 1) / this->ss_granularity;
350 
351     int64_t samples_per_row = sb.sb_count / this->ss_cached_line_count;
352     spectrogram_thresholds &st = this->ss_cached_thresholds;
353 
354     st.st_yellow_threshold = samples_per_row / 2;
355     st.st_green_threshold = st.st_yellow_threshold / 2;
356 
357     if (st.st_green_threshold <= 1) {
358         st.st_green_threshold = 2;
359     }
360     if (st.st_yellow_threshold <= st.st_green_threshold) {
361         st.st_yellow_threshold = st.st_green_threshold + 1;
362     }
363 }
364 
load_row(textview_curses & tc,int row)365 spectrogram_row &spectrogram_source::load_row(textview_curses &tc, int row)
366 {
367     this->cache_bounds();
368 
369     unsigned long width;
370     vis_line_t height;
371 
372     tc.get_dimensions(height, width);
373     width -= 2;
374 
375     spectrogram_bounds &sb = this->ss_cached_bounds;
376     spectrogram_request sr(sb);
377     time_t row_time;
378 
379     sr.sr_width = width;
380     row_time = rounddown(sb.sb_begin_time, this->ss_granularity) +
381                row * this->ss_granularity;
382     sr.sr_begin_time = row_time;
383     sr.sr_end_time = row_time + this->ss_granularity;
384 
385     sr.sr_column_size = (sb.sb_max_value_out - sb.sb_min_value_out) /
386                         (double) (width - 1);
387 
388     spectrogram_row &s_row = this->ss_row_cache[row_time];
389 
390     if (s_row.sr_values == nullptr ||
391         s_row.sr_width != width ||
392         s_row.sr_column_size != sr.sr_column_size) {
393         s_row.sr_width = width;
394         s_row.sr_column_size = sr.sr_column_size;
395         delete[] s_row.sr_values;
396         s_row.sr_values = new spectrogram_row::row_bucket[width + 1];
397         this->ss_value_source->spectro_row(sr, s_row);
398     }
399 
400     return s_row;
401 }
402