1 /**
2  * Copyright (c) 2014, 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 
30 #include "config.h"
31 
32 #include <regex>
33 
34 #include "base/date_time_scanner.hh"
35 #include "base/time_util.hh"
36 
37 #include "yajlpp/json_ptr.hh"
38 #include "db_sub_source.hh"
39 
40 const char *db_label_source::NULL_STR = "<NULL>";
41 
42 constexpr size_t MAX_COLUMN_WIDTH = 120;
43 
text_value_for_line(textview_curses & tc,int row,std::string & label_out,text_sub_source::line_flags_t flags)44 void db_label_source::text_value_for_line(textview_curses &tc, int row,
45                                           std::string &label_out,
46                                           text_sub_source::line_flags_t flags)
47 {
48     static const std::string TAB_SYMBOL = "\u21e5";
49     static const std::string LF_SYMBOL = "\u240a";
50     static const std::string CR_SYMBOL = "\u240d";
51 
52     /*
53      * start_value is the result rowid, each bucket type is a column value
54      * label_out should be the raw text output.
55      */
56 
57     label_out.clear();
58     if (row >= (int)this->dls_rows.size()) {
59         return;
60     }
61     for (int lpc = 0; lpc < (int)this->dls_rows[row].size(); lpc++) {
62         auto actual_col_size = std::min(MAX_COLUMN_WIDTH,
63                                         this->dls_headers[lpc].hm_column_size);
64         auto raw_cell_str = std::string(this->dls_rows[row][lpc]);
65         std::string cell_str;
66 
67         for (const auto ch : raw_cell_str) {
68             switch (ch) {
69                 case '\t':
70                     cell_str.append(TAB_SYMBOL);
71                     break;
72                 case '\n':
73                     cell_str.append(LF_SYMBOL);
74                     break;
75                 case '\r':
76                     cell_str.append(CR_SYMBOL);
77                     break;
78                 default:
79                     cell_str.append(1, ch);
80                     break;
81             }
82         }
83 
84         truncate_to(cell_str, MAX_COLUMN_WIDTH);
85 
86         auto cell_length = utf8_string_length(cell_str)
87             .unwrapOr(actual_col_size);
88         auto padding = actual_col_size - cell_length;
89         this->dls_cell_width[lpc] = cell_str.length() + padding;
90         if (this->dls_headers[lpc].hm_column_type != SQLITE3_TEXT) {
91             label_out.append(padding, ' ');
92         }
93         label_out.append(cell_str);
94         if (this->dls_headers[lpc].hm_column_type == SQLITE3_TEXT) {
95             label_out.append(padding, ' ');
96         }
97         label_out.append(1, ' ');
98     }
99 }
100 
text_attrs_for_line(textview_curses & tc,int row,string_attrs_t & sa)101 void db_label_source::text_attrs_for_line(textview_curses &tc, int row,
102                                           string_attrs_t &sa)
103 {
104     struct line_range lr(0, 0);
105     struct line_range lr2(0, -1);
106 
107     if (row >= (int)this->dls_rows.size()) {
108         return;
109     }
110     for (size_t lpc = 0; lpc < this->dls_headers.size() - 1; lpc++) {
111         if (row % 2 == 0) {
112             sa.emplace_back(lr2, &view_curses::VC_STYLE, A_BOLD);
113         }
114         lr.lr_start += this->dls_cell_width[lpc];
115         lr.lr_end = lr.lr_start + 1;
116         sa.emplace_back(lr, &view_curses::VC_GRAPHIC, ACS_VLINE);
117         lr.lr_start += 1;
118     }
119 
120     int left = 0;
121     for (size_t lpc = 0; lpc < this->dls_headers.size(); lpc++) {
122         const char *row_value = this->dls_rows[row][lpc];
123         size_t row_len = strlen(row_value);
124 
125         if (this->dls_headers[lpc].hm_graphable) {
126             double num_value;
127 
128             if (sscanf(row_value, "%lf", &num_value) == 1) {
129                 this->dls_chart.chart_attrs_for_value(tc, left, this->dls_headers[lpc].hm_name, num_value, sa);
130             }
131         }
132         if (row_len > 2 && row_len < MAX_COLUMN_WIDTH &&
133             ((row_value[0] == '{' && row_value[row_len - 1] == '}') ||
134              (row_value[0] == '[' && row_value[row_len - 1] == ']'))) {
135             json_ptr_walk jpw;
136 
137             if (jpw.parse(row_value, row_len) == yajl_status_ok &&
138                 jpw.complete_parse() == yajl_status_ok) {
139                 for (auto &jpw_value : jpw.jpw_values) {
140                     double num_value;
141 
142                     if (jpw_value.wt_type == yajl_t_number &&
143                         sscanf(jpw_value.wt_value.c_str(), "%lf", &num_value) == 1) {
144                         this->dls_chart.chart_attrs_for_value(tc, left,
145                                                               jpw_value.wt_ptr, num_value, sa);
146                     }
147                 }
148             }
149         }
150     }
151 }
152 
push_header(const std::string & colstr,int type,bool graphable)153 void db_label_source::push_header(const std::string &colstr, int type,
154                                   bool graphable)
155 {
156     this->dls_headers.emplace_back(colstr);
157     this->dls_cell_width.push_back(0);
158 
159     header_meta &hm = this->dls_headers.back();
160 
161     hm.hm_column_size = utf8_string_length(colstr).unwrapOr(colstr.length());
162     hm.hm_column_type = type;
163     hm.hm_graphable = graphable;
164     if (colstr == "log_time") {
165         this->dls_time_column_index = this->dls_headers.size() - 1;
166     }
167 }
168 
push_column(const char * colstr)169 void db_label_source::push_column(const char *colstr)
170 {
171     view_colors &vc = view_colors::singleton();
172     int index = this->dls_rows.back().size();
173     double num_value = 0.0;
174     size_t value_len;
175 
176     if (colstr == nullptr) {
177         colstr = NULL_STR;
178     }
179     else {
180         colstr = strdup(colstr);
181         if (colstr == nullptr) {
182             throw "out of memory";
183         }
184     }
185     value_len = strlen(colstr);
186 
187     if (index == this->dls_time_column_index) {
188         date_time_scanner dts;
189         struct timeval tv;
190 
191         if (!dts.convert_to_timeval(colstr, -1, nullptr, tv)) {
192             tv.tv_sec = -1;
193             tv.tv_usec = -1;
194         }
195         if (!this->dls_time_column.empty() && tv < this->dls_time_column.back()) {
196             this->dls_time_column_index = -1;
197             this->dls_time_column.clear();
198         }
199         else {
200             this->dls_time_column.push_back(tv);
201         }
202     }
203 
204     this->dls_rows.back().push_back(colstr);
205     this->dls_headers[index].hm_column_size =
206         std::max(this->dls_headers[index].hm_column_size,
207                  utf8_string_length(colstr, value_len).unwrapOr(value_len));
208 
209     if (colstr != nullptr && this->dls_headers[index].hm_graphable) {
210         if (sscanf(colstr, "%lf", &num_value) != 1) {
211             num_value = 0.0;
212         }
213         this->dls_chart.add_value(this->dls_headers[index].hm_name, num_value);
214     }
215     else if (value_len > 2 &&
216              ((colstr[0] == '{' && colstr[value_len - 1] == '}') ||
217               (colstr[0] == '[' && colstr[value_len - 1] == ']'))) {
218         json_ptr_walk jpw;
219 
220         if (jpw.parse(colstr, value_len) == yajl_status_ok &&
221             jpw.complete_parse() == yajl_status_ok) {
222             for (auto &jpw_value : jpw.jpw_values) {
223                 if (jpw_value.wt_type == yajl_t_number &&
224                     sscanf(jpw_value.wt_value.c_str(), "%lf", &num_value) == 1) {
225                     this->dls_chart.add_value(jpw_value.wt_ptr, num_value);
226                     this->dls_chart.with_attrs_for_ident(
227                         jpw_value.wt_ptr, vc.attrs_for_ident(jpw_value.wt_ptr));
228                 }
229             }
230         }
231     }
232 }
233 
clear()234 void db_label_source::clear()
235 {
236     this->dls_chart.clear();
237     this->dls_headers.clear();
238     for (size_t row = 0; row < this->dls_rows.size(); row++) {
239         for (size_t col = 0; col < this->dls_rows[row].size(); col++) {
240             if (this->dls_rows[row][col] != NULL_STR) {
241                 free((void *)this->dls_rows[row][col]);
242             }
243         }
244     }
245     this->dls_rows.clear();
246     this->dls_time_column.clear();
247     this->dls_cell_width.clear();
248 }
249 
column_name_to_index(const std::string & name) const250 long db_label_source::column_name_to_index(const std::string &name) const
251 {
252     std::vector<header_meta>::const_iterator iter;
253 
254     iter = std::find(this->dls_headers.begin(),
255                      this->dls_headers.end(),
256                      name);
257     if (iter == this->dls_headers.end()) {
258         return -1;
259     }
260 
261     return std::distance(this->dls_headers.begin(), iter);
262 }
263 
row_for_time(struct timeval time_bucket)264 nonstd::optional<vis_line_t> db_label_source::row_for_time(struct timeval time_bucket)
265 {
266     std::vector<struct timeval>::iterator iter;
267 
268     iter = std::lower_bound(this->dls_time_column.begin(),
269                             this->dls_time_column.end(),
270                             time_bucket);
271     if (iter != this->dls_time_column.end()) {
272         return vis_line_t(std::distance(this->dls_time_column.begin(), iter));
273     }
274     return nonstd::nullopt;
275 }
276 
list_overlay_count(const listview_curses & lv)277 size_t db_overlay_source::list_overlay_count(const listview_curses &lv)
278 {
279     size_t retval = 1;
280 
281     if (!this->dos_active || lv.get_inner_height() == 0) {
282         this->dos_lines.clear();
283 
284         return retval;
285     }
286 
287     view_colors &vc = view_colors::singleton();
288     vis_line_t top = lv.get_top();
289     const std::vector<const char *> &cols = this->dos_labels->dls_rows[top];
290     unsigned long width;
291     vis_line_t height;
292 
293     lv.get_dimensions(height, width);
294 
295     this->dos_lines.clear();
296     for (size_t col = 0; col < cols.size(); col++) {
297         const char *col_value = cols[col];
298         size_t col_len = strlen(col_value);
299 
300         if (!(col_len >= 2 &&
301               ((col_value[0] == '{' && col_value[col_len - 1] == '}') ||
302                (col_value[0] == '[' && col_value[col_len - 1] == ']')))) {
303             continue;
304         }
305 
306         json_ptr_walk jpw;
307 
308         if (jpw.parse(col_value, col_len) == yajl_status_ok &&
309             jpw.complete_parse() == yajl_status_ok) {
310 
311             {
312                 const std::string &header = this->dos_labels->dls_headers[col].hm_name;
313                 this->dos_lines.emplace_back(" JSON Column: " + header);
314 
315                 retval += 1;
316             }
317 
318             stacked_bar_chart<std::string> chart;
319             int start_line = this->dos_lines.size();
320 
321             chart.with_stacking_enabled(false)
322                 .with_margins(3, 0);
323 
324             for (auto &jpw_value : jpw.jpw_values) {
325                 this->dos_lines.emplace_back("   " + jpw_value.wt_ptr + " = " +
326                                           jpw_value.wt_value);
327 
328                 string_attrs_t &sa = this->dos_lines.back().get_attrs();
329                 struct line_range lr(1, 2);
330 
331                 sa.emplace_back(lr, &view_curses::VC_GRAPHIC, ACS_LTEE);
332                 lr.lr_start = 3 + jpw_value.wt_ptr.size() + 3;
333                 lr.lr_end = -1;
334                 sa.emplace_back(lr, &view_curses::VC_STYLE, A_BOLD);
335 
336                 double num_value = 0.0;
337 
338                 if (jpw_value.wt_type == yajl_t_number &&
339                     sscanf(jpw_value.wt_value.c_str(), "%lf", &num_value) == 1) {
340                     int attrs = vc.attrs_for_ident(jpw_value.wt_ptr);
341 
342                     chart.add_value(jpw_value.wt_ptr, num_value);
343                     chart.with_attrs_for_ident(jpw_value.wt_ptr, attrs);
344                 }
345 
346                 retval += 1;
347             }
348 
349             int curr_line = start_line;
350             for (auto iter = jpw.jpw_values.begin();
351                  iter != jpw.jpw_values.end();
352                  ++iter, curr_line++) {
353                 double num_value = 0.0;
354 
355                 if (iter->wt_type == yajl_t_number &&
356                     sscanf(iter->wt_value.c_str(), "%lf", &num_value) == 1) {
357                     string_attrs_t &sa = this->dos_lines[curr_line].get_attrs();
358                     int left = 3;
359 
360                     chart.chart_attrs_for_value(lv, left, iter->wt_ptr, num_value, sa);
361                 }
362             }
363         }
364     }
365 
366     if (retval > 1) {
367         this->dos_lines.emplace_back("");
368 
369         string_attrs_t &sa = this->dos_lines.back().get_attrs();
370         struct line_range lr(1, 2);
371 
372         sa.emplace_back(lr, &view_curses::VC_GRAPHIC, ACS_LLCORNER);
373         lr.lr_start = 2;
374         lr.lr_end = -1;
375         sa.emplace_back(lr, &view_curses::VC_GRAPHIC, ACS_HLINE);
376 
377         retval += 1;
378     }
379 
380     return retval;
381 }
382 
list_value_for_overlay(const listview_curses & lv,int y,int bottom,vis_line_t row,attr_line_t & value_out)383 bool db_overlay_source::list_value_for_overlay(const listview_curses &lv, int y,
384                                                int bottom, vis_line_t row,
385                                                attr_line_t &value_out)
386 {
387     view_colors &vc = view_colors::singleton();
388 
389     if (y == 0) {
390         this->list_overlay_count(lv);
391         std::string &line = value_out.get_string();
392         db_label_source *dls = this->dos_labels;
393         string_attrs_t &sa = value_out.get_attrs();
394 
395         for (size_t lpc = 0;
396              lpc < this->dos_labels->dls_headers.size();
397              lpc++) {
398             auto actual_col_size = std::min(
399                 MAX_COLUMN_WIDTH, dls->dls_headers[lpc].hm_column_size);
400             std::string cell_title = dls->dls_headers[lpc].hm_name;
401 
402             truncate_to(cell_title, MAX_COLUMN_WIDTH);
403 
404             auto cell_length = utf8_string_length(cell_title)
405                 .unwrapOr(actual_col_size);
406             int before, total_fill = actual_col_size - cell_length;
407             auto line_len_before = line.length();
408 
409             before = total_fill / 2;
410             total_fill -= before;
411             line.append(before, ' ');
412             line.append(cell_title);
413             line.append(total_fill, ' ');
414             line.append(1, ' ');
415 
416             struct line_range header_range(line_len_before, line.length());
417 
418             int attrs =
419                 vc.attrs_for_ident(dls->dls_headers[lpc].hm_name) | A_REVERSE;
420             if (!this->dos_labels->dls_headers[lpc].hm_graphable) {
421                 attrs = A_UNDERLINE;
422             }
423             sa.emplace_back(header_range, &view_curses::VC_STYLE, attrs);
424         }
425 
426         struct line_range lr(0);
427 
428         sa.emplace_back(lr, &view_curses::VC_STYLE, A_BOLD | A_UNDERLINE);
429         return true;
430     }
431     else if (this->dos_active && y >= 2 && ((size_t) y) < (this->dos_lines.size() + 2)) {
432         value_out = this->dos_lines[y - 2];
433         return true;
434     }
435 
436     return false;
437 }
438