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