diff --git a/NEWS.md b/NEWS.md index bb14c3f6..574bb597 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,11 @@ ## lnav v0.12.0 Features: +* Added a Gantt Chart view to visualize operations over time + based on the "opid" in log messages. The view shows + the operation IDs, a description of the operation captured + from log messages, and a bar representing the period of + time that the operation was running. * Added the `:sh` command and `-e` option to execute a shell command-line and display its output within **lnav**. The captured output will be displayed in the TEXT view. The diff --git a/docs/schemas/format-v1.schema.json b/docs/schemas/format-v1.schema.json index 9e75876a..1b9b32d5 100644 --- a/docs/schemas/format-v1.schema.json +++ b/docs/schemas/format-v1.schema.json @@ -199,6 +199,53 @@ "description": "The name of the operation-id field in the log message pattern", "type": "string" }, + "opid": { + "title": "//opid", + "type": "object", + "properties": { + "description": { + "title": "//opid/description", + "type": "object", + "patternProperties": { + "([\\w\\.\\-]+)": { + "title": "//opid/description/", + "type": "object", + "properties": { + "format": { + "title": "//opid/description//format", + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "title": "//opid/description//format/field", + "type": "string" + }, + "extractor": { + "title": "//opid/description//format/extractor", + "type": "string" + }, + "prefix": { + "title": "//opid/description//format/prefix", + "type": "string" + }, + "suffix": { + "title": "//opid/description//format/suffix", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, "ordered-by-time": { "title": "//ordered-by-time", "description": "Indicates that the order of messages in the file is time-based.", diff --git a/docs/source/config.rst b/docs/source/config.rst index 3e103ba4..13b018bf 100644 --- a/docs/source/config.rst +++ b/docs/source/config.rst @@ -272,7 +272,7 @@ or interrupting the viewing experience. An annotation is defined by a condition and a handler in the **lnav** configuration. The condition is tested against a log message to determine if the annotation is applicable. If it is, the handler script will be executed for that log message when -the user runs the :ref:`:annotation` command. +the user runs the :ref:`:annotate` command. Conditions are SQLite expressions like the ones passed to :ref:`:filter-expr` where the expression is appended to diff --git a/docs/source/ui.rst b/docs/source/ui.rst index fc0a5801..e6ca1667 100644 --- a/docs/source/ui.rst +++ b/docs/source/ui.rst @@ -296,6 +296,34 @@ can also press :kbd:`Shift` + :kbd:`i` to toggle the histogram view while synchronizing the top time. While in the histogram view, pressing :kbd:`z` / :kbd:`Shift` + :kbd:`z` will zoom in/out. +GANTT +^^^^^ + +.. note:: This feature is available in v0.12.0+. + +The Gantt Chart view visualizes operations over time. The operations +are identified by the "opid" field defined in the log format. In the +view, there is a header that shows the overall time span, the +narrowed time span around the focused line, and the column headers. +Each row in the view shows the following: + +* The duration of the operation +* Sparklines showing the number of errors and warnings relative to the + total number of messages associated with the OPID. +* The OPID itself. +* A description of the operation as captured from the log messages. + +The rows are sorted by the start time of each operation. + +If an operation row is in the focused time span, a reverse-video +bar will show when the operation started and finished (unless it +extends outside the time span). As you move the focused line, the +focused time span will be adjusted to keep the preceding and following +five operations within the span. + +The preview panel at the bottom of the display will show the +messages associated with the operation. + PRETTY ^^^^^^ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 25784cae..2ffc1362 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -364,6 +364,7 @@ add_library( fs-extension-functions.cc fstat_vtab.cc fts_fuzzy_match.cc + gantt_source.cc help_text.cc help_text_formatter.cc highlighter.cc @@ -477,6 +478,7 @@ add_library( filter_sub_source.hh fstat_vtab.hh fts_fuzzy_match.hh + gantt_source.hh grep_highlighter.hh hasher.hh help_text.hh diff --git a/src/Makefile.am b/src/Makefile.am index b0e482d9..8aac6168 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -214,6 +214,7 @@ noinst_HEADERS = \ filter_sub_source.hh \ fstat_vtab.hh \ fts_fuzzy_match.hh \ + gantt_source.hh \ grep_highlighter.hh \ grep_proc.hh \ hasher.hh \ @@ -409,6 +410,7 @@ libdiag_a_SOURCES = \ fstat_vtab.cc \ fs-extension-functions.cc \ fts_fuzzy_match.cc \ + gantt_source.cc \ grep_proc.cc \ help_text.cc \ help_text_formatter.cc \ diff --git a/src/base/string_attr_type.hh b/src/base/string_attr_type.hh index f1a1ee95..09359e3d 100644 --- a/src/base/string_attr_type.hh +++ b/src/base/string_attr_type.hh @@ -518,6 +518,13 @@ inline std::pair operator"" _error( VC_ROLE.template value(role_t::VCR_ERROR)); } +inline std::pair operator"" _warning( + const char* str, std::size_t len) +{ + return std::make_pair(std::string(str, len), + VC_ROLE.template value(role_t::VCR_WARNING)); +} + inline std::pair operator"" _info( const char* str, std::size_t len) { diff --git a/src/base/time_util.hh b/src/base/time_util.hh index ef9687f3..0c4aa00f 100644 --- a/src/base/time_util.hh +++ b/src/base/time_util.hh @@ -138,6 +138,13 @@ operator<(const struct timeval& left, const struct timeval& right) || ((left.tv_sec == right.tv_sec) && (left.tv_usec < right.tv_usec)); } +inline bool +operator<=(const struct timeval& left, const struct timeval& right) +{ + return left.tv_sec <= right.tv_sec + || ((left.tv_sec == right.tv_sec) && (left.tv_usec <= right.tv_usec)); +} + inline bool operator!=(const struct timeval& left, const struct timeval& right) { diff --git a/src/command_executor.cc b/src/command_executor.cc index 07e622b7..a7b82c98 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -370,9 +370,13 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) switch (retcode) { case SQLITE_OK: - case SQLITE_DONE: + case SQLITE_DONE: { + auto changes = sqlite3_changes(lnav_data.ld_db.in()); + + log_info("sqlite3_changes() -> %d", changes); done = true; break; + } case SQLITE_ROW: ec.ec_sql_callback(ec, stmt.in()); diff --git a/src/db_sub_source.cc b/src/db_sub_source.cc index fd7d4631..d4e1bc7d 100644 --- a/src/db_sub_source.cc +++ b/src/db_sub_source.cc @@ -442,7 +442,7 @@ db_overlay_source::list_static_overlay(const listview_curses& lv, } else { attrs.ta_attrs = A_UNDERLINE; } - sa.emplace_back(header_range, VC_STYLE.value(text_attrs{attrs})); + sa.emplace_back(header_range, VC_STYLE.value(attrs)); } struct line_range lr(0); diff --git a/src/formats/vmw_log.json b/src/formats/vmw_log.json index c5c5f7b2..7b88a22c 100644 --- a/src/formats/vmw_log.json +++ b/src/formats/vmw_log.json @@ -53,6 +53,31 @@ "fatal": "^(?i)(?:alert|fatal|panic|Al|Em)$" }, "opid-field": "opid", + "opid": { + "description": { + "vum": { + "format": [ + { + "field": "sub", + "extractor": "^(com\\..*)$" + } + ] + }, + "hostd": { + "format": [ + { + "field": "body", + "extractor": "target='([^']+)'" + }, + { + "prefix": ".", + "field": "body", + "extractor": "method='([^']+)'" + } + ] + } + } + }, "value": { "prc": { "kind": "string", diff --git a/src/gantt_source.cc b/src/gantt_source.cc new file mode 100644 index 00000000..b9c8435e --- /dev/null +++ b/src/gantt_source.cc @@ -0,0 +1,479 @@ +/** + * Copyright (c) 2023, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "gantt_source.hh" + +#include "base/humanize.hh" +#include "base/humanize.time.hh" +#include "base/math_util.hh" +#include "md4cpp.hh" +#include "sql_util.hh" + +using namespace std::chrono_literals; +using namespace lnav::roles::literals; +using namespace md4cpp::literals; + +static const std::vector TIME_SPANS = { + 5min, + 15min, + 1h, + 8h, + 24h, + 7 * 24h, + 30 * 24h, + 365 * 24h, +}; + +gantt_header_overlay::gantt_header_overlay(std::shared_ptr src) + : gho_src(src) +{ +} + +bool +gantt_header_overlay::list_static_overlay(const listview_curses& lv, + int y, + int bottom, + attr_line_t& value_out) +{ + if (y >= 3) { + return false; + } + + if (this->gho_src->gs_time_order.empty()) { + if (y == 0) { + value_out.append("No operations found"_error); + return true; + } + + return false; + } + + auto bounds = this->gho_src->get_time_bounds_for(lv.get_selection()); + auto width = lv.get_dimensions().second - 1; + + char datebuf[64]; + + if (y == 0) { + auto lb = this->gho_src->gs_lower_bound; + auto ub = this->gho_src->gs_upper_bound; + + double span = ub.tv_sec - lb.tv_sec; + double per_ch = span / (double) width; + sql_strftime(datebuf, sizeof(datebuf), lb, 'T'); + value_out.appendf(FMT_STRING(" {}"), datebuf); + + auto upper_size = sql_strftime(datebuf, sizeof(datebuf), ub, 'T'); + value_out.append(width - value_out.length() - upper_size - 1, ' ') + .append(datebuf); + + auto lr = line_range{}; + if (lb.tv_sec < bounds.first.tv_sec) { + auto start_diff = bounds.first.tv_sec - lb.tv_sec; + lr.lr_start = start_diff / per_ch; + } else { + lr.lr_start = 0; + } + if (lb.tv_sec < bounds.second.tv_sec) { + auto start_diff = bounds.second.tv_sec - lb.tv_sec; + lr.lr_end = start_diff / per_ch; + } else { + lr.lr_end = 1; + } + if (lr.lr_start == lr.lr_end) { + lr.lr_end += 1; + } + + value_out.get_attrs().emplace_back( + lr, VC_ROLE.value(role_t::VCR_CURSOR_LINE)); + value_out.with_attr_for_all(VC_ROLE.value(role_t::VCR_STATUS_INFO)); + } else if (y == 1) { + sql_strftime(datebuf, sizeof(datebuf), bounds.first, 'T'); + value_out.appendf(FMT_STRING(" {}"), datebuf); + + auto upper_size + = sql_strftime(datebuf, sizeof(datebuf), bounds.second, 'T'); + value_out.append(width - value_out.length() - upper_size - 5, ' ') + .append(datebuf); + value_out.with_attr_for_all(VC_ROLE.value(role_t::VCR_CURSOR_LINE)); + } else { + value_out.append(" Duration "_h1) + .append("|", VC_GRAPHIC.value(ACS_VLINE)) + .append(" ") + .append("\u2718"_error) + .append("\u25b2"_warning) + .append(" ") + .append("|", VC_GRAPHIC.value(ACS_VLINE)) + .append(" Operation"_h1); + auto hdr_attrs = text_attrs{}; + hdr_attrs.ta_attrs = A_UNDERLINE; + value_out.get_attrs().emplace_back(line_range{0, -1}, + VC_STYLE.value(hdr_attrs)); + value_out.with_attr_for_all(VC_ROLE.value(role_t::VCR_STATUS_INFO)); + } + + return true; +} + +gantt_source::gantt_source(textview_curses& log_view, + logfile_sub_source& lss, + plain_text_source& preview_source, + preview_status_source& preview_status_source) + : gs_log_view(log_view), gs_lss(lss), gs_preview_source(preview_source), + gs_preview_status_source(preview_status_source) +{ +} + +std::pair +gantt_source::get_time_bounds_for(int line) +{ + static const int CONTEXT_LINES = 5; + + const auto& low_row + = this->gs_time_order[std::max(0, line - CONTEXT_LINES)]; + const auto& sel_row = this->gs_time_order[line]; + const auto& high_row = this->gs_time_order[std::min( + line + CONTEXT_LINES, (int) this->gs_time_order.size() - 1)]; + auto high_tv_sec = std::max(sel_row.or_value.otr_end.tv_sec, + high_row.or_value.otr_begin.tv_sec); + + auto duration + = std::chrono::seconds{high_tv_sec - low_row.or_value.otr_begin.tv_sec}; + auto span_iter + = std::upper_bound(TIME_SPANS.begin(), TIME_SPANS.end(), duration); + if (span_iter == TIME_SPANS.end()) { + --span_iter; + } + auto span_secs = span_iter->count() - 60; + struct timeval lower_tv = { + rounddown(low_row.or_value.otr_begin.tv_sec, 60), + 0, + }; + lower_tv.tv_sec -= span_secs / 2; + struct timeval upper_tv = { + static_cast(roundup_size(high_tv_sec, 60)), + 0, + }; + upper_tv.tv_sec += span_secs / 2; + + return {lower_tv, upper_tv}; +} + +size_t +gantt_source::text_line_count() +{ + return this->gs_time_order.size(); +} + +void +gantt_source::text_value_for_line(textview_curses& tc, + int line, + std::string& value_out, + text_sub_source::line_flags_t flags) +{ + if (line < this->gs_time_order.size()) { + const auto& row = this->gs_time_order[line]; + auto duration = row.or_value.otr_end - row.or_value.otr_begin; + auto duration_str = fmt::format( + FMT_STRING(" {: >13}"), + humanize::time::duration::from_tv(duration).to_string()); + + this->gs_rendered_line.clear(); + + auto total_msgs = row.or_value.get_total_msgs(); + this->gs_rendered_line + .append(duration_str, VC_ROLE.value(role_t::VCR_OFFSET_TIME)) + .append(" ") + .append(lnav::roles::error(humanize::sparkline( + row.or_value.get_error_count(), total_msgs))) + .append(lnav::roles::warning(humanize::sparkline( + row.or_value.otr_level_counts[log_level_t::LEVEL_WARNING], + total_msgs))) + .append(" ") + .append(lnav::roles::identifier(row.or_name.to_string())) + .append(this->gs_opid_width - row.or_name.length(), ' '); + for (const auto& desc_pair : row.or_value.otr_description) { + this->gs_rendered_line.append(" "); + this->gs_rendered_line.append(desc_pair.second); + } + this->gs_rendered_line.with_attr_for_all( + VC_ROLE.value(role_t::VCR_COMMENT)); + + value_out = this->gs_rendered_line.get_string(); + } +} + +void +gantt_source::text_attrs_for_line(textview_curses& tc, + int line, + string_attrs_t& value_out) +{ + if (line < this->gs_time_order.size()) { + const auto& row = this->gs_time_order[line]; + + value_out = this->gs_rendered_line.get_attrs(); + + auto lr = line_range{}; + auto sel_bounds = this->get_time_bounds_for(tc.get_selection()); + + if (row.or_value.otr_begin <= sel_bounds.second + && sel_bounds.first <= row.or_value.otr_end) + { + static const int INDENT = 22; + + auto width = tc.get_dimensions().second; + + if (width > INDENT) { + width -= INDENT; + double span + = sel_bounds.second.tv_sec - sel_bounds.first.tv_sec; + double per_ch = span / (double) width; + + if (row.or_value.otr_begin <= sel_bounds.first) { + lr.lr_start = INDENT; + } else { + auto start_diff = row.or_value.otr_begin.tv_sec + - sel_bounds.first.tv_sec; + + lr.lr_start = INDENT + start_diff / per_ch; + } + + if (sel_bounds.second < row.or_value.otr_end) { + lr.lr_end = -1; + } else { + auto end_diff + = row.or_value.otr_end.tv_sec - sel_bounds.first.tv_sec; + + lr.lr_end = INDENT + end_diff / per_ch; + if (lr.lr_start == lr.lr_end) { + lr.lr_end += 1; + } + } + + auto block_attrs = text_attrs{}; + block_attrs.ta_attrs = A_REVERSE; + value_out.emplace_back(lr, VC_STYLE.value(block_attrs)); + } + } + auto alt_row_index = line % 4; + if (alt_row_index == 2 || alt_row_index == 3) { + value_out.emplace_back(line_range{0, -1}, + VC_ROLE.value(role_t::VCR_ALT_ROW)); + } + } +} + +size_t +gantt_source::text_size_for_line(textview_curses& tc, + int line, + text_sub_source::line_flags_t raw) +{ + return this->gs_total_width; +} + +void +gantt_source::rebuild_indexes() +{ + auto& bm = this->tss_view->get_bookmarks(); + auto& bm_errs = bm[&logfile_sub_source::BM_ERRORS]; + auto& bm_warns = bm[&logfile_sub_source::BM_WARNINGS]; + + bm_errs.clear(); + bm_warns.clear(); + + this->gs_lower_bound = {}; + this->gs_upper_bound = {}; + this->gs_opid_width = 0; + this->gs_total_width = 0; + + auto max_desc_width = size_t{0}; + + log_opid_map active_opids; + for (const auto& ld : this->gs_lss) { + if (ld->get_file_ptr() == nullptr) { + continue; + } + + safe::ReadAccess r_opid_map( + ld->get_file_ptr()->get_opids()); + + for (const auto& pair : *r_opid_map) { + auto iter = this->gs_opid_map.find(pair.first); + if (iter == this->gs_opid_map.end()) { + auto opid = pair.first.to_owned(this->gs_allocator); + auto emp_res + = this->gs_opid_map.emplace(opid, opid_description_defs{}); + iter = emp_res.first; + } + + auto active_iter = active_opids.find(pair.first); + if (active_iter == active_opids.end()) { + active_opids.emplace(iter->first, pair.second); + } else { + active_iter->second |= pair.second; + } + } + } + + std::multimap time_order_map; + for (const auto& pair : active_opids) { + if (this->gs_lower_bound.tv_sec == 0 + || pair.second.otr_begin < this->gs_lower_bound) + { + this->gs_lower_bound = pair.second.otr_begin; + } + if (this->gs_upper_bound.tv_sec == 0 + || this->gs_upper_bound < pair.second.otr_end) + { + this->gs_upper_bound = pair.second.otr_end; + } + if (pair.first.length() > this->gs_opid_width) { + this->gs_opid_width = pair.first.length(); + } + time_order_map.emplace(pair.second.otr_begin, + opid_row{pair.first, pair.second}); + } + this->gs_time_order.clear(); + for (const auto& pair : time_order_map) { + if (pair.second.or_value.get_error_count() > 0) { + bm_errs.insert_once(vis_line_t(this->gs_time_order.size())); + } else if (pair.second.or_value + .otr_level_counts[log_level_t::LEVEL_WARNING] + > 0) + { + bm_warns.insert_once(vis_line_t(this->gs_time_order.size())); + } + auto total_desc_width = size_t{0}; + for (const auto& desc : pair.second.or_value.otr_description) { + total_desc_width += 1 + desc.second.length(); + } + if (total_desc_width > max_desc_width) { + max_desc_width = total_desc_width; + } + this->gs_time_order.emplace_back(pair.second); + } + this->gs_total_width = 8 + this->gs_opid_width + max_desc_width; +} + +nonstd::optional +gantt_source::row_for_time(struct timeval time_bucket) +{ + auto iter = std::lower_bound(this->gs_time_order.begin(), + this->gs_time_order.end(), + time_bucket, + [](const opid_row& lhs, const timeval& rhs) { + return lhs.or_value.otr_begin < rhs; + }); + if (iter == this->gs_time_order.end()) { + return nonstd::nullopt; + } + + return vis_line_t(std::distance(this->gs_time_order.begin(), iter)); +} + +nonstd::optional +gantt_source::time_for_row(vis_line_t row) +{ + if (row >= this->gs_time_order.size()) { + return nonstd::nullopt; + } + + return this->gs_time_order[row].or_value.otr_begin; +} + +size_t +gantt_source::text_line_width(textview_curses& curses) +{ + return this->gs_total_width; +} + +void +gantt_source::text_selection_changed(textview_curses& tc) +{ + static const size_t MAX_PREVIEW_LINES = 5; + + auto sel = tc.get_selection(); + + this->gs_preview_source.clear(); + if (sel >= this->gs_time_order.size()) { + return; + } + + const auto& row = this->gs_time_order[sel]; + + auto low_vl = this->gs_lss.row_for_time(row.or_value.otr_begin); + auto high_tv = row.or_value.otr_end; + high_tv.tv_sec += 1; + auto high_vl = this->gs_lss.row_for_time(high_tv); + + if (!low_vl || !high_vl) { + return; + } + + auto preview_content = attr_line_t(); + auto msgs_remaining = size_t{MAX_PREVIEW_LINES}; + auto win = this->gs_lss.window_at(low_vl.value(), high_vl.value()); + auto id_hash = hash_str(row.or_name.data(), row.or_name.length()); + for (const auto& msg_line : win) { + if (!msg_line.get_logline().match_opid_hash(id_hash)) { + continue; + } + + const auto& sa = msg_line.get_attrs(); + auto opid_opt = get_string_attr(sa, logline::L_OPID); + + if (opid_opt) { + auto opid_range = opid_opt.value().saw_string_attr->sa_range; + auto opid_sf = msg_line.to_string(opid_range); + + if (opid_sf == row.or_name) { + std::vector rows_al(1); + + this->gs_log_view.listview_value_for_rows( + this->gs_log_view, msg_line.get_vis_line(), rows_al); + + preview_content.append(rows_al[0]).append("\n"); + msgs_remaining -= 1; + if (msgs_remaining == 0) { + break; + } + } + } + } + + while (msgs_remaining > 0) { + preview_content.append("\u2800\n"); + msgs_remaining -= 1; + } + + this->gs_preview_source.replace_with(preview_content); + this->gs_preview_status_source.get_description().set_value( + "Messages with opid: %.*s", row.or_name.length(), row.or_name.data()); +} diff --git a/src/gantt_source.hh b/src/gantt_source.hh new file mode 100644 index 00000000..0c370a3a --- /dev/null +++ b/src/gantt_source.hh @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2023, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lnav_gantt_source_hh +#define lnav_gantt_source_hh + +#include "logfile_sub_source.hh" +#include "plain_text_source.hh" +#include "preview_status_source.hh" +#include "textview_curses.hh" + +class gantt_source + : public text_sub_source + , public text_time_translator { +public: + explicit gantt_source(textview_curses& log_view, + logfile_sub_source& lss, + plain_text_source& preview_source, + preview_status_source& preview_status_source); + + size_t text_line_count() override; + + size_t text_line_width(textview_curses& curses) override; + + void text_value_for_line(textview_curses& tc, + int line, + std::string& value_out, + line_flags_t flags) override; + + void text_attrs_for_line(textview_curses& tc, + int line, + string_attrs_t& value_out) override; + + size_t text_size_for_line(textview_curses& tc, + int line, + line_flags_t raw) override; + + void text_selection_changed(textview_curses& tc) override; + + nonstd::optional row_for_time( + struct timeval time_bucket) override; + nonstd::optional time_for_row(vis_line_t row) override; + + void rebuild_indexes(); + + std::pair get_time_bounds_for(int line); + + textview_curses& gs_log_view; + logfile_sub_source& gs_lss; + plain_text_source& gs_preview_source; + preview_status_source& gs_preview_status_source; + ArenaAlloc::Alloc gs_allocator{64 * 1024}; + + struct opid_description_defs {}; + + using gantt_opid_map + = robin_hood::unordered_map>; + + gantt_opid_map gs_opid_map; + + struct opid_row { + string_fragment or_name; + opid_time_range or_value; + }; + + attr_line_t gs_rendered_line; + size_t gs_opid_width{0}; + size_t gs_total_width{0}; + std::vector gs_time_order; + struct timeval gs_lower_bound {}; + struct timeval gs_upper_bound {}; +}; + +class gantt_header_overlay : public list_overlay_source { +public: + gantt_header_overlay(std::shared_ptr src); + + bool list_static_overlay(const listview_curses& lv, + int y, + int bottom, + attr_line_t& value_out) override; + +private: + std::shared_ptr gho_src; +}; + +#endif diff --git a/src/listview_curses.cc b/src/listview_curses.cc index 0b5a2034..0f7f6b26 100644 --- a/src/listview_curses.cc +++ b/src/listview_curses.cc @@ -654,9 +654,32 @@ listview_curses::shift_selection(shift_amount_t sa) if (this->is_selectable()) { auto new_selection = this->lv_selection + vis_line_t(offset); - if (new_selection >= 0_vl && new_selection < this->get_inner_height()) { - this->set_selection(new_selection); + if (new_selection < 0_vl) { + new_selection = 0_vl; + } else if (new_selection >= this->get_inner_height()) { + auto rows_avail + = this->rows_available(this->lv_top, RD_DOWN) - 1_vl; + auto top_for_last = this->get_top_for_last_row(); + + if ((this->lv_top < top_for_last) + && (this->lv_top + rows_avail > top_for_last)) + { + this->set_top(top_for_last); + if (this->lv_selection <= top_for_last) { + this->set_selection(top_for_last + 1_vl); + } + } else { + this->shift_top(rows_avail); + + auto inner_height = this->get_inner_height(); + if (this->lv_selectable && this->lv_top >= top_for_last + && inner_height > 0_vl) + { + this->set_selection(inner_height - 1_vl); + } + } } + this->set_selection(new_selection); } else { this->shift_top(vis_line_t{offset}); } diff --git a/src/lnav.cc b/src/lnav.cc index 634d960f..4b512748 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -98,6 +98,7 @@ #include "file_converter_manager.hh" #include "filter_sub_source.hh" #include "fstat_vtab.hh" +#include "gantt_source.hh" #include "grep_proc.hh" #include "hist_source.hh" #include "init-sql.h" @@ -2744,6 +2745,23 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' .add_input_delegate(*lnav_data.ld_spectro_source) .set_tail_space(4_vl); lnav_data.ld_views[LNV_SPECTRO].set_selectable(true); + auto gantt_view_source + = std::make_shared(lnav_data.ld_views[LNV_LOG], + lnav_data.ld_log_source, + lnav_data.ld_preview_source, + lnav_data.ld_preview_status_source); + auto gantt_header_source + = std::make_shared(gantt_view_source); + lnav_data.ld_views[LNV_GANTT] + .set_sub_source(gantt_view_source.get()) + .set_overlay_source(gantt_header_source.get()) + .set_tail_space(4_vl); + lnav_data.ld_views[LNV_GANTT].set_selectable(true); + + auto _gantt_cleanup = finally([] { + lnav_data.ld_views[LNV_GANTT].set_sub_source(nullptr); + lnav_data.ld_views[LNV_GANTT].set_overlay_source(nullptr); + }); lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source); lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source); @@ -3335,6 +3353,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' *tc, y, tc->get_inner_height(), ov_al)) { write_line_to(stdout, ov_al); + ov_al.clear(); ++y; } diff --git a/src/lnav.indexing.cc b/src/lnav.indexing.cc index 52473995..0c905c88 100644 --- a/src/lnav.indexing.cc +++ b/src/lnav.indexing.cc @@ -194,7 +194,7 @@ rebuild_indexes(nonstd::optional deadline) bool scroll_downs[LNV__MAX]; size_t retval = 0; - for (int lpc = 0; lpc < LNV__MAX; lpc++) { + for (auto lpc : {LNV_LOG, LNV_TEXT}) { auto& view = lnav_data.ld_views[lpc]; if (view.is_selectable()) { @@ -333,7 +333,7 @@ rebuild_indexes(nonstd::optional deadline) retval += 1; } - for (int lpc = 0; lpc < LNV__MAX; lpc++) { + for (auto lpc : {LNV_LOG, LNV_TEXT}) { auto& scroll_view = lnav_data.ld_views[lpc]; if (scroll_downs[lpc]) { diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 93b8c018..1fcb41c8 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -1323,6 +1323,7 @@ com_save_to(exec_context& ec, && los->list_static_overlay(*tc, y, tc->get_inner_height(), ov_al)) { write_line_to(outfile, ov_al); + ov_al.clear(); ++y; } tc->listview_value_for_rows(*tc, top, rows); @@ -1335,8 +1336,8 @@ com_save_to(exec_context& ec, write_line_to(outfile, al); ++y; - std::vector row_overlay_content; if (los != nullptr) { + std::vector row_overlay_content; los->list_value_for_overlay(*tc, top, row_overlay_content); for (const auto& ov_row : row_overlay_content) { write_line_to(outfile, ov_row); @@ -1474,6 +1475,7 @@ com_save_to(exec_context& ec, && los->list_static_overlay(*tc, y, tc->get_inner_height(), ov_al)) { write_line_to(outfile, ov_al); + ov_al.clear(); ++y; } for (auto iter = all_user_marks.begin(); iter != all_user_marks.end(); @@ -1490,8 +1492,8 @@ com_save_to(exec_context& ec, write_line_to(outfile, rows[0]); y = 0_vl; - std::vector row_overlay_content; if (los != nullptr) { + std::vector row_overlay_content; los->list_value_for_overlay(*tc, (*iter), row_overlay_content); for (const auto& ov_row : row_overlay_content) { write_line_to(outfile, ov_row); diff --git a/src/log_format.cc b/src/log_format.cc index 8545732f..f1c3f652 100644 --- a/src/log_format.cc +++ b/src/log_format.cc @@ -27,6 +27,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include @@ -68,6 +69,47 @@ external_log_format::mod_map_t external_log_format::MODULE_FORMATS; std::vector> external_log_format::GRAPH_ORDERED_FORMATS; +size_t +opid_time_range::get_total_msgs() const +{ + return std::accumulate(this->otr_level_counts.begin(), + this->otr_level_counts.end(), + size_t{0}); +} + +size_t +opid_time_range::get_error_count() const +{ + size_t retval = 0; + + for (const auto level : { + log_level_t::LEVEL_ERROR, + log_level_t::LEVEL_CRITICAL, + log_level_t::LEVEL_FATAL, + }) + { + retval += this->otr_level_counts[level]; + } + + return retval; +} + +opid_time_range& +opid_time_range::operator|=(const opid_time_range& rhs) +{ + if (rhs.otr_begin < this->otr_begin) { + this->otr_begin = rhs.otr_begin; + } + if (this->otr_end < rhs.otr_end) { + this->otr_end = rhs.otr_end; + } + if (this->otr_description.size() < rhs.otr_description.size()) { + this->otr_description = rhs.otr_description; + } + + return *this; +} + struct line_range logline_value::origin_in_full_msg(const char* msg, ssize_t len) const { @@ -451,6 +493,7 @@ struct json_log_userdata { uint32_t jlu_quality{0}; shared_buffer_ref& jlu_shared_buffer; scan_batch_context* jlu_batch_context; + nonstd::optional jlu_opid_frag; }; static int read_json_field(yajlpp_parse_context* ypc, @@ -780,6 +823,21 @@ external_log_format::scan(logfile& lf, "JSON message does not have expected timestamp property"}; } + if (jlu.jlu_opid_frag) { + auto opid_iter = sbc.sbc_opids.find(jlu.jlu_opid_frag.value()); + if (opid_iter == sbc.sbc_opids.end()) { + auto otr + = opid_time_range{ll.get_timeval(), ll.get_timeval()}; + auto emplace_res + = sbc.sbc_opids.emplace(jlu.jlu_opid_frag.value(), otr); + opid_iter = emplace_res.first; + } else { + opid_iter->second.otr_end = ll.get_timeval(); + } + + opid_iter->second.otr_level_counts[ll.get_msg_level()] += 1; + } + jlu.jlu_sub_line_count += this->jlf_line_format_init_count; for (int lpc = 0; lpc < jlu.jlu_sub_line_count; lpc++) { ll.set_sub_offset(lpc); @@ -905,17 +963,127 @@ external_log_format::scan(logfile& lf, } if (opid_cap && !opid_cap->empty()) { - { - auto opid_iter = sbc.sbc_opids.find(opid_cap.value()); + auto opid_iter = sbc.sbc_opids.find(opid_cap.value()); - if (opid_iter == sbc.sbc_opids.end()) { - auto opid_copy = opid_cap->to_owned(sbc.sbc_allocator); - auto otr = opid_time_range{log_tv, log_tv}; - sbc.sbc_opids.emplace(opid_copy, otr); - } else { - opid_iter->second.otr_end = log_tv; + if (opid_iter == sbc.sbc_opids.end()) { + auto opid_copy = opid_cap->to_owned(sbc.sbc_allocator); + auto otr = opid_time_range{log_tv, log_tv}; + auto emplace_res = sbc.sbc_opids.emplace(opid_copy, otr); + opid_iter = emplace_res.first; + } else { + opid_iter->second.otr_end = log_tv; + } + + opid_iter->second.otr_level_counts[level] += 1; + + auto& otr = opid_iter->second; + if (!otr.otr_description_id) { + for (const auto& desc_def_pair : *this->lf_opid_description_def) + { + if (otr.otr_description_id) { + break; + } + for (const auto& desc_def : + desc_def_pair.second.od_descriptors) + { + auto desc_field_index_iter + = fpat->p_value_name_to_index.find( + desc_def.od_field.pp_value); + + if (desc_field_index_iter + != fpat->p_value_name_to_index.end()) + { + auto desc_cap_opt + = md[desc_field_index_iter->second]; + + if (desc_cap_opt) { + if (desc_def.od_extractor.pp_value) { + static thread_local auto desc_md = lnav:: + pcre2pp::match_data::unitialized(); + + auto desc_match_res + = desc_def.od_extractor.pp_value + ->capture_from( + desc_cap_opt.value()) + .into(desc_md) + .matches(PCRE2_NO_UTF_CHECK) + .ignore_error(); + if (desc_match_res) { + otr.otr_description_id + = desc_def_pair.first; + } + } else { + otr.otr_description_id + = desc_def_pair.first; + } + } + } + } + } + } + + if (otr.otr_description_id) { + const auto& desc_def_v + = this->lf_opid_description_def + ->find(opid_iter->second.otr_description_id.value()) + ->second.od_descriptors; + auto& desc_v = opid_iter->second.otr_description; + + if (desc_def_v.size() != desc_v.size()) { + for (size_t desc_def_index = 0; + desc_def_index < desc_def_v.size(); + desc_def_index++) + { + const auto& desc_def = desc_def_v[desc_def_index]; + auto found_desc = false; + + for (const auto& desc_pair : desc_v) { + if (desc_pair.first == desc_def_index) { + found_desc = true; + break; + } + } + if (!found_desc) { + auto desc_field_index_iter + = fpat->p_value_name_to_index.find( + desc_def.od_field.pp_value); + + if (desc_field_index_iter + != fpat->p_value_name_to_index.end()) + { + auto desc_cap_opt + = md[desc_field_index_iter->second]; + + if (desc_cap_opt) { + if (desc_def.od_extractor.pp_value) { + static thread_local auto desc_md + = lnav::pcre2pp::match_data:: + unitialized(); + + auto match_res + = desc_def.od_extractor.pp_value + ->capture_from( + desc_cap_opt.value()) + .into(desc_md) + .matches(PCRE2_NO_UTF_CHECK) + .ignore_error(); + if (match_res) { + desc_v.emplace_back( + desc_def_index, + desc_md.to_string()); + } + } else { + desc_v.emplace_back( + desc_def_index, + desc_cap_opt->to_string()); + } + } + } + } + } } } + opid = hash_str(opid_cap->data(), opid_cap->length()); } @@ -1326,6 +1494,7 @@ read_json_field(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) const intern_string_t field_name = ypc->get_path(); struct exttm tm_out; struct timeval tv_out; + auto frag = string_fragment::from_bytes(str, len); if (jlu->jlu_format->lf_timestamp_field == field_name) { jlu->jlu_format->lf_date_time.scan( @@ -1344,17 +1513,25 @@ read_json_field(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) .ignore_error() .has_value()) { - jlu->jlu_base_line->set_level(jlu->jlu_format->convert_level( - string_fragment::from_bytes(str, len), jlu->jlu_batch_context)); + jlu->jlu_base_line->set_level( + jlu->jlu_format->convert_level(frag, jlu->jlu_batch_context)); } } if (jlu->jlu_format->elf_level_field == field_name) { - jlu->jlu_base_line->set_level(jlu->jlu_format->convert_level( - string_fragment::from_bytes(str, len), jlu->jlu_batch_context)); + jlu->jlu_base_line->set_level( + jlu->jlu_format->convert_level(frag, jlu->jlu_batch_context)); } if (jlu->jlu_format->elf_opid_field == field_name) { uint8_t opid = hash_str((const char*) str, len); jlu->jlu_base_line->set_opid(opid); + + auto& sbc = *jlu->jlu_batch_context; + auto opid_iter = sbc.sbc_opids.find(frag); + if (opid_iter == sbc.sbc_opids.end()) { + jlu->jlu_opid_frag = frag.to_owned(sbc.sbc_allocator); + } else { + jlu->jlu_opid_frag = opid_iter->first; + } } jlu->add_sub_lines_for( @@ -1789,6 +1966,11 @@ external_log_format::get_subline(const logline& ll, break; } } + lv.lv_origin.lr_end = this->jlf_cached_line.size() - 1; + if (lv.lv_meta.lvm_name == this->elf_opid_field) { + this->jlf_line_attrs.emplace_back(lv.lv_origin, + logline::L_OPID.value()); + } } } @@ -2073,6 +2255,7 @@ external_log_format::build(std::vector& errors) ivd.ivd_value_def = vd; pat.p_value_by_index.push_back(ivd); } + pat.p_value_name_to_index[name] = named_cap.get_index(); } stable_sort(pat.p_value_by_index.begin(), pat.p_value_by_index.end()); @@ -2310,6 +2493,23 @@ external_log_format::build(std::vector& errors) } } + for (const auto& opid_desc_pair : *this->lf_opid_description_def) { + for (const auto& opid_desc : opid_desc_pair.second.od_descriptors) { + auto iter = this->elf_value_defs.find(opid_desc.od_field.pp_value); + if (iter == this->elf_value_defs.end()) { + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid opid description field ") + .append_quoted(lnav::roles::symbol( + opid_desc.od_field.pp_path.to_string()))) + .with_reason( + attr_line_t("unknown value name ") + .append_quoted(opid_desc.od_field.pp_value)) + .with_snippets(this->get_snippets())); + } + } + } + if (this->elf_type == elf_type_t::ELF_TYPE_TEXT && this->elf_samples.empty()) { diff --git a/src/log_format.hh b/src/log_format.hh index 88f459a2..c5992499 100644 --- a/src/log_format.hh +++ b/src/log_format.hh @@ -541,6 +541,21 @@ public: std::map> lf_tag_defs; + struct opid_descriptor { + positioned_property od_field; + factory_container od_extractor; + std::string od_prefix{" "}; + std::string od_suffix; + }; + + struct opid_descriptors { + std::vector od_descriptors; + }; + + std::shared_ptr> + lf_opid_description_def{ + std::make_shared>()}; + protected: static std::vector> lf_root_formats; diff --git a/src/log_format_ext.hh b/src/log_format_ext.hh index ea6a88dd..1c0db0dc 100644 --- a/src/log_format_ext.hh +++ b/src/log_format_ext.hh @@ -104,6 +104,7 @@ public: int>::with_default_args p_pcre; std::vector p_value_by_index; + std::map p_value_name_to_index; std::vector p_numeric_value_indexes; int p_timestamp_field_index{-1}; int p_time_field_index{-1}; diff --git a/src/log_format_fwd.hh b/src/log_format_fwd.hh index 8b9d3017..c182f7d4 100644 --- a/src/log_format_fwd.hh +++ b/src/log_format_fwd.hh @@ -51,6 +51,14 @@ class log_format; struct opid_time_range { struct timeval otr_begin; struct timeval otr_end; + std::array otr_level_counts; + nonstd::optional otr_description_id; + std::vector> otr_description; + + size_t get_total_msgs() const; + size_t get_error_count() const; + + opid_time_range& operator|=(const opid_time_range& rhs); }; using log_opid_map = robin_hood::unordered_mapll_opid; } + bool match_opid_hash(unsigned long hash) const + { + struct { + unsigned int value : 6; + } reduced = { + (unsigned int) hash, + }; + + return this->ll_opid == reduced.value; + } + /** * @return True if there is a schema value set for this log line. */ diff --git a/src/log_format_loader.cc b/src/log_format_loader.cc index 0e4e7abf..afa3acc9 100644 --- a/src/log_format_loader.cc +++ b/src/log_format_loader.cc @@ -847,6 +847,34 @@ static const struct json_path_container converter_handlers = { .for_field(&external_log_format::converter::c_command), }; +static const struct json_path_container opid_descriptor_handlers = { + yajlpp::property_handler("field").for_field( + &log_format::opid_descriptor::od_field), + yajlpp::property_handler("extractor") + .for_field(&log_format::opid_descriptor::od_extractor), + yajlpp::property_handler("prefix").for_field( + &log_format::opid_descriptor::od_prefix), + yajlpp::property_handler("suffix").for_field( + &log_format::opid_descriptor::od_suffix), +}; + +static const struct json_path_container opid_description_format_handlers = { + yajlpp::property_handler("format#") + .for_field(&log_format::opid_descriptors::od_descriptors) + .with_children(opid_descriptor_handlers), +}; + +static const struct json_path_container opid_description_handlers = { + yajlpp::pattern_property_handler(R"((?[\w\.\-]+))") + .for_field(&log_format::lf_opid_description_def) + .with_children(opid_description_format_handlers), +}; + +static const struct json_path_container opid_handlers = { + yajlpp::property_handler("description") + .with_children(opid_description_handlers), +}; + const struct json_path_container format_handlers = { yajlpp::property_handler("regex") .with_description( @@ -930,6 +958,7 @@ const struct json_path_container format_handlers = { .with_description( "The name of the operation-id field in the log message pattern") .for_field(&external_log_format::elf_opid_field), + yajlpp::property_handler("opid").with_children(opid_handlers), yajlpp::property_handler("ordered-by-time") .with_synopsis("") .with_description( diff --git a/src/pcrepp/pcre2pp.cc b/src/pcrepp/pcre2pp.cc index e4ff89e7..ce549d91 100644 --- a/src/pcrepp/pcre2pp.cc +++ b/src/pcrepp/pcre2pp.cc @@ -55,6 +55,29 @@ quote(const char* unquoted) return retval; } +std::string +match_data::to_string() const +{ + std::string retval; + + if (this->get_count() == 1) { + auto cap = (*this)[0]; + retval.append(cap->data(), cap->length()); + } else { + for (size_t lpc = 1; lpc < this->get_count(); lpc++) { + auto cap = (*this)[lpc]; + + if (!cap) { + continue; + } + + retval.append(cap->data(), cap->length()); + } + } + + return retval; +} + matcher capture_builder::into(lnav::pcre2pp::match_data& md) && { diff --git a/src/pcrepp/pcre2pp.hh b/src/pcrepp/pcre2pp.hh index caef54a3..366f801c 100644 --- a/src/pcrepp/pcre2pp.hh +++ b/src/pcrepp/pcre2pp.hh @@ -108,6 +108,8 @@ public: uint32_t get_capacity() const { return this->md_ovector_count; } + std::string to_string() const; + private: friend matcher; friend code; diff --git a/src/preview_status_source.hh b/src/preview_status_source.hh index 25622b07..6f7dae37 100644 --- a/src/preview_status_source.hh +++ b/src/preview_status_source.hh @@ -60,22 +60,19 @@ public: this->tss_fields[TSF_TOGGLE].set_width(strlen(TOGGLE_MSG) + 1); this->tss_fields[TSF_TOGGLE].set_value(TOGGLE_MSG); this->tss_fields[TSF_TOGGLE].right_justify(true); - }; + } - size_t statusview_fields() override - { - return TSF__MAX; - }; + size_t statusview_fields() override { return TSF__MAX; } status_field& statusview_value_for_field(int field) override { return this->tss_fields[field]; - }; + } status_field& get_description() { return this->tss_fields[TSF_DESCRIPTION]; - }; + } private: status_field tss_fields[TSF__MAX]; diff --git a/src/session_data.cc b/src/session_data.cc index 5bf52772..b7cf0373 100644 --- a/src/session_data.cc +++ b/src/session_data.cc @@ -1682,7 +1682,7 @@ lnav::session::restore_view_states() log_info("restoring %s view top: %d", lnav_view_strings[view_index], vs.vs_top); - lnav_data.ld_views[view_index].set_top(vis_line_t(vs.vs_top)); + lnav_data.ld_views[view_index].set_top(vis_line_t(vs.vs_top), true); } if (vs.vs_selection) { log_info("restoring %s view selection: %d", diff --git a/src/themes/monocai.json b/src/themes/monocai.json index ef9319a7..3bd41226 100644 --- a/src/themes/monocai.json +++ b/src/themes/monocai.json @@ -47,9 +47,8 @@ }, "cursor-line": { "color": "$cyan", - "background-color": "$red", - "bold": true, - "underline": true + "background-color": "#d7005f", + "bold": true }, "adjusted-time": { "color": "$magenta" diff --git a/src/view_helpers.cc b/src/view_helpers.cc index 5c8bd3e0..997fd2e1 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -35,6 +35,7 @@ #include "document.sections.hh" #include "environ_vtab.hh" #include "filter_sub_source.hh" +#include "gantt_source.hh" #include "help-md.h" #include "intervaltree/IntervalTree.h" #include "lnav.hh" @@ -63,6 +64,7 @@ const char* lnav_view_strings[LNV__MAX + 1] = { "schema", "pretty", "spectro", + "gantt", nullptr, }; @@ -76,6 +78,7 @@ const char* lnav_view_titles[LNV__MAX] = { "SCHEMA", "PRETTY", "SPECTRO", + "GANTT", }; nonstd::optional @@ -124,6 +127,17 @@ open_schema_view() schema_tc->redo_search(); } +static void +open_gantt_view() +{ + auto* gantt_tc = &lnav_data.ld_views[LNV_GANTT]; + auto* gantt_src = dynamic_cast(gantt_tc->get_sub_source()); + + gantt_src->rebuild_indexes(); + gantt_tc->reload_data(); + gantt_tc->redo_search(); +} + class pretty_sub_source : public plain_text_source { public: void text_crumbs_for_line(int line, @@ -907,6 +921,9 @@ toggle_view(textview_curses* toggle_tc) require(toggle_tc >= &lnav_data.ld_views[0]); require(toggle_tc < &lnav_data.ld_views[LNV__MAX]); + lnav_data.ld_preview_source.clear(); + lnav_data.ld_preview_status_source.get_description().clear(); + if (tc == toggle_tc) { if (lnav_data.ld_view_stack.size() == 1) { return false; @@ -923,6 +940,8 @@ toggle_view(textview_curses* toggle_tc) open_schema_view(); } else if (toggle_tc == &lnav_data.ld_views[LNV_PRETTY]) { open_pretty_view(); + } else if (toggle_tc == &lnav_data.ld_views[LNV_GANTT]) { + open_gantt_view(); } else if (toggle_tc == &lnav_data.ld_views[LNV_HISTOGRAM]) { // Rebuild to reflect changes in marks. rebuild_hist(); diff --git a/src/view_helpers.hh b/src/view_helpers.hh index 19989e16..1d64161c 100644 --- a/src/view_helpers.hh +++ b/src/view_helpers.hh @@ -51,6 +51,7 @@ typedef enum { LNV_SCHEMA, LNV_PRETTY, LNV_SPECTRO, + LNV_GANTT, LNV__MAX } lnav_view_t; diff --git a/src/views_vtab.cc b/src/views_vtab.cc index 252de2ba..97172ebb 100644 --- a/src/views_vtab.cc +++ b/src/views_vtab.cc @@ -234,7 +234,8 @@ CREATE TABLE lnav_views ( = dynamic_cast(tc.get_sub_source()); if (time_source != nullptr && tc.get_inner_height() > 0) { - auto top_time_opt = time_source->time_for_row(tc.get_top()); + auto top_time_opt + = time_source->time_for_row(tc.get_selection()); if (top_time_opt) { char timestamp[64]; @@ -303,7 +304,7 @@ CREATE TABLE lnav_views ( top_line_meta tlm; if (time_source != nullptr) { auto top_time_opt - = time_source->time_for_row(tc.get_top()); + = time_source->time_for_row(tc.get_selection()); if (top_time_opt) { char timestamp[64]; @@ -386,6 +387,8 @@ CREATE TABLE lnav_views ( = dynamic_cast(tc.get_sub_source()); if (tc.get_top() != top_row) { + log_debug( + "setting top for %s to %d", tc.get_title().c_str(), top_row); tc.set_top(vis_line_t(top_row)); if (!tc.is_selectable()) { selection = top_row; @@ -394,18 +397,31 @@ CREATE TABLE lnav_views ( date_time_scanner dts; struct timeval tv; + log_debug("setting top time for %s to %s", + tc.get_title().c_str(), + top_time); if (dts.convert_to_timeval(top_time, -1, nullptr, tv)) { - auto last_time_opt = time_source->time_for_row(tc.get_top()); + auto last_time_opt + = time_source->time_for_row(tc.get_selection()); if (last_time_opt) { auto last_time = last_time_opt.value(); if (tv != last_time) { time_source->row_for_time(tv) | - [&tc](auto row) { tc.set_top(row); }; + [&tc, &selection](auto row) { + log_debug("setting top for %s to %d from time", + tc.get_title().c_str(), + row); + selection = row; + tc.set_selection(row); + }; if (!tc.is_selectable()) { selection = tc.get_top(); } } + } else { + log_warning(" could not get for time top row of %s", + tc.get_title().c_str()); } } else { auto um = lnav::console::user_message::error( @@ -549,6 +565,9 @@ CREATE TABLE lnav_view_stack ( lnav_data.ld_last_view = *lnav_data.ld_view_stack.top(); lnav_data.ld_view_stack.pop_back(); + lnav_data.ld_preview_source.clear(); + lnav_data.ld_preview_status_source.get_description().clear(); + return SQLITE_OK; } diff --git a/src/yajlpp/yajlpp_def.hh b/src/yajlpp/yajlpp_def.hh index ca76bb75..a70db972 100644 --- a/src/yajlpp/yajlpp_def.hh +++ b/src/yajlpp/yajlpp_def.hh @@ -272,6 +272,12 @@ struct json_path_handler : public json_path_handler_base { return ypc->ypc_current_handler->jph_double_cb(ypc, val); } + template + static inline U& get_field(T& input, std::shared_ptr(T::*field)) + { + return *(input.*field); + } + template static inline U& get_field(T& input, U(T::*field)) { @@ -371,12 +377,36 @@ struct json_path_handler : public json_path_handler_base { static constexpr bool value = true; }; + template + struct LastIsVector> T::*> { + using value_type = U; + static constexpr bool value = true; + }; + template struct LastIsVector { using value_type = void; static constexpr bool value = false; }; + template + struct LastIsMap { + using value_type = typename LastIsMap::value_type; + static constexpr bool value = LastIsMap::value; + }; + + template + struct LastIsMap> T::*> { + using value_type = U; + static constexpr bool value = true; + }; + + template + struct LastIsMap { + using value_type = void; + static constexpr bool value = false; + }; + template static bool is_field_set(const nonstd::optional& field) { @@ -579,6 +609,27 @@ struct json_path_handler : public json_path_handler_base { return *this; } + template::value, bool> = true> + json_path_handler& for_field(Args... args) + { + this->jph_path_provider = + [args...](void* root, std::vector& paths_out) { + const auto& field = json_path_handler::get_field(root, args...); + + for (const auto& pair : field) { + paths_out.emplace_back(std::to_string(pair.first)); + } + }; + this->jph_obj_provider + = [args...](const yajlpp_provider_context& ypc, void* root) { + auto& field = json_path_handler::get_field(root, args...); + + return &(field[ypc.get_substr_i(0)]); + }; + return *this; + } + template>, diff --git a/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out b/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out index e61647e6..58e8635d 100644 --- a/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out +++ b/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out @@ -1686,8 +1686,8 @@ }, "cursor-line": { "color": "$cyan", - "background-color": "$red", - "underline": true, + "background-color": "#d7005f", + "underline": false, "bold": true }, "adjusted-time": { diff --git a/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out b/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out index 36cee106..a85dde0a 100644 --- a/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out +++ b/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out @@ -416,112 +416,111 @@ /ui/theme-defs/grayscale/vars/red -> grayscale.json:8 /ui/theme-defs/grayscale/vars/white -> grayscale.json:14 /ui/theme-defs/grayscale/vars/yellow -> grayscale.json:10 -/ui/theme-defs/monocai/log-level-styles/critical/color -> monocai.json:261 -/ui/theme-defs/monocai/log-level-styles/error/color -> monocai.json:258 -/ui/theme-defs/monocai/log-level-styles/fatal/color -> monocai.json:264 -/ui/theme-defs/monocai/log-level-styles/warning/color -> monocai.json:255 -/ui/theme-defs/monocai/status-styles/active/background-color -> monocai.json:242 -/ui/theme-defs/monocai/status-styles/active/color -> monocai.json:241 -/ui/theme-defs/monocai/status-styles/alert/background-color -> monocai.json:238 -/ui/theme-defs/monocai/status-styles/alert/color -> monocai.json:237 -/ui/theme-defs/monocai/status-styles/disabled-title/background-color -> monocai.json:202 -/ui/theme-defs/monocai/status-styles/disabled-title/bold -> monocai.json:203 -/ui/theme-defs/monocai/status-styles/disabled-title/color -> monocai.json:201 -/ui/theme-defs/monocai/status-styles/hotkey/color -> monocai.json:225 -/ui/theme-defs/monocai/status-styles/hotkey/underline -> monocai.json:226 -/ui/theme-defs/monocai/status-styles/inactive-alert/background-color -> monocai.json:250 -/ui/theme-defs/monocai/status-styles/inactive-alert/color -> monocai.json:249 -/ui/theme-defs/monocai/status-styles/inactive/background-color -> monocai.json:246 -/ui/theme-defs/monocai/status-styles/inactive/color -> monocai.json:245 -/ui/theme-defs/monocai/status-styles/info/background-color -> monocai.json:217 -/ui/theme-defs/monocai/status-styles/info/color -> monocai.json:216 -/ui/theme-defs/monocai/status-styles/subtitle/background-color -> monocai.json:212 -/ui/theme-defs/monocai/status-styles/subtitle/bold -> monocai.json:213 -/ui/theme-defs/monocai/status-styles/subtitle/color -> monocai.json:211 -/ui/theme-defs/monocai/status-styles/text/background-color -> monocai.json:230 -/ui/theme-defs/monocai/status-styles/text/color -> monocai.json:229 -/ui/theme-defs/monocai/status-styles/title-hotkey/background-color -> monocai.json:221 -/ui/theme-defs/monocai/status-styles/title-hotkey/color -> monocai.json:220 -/ui/theme-defs/monocai/status-styles/title-hotkey/underline -> monocai.json:222 -/ui/theme-defs/monocai/status-styles/title/background-color -> monocai.json:207 -/ui/theme-defs/monocai/status-styles/title/bold -> monocai.json:208 -/ui/theme-defs/monocai/status-styles/title/color -> monocai.json:206 -/ui/theme-defs/monocai/status-styles/warn/background-color -> monocai.json:234 -/ui/theme-defs/monocai/status-styles/warn/color -> monocai.json:233 -/ui/theme-defs/monocai/styles/adjusted-time/color -> monocai.json:55 +/ui/theme-defs/monocai/log-level-styles/critical/color -> monocai.json:260 +/ui/theme-defs/monocai/log-level-styles/error/color -> monocai.json:257 +/ui/theme-defs/monocai/log-level-styles/fatal/color -> monocai.json:263 +/ui/theme-defs/monocai/log-level-styles/warning/color -> monocai.json:254 +/ui/theme-defs/monocai/status-styles/active/background-color -> monocai.json:241 +/ui/theme-defs/monocai/status-styles/active/color -> monocai.json:240 +/ui/theme-defs/monocai/status-styles/alert/background-color -> monocai.json:237 +/ui/theme-defs/monocai/status-styles/alert/color -> monocai.json:236 +/ui/theme-defs/monocai/status-styles/disabled-title/background-color -> monocai.json:201 +/ui/theme-defs/monocai/status-styles/disabled-title/bold -> monocai.json:202 +/ui/theme-defs/monocai/status-styles/disabled-title/color -> monocai.json:200 +/ui/theme-defs/monocai/status-styles/hotkey/color -> monocai.json:224 +/ui/theme-defs/monocai/status-styles/hotkey/underline -> monocai.json:225 +/ui/theme-defs/monocai/status-styles/inactive-alert/background-color -> monocai.json:249 +/ui/theme-defs/monocai/status-styles/inactive-alert/color -> monocai.json:248 +/ui/theme-defs/monocai/status-styles/inactive/background-color -> monocai.json:245 +/ui/theme-defs/monocai/status-styles/inactive/color -> monocai.json:244 +/ui/theme-defs/monocai/status-styles/info/background-color -> monocai.json:216 +/ui/theme-defs/monocai/status-styles/info/color -> monocai.json:215 +/ui/theme-defs/monocai/status-styles/subtitle/background-color -> monocai.json:211 +/ui/theme-defs/monocai/status-styles/subtitle/bold -> monocai.json:212 +/ui/theme-defs/monocai/status-styles/subtitle/color -> monocai.json:210 +/ui/theme-defs/monocai/status-styles/text/background-color -> monocai.json:229 +/ui/theme-defs/monocai/status-styles/text/color -> monocai.json:228 +/ui/theme-defs/monocai/status-styles/title-hotkey/background-color -> monocai.json:220 +/ui/theme-defs/monocai/status-styles/title-hotkey/color -> monocai.json:219 +/ui/theme-defs/monocai/status-styles/title-hotkey/underline -> monocai.json:221 +/ui/theme-defs/monocai/status-styles/title/background-color -> monocai.json:206 +/ui/theme-defs/monocai/status-styles/title/bold -> monocai.json:207 +/ui/theme-defs/monocai/status-styles/title/color -> monocai.json:205 +/ui/theme-defs/monocai/status-styles/warn/background-color -> monocai.json:233 +/ui/theme-defs/monocai/status-styles/warn/color -> monocai.json:232 +/ui/theme-defs/monocai/styles/adjusted-time/color -> monocai.json:54 /ui/theme-defs/monocai/styles/alt-text/background-color -> monocai.json:26 -/ui/theme-defs/monocai/styles/breadcrumb/color -> monocai.json:112 +/ui/theme-defs/monocai/styles/breadcrumb/color -> monocai.json:111 /ui/theme-defs/monocai/styles/cursor-line/background-color -> monocai.json:50 /ui/theme-defs/monocai/styles/cursor-line/bold -> monocai.json:51 /ui/theme-defs/monocai/styles/cursor-line/color -> monocai.json:49 -/ui/theme-defs/monocai/styles/cursor-line/underline -> monocai.json:52 -/ui/theme-defs/monocai/styles/disabled-focused/background-color -> monocai.json:72 -/ui/theme-defs/monocai/styles/disabled-focused/color -> monocai.json:71 +/ui/theme-defs/monocai/styles/disabled-focused/background-color -> monocai.json:71 +/ui/theme-defs/monocai/styles/disabled-focused/color -> monocai.json:70 /ui/theme-defs/monocai/styles/error/bold -> monocai.json:38 /ui/theme-defs/monocai/styles/error/color -> monocai.json:37 -/ui/theme-defs/monocai/styles/focused/background-color -> monocai.json:68 -/ui/theme-defs/monocai/styles/focused/color -> monocai.json:67 -/ui/theme-defs/monocai/styles/footnote-border/background-color -> monocai.json:129 -/ui/theme-defs/monocai/styles/footnote-border/color -> monocai.json:128 -/ui/theme-defs/monocai/styles/footnote-text/background-color -> monocai.json:133 -/ui/theme-defs/monocai/styles/footnote-text/color -> monocai.json:132 -/ui/theme-defs/monocai/styles/h1/bold -> monocai.json:84 -/ui/theme-defs/monocai/styles/h1/color -> monocai.json:83 -/ui/theme-defs/monocai/styles/h2/color -> monocai.json:87 -/ui/theme-defs/monocai/styles/h2/underline -> monocai.json:88 -/ui/theme-defs/monocai/styles/h3/color -> monocai.json:91 -/ui/theme-defs/monocai/styles/h4/underline -> monocai.json:94 -/ui/theme-defs/monocai/styles/h5/underline -> monocai.json:97 -/ui/theme-defs/monocai/styles/h6/underline -> monocai.json:100 +/ui/theme-defs/monocai/styles/focused/background-color -> monocai.json:67 +/ui/theme-defs/monocai/styles/focused/color -> monocai.json:66 +/ui/theme-defs/monocai/styles/footnote-border/background-color -> monocai.json:128 +/ui/theme-defs/monocai/styles/footnote-border/color -> monocai.json:127 +/ui/theme-defs/monocai/styles/footnote-text/background-color -> monocai.json:132 +/ui/theme-defs/monocai/styles/footnote-text/color -> monocai.json:131 +/ui/theme-defs/monocai/styles/h1/bold -> monocai.json:83 +/ui/theme-defs/monocai/styles/h1/color -> monocai.json:82 +/ui/theme-defs/monocai/styles/h2/color -> monocai.json:86 +/ui/theme-defs/monocai/styles/h2/underline -> monocai.json:87 +/ui/theme-defs/monocai/styles/h3/color -> monocai.json:90 +/ui/theme-defs/monocai/styles/h4/underline -> monocai.json:93 +/ui/theme-defs/monocai/styles/h5/underline -> monocai.json:96 +/ui/theme-defs/monocai/styles/h6/underline -> monocai.json:99 /ui/theme-defs/monocai/styles/hidden/bold -> monocai.json:46 /ui/theme-defs/monocai/styles/hidden/color -> monocai.json:45 -/ui/theme-defs/monocai/styles/hr/color -> monocai.json:103 -/ui/theme-defs/monocai/styles/hyperlink/underline -> monocai.json:106 +/ui/theme-defs/monocai/styles/hr/color -> monocai.json:102 +/ui/theme-defs/monocai/styles/hyperlink/underline -> monocai.json:105 /ui/theme-defs/monocai/styles/identifier/color -> monocai.json:19 /ui/theme-defs/monocai/styles/info/bold -> monocai.json:34 /ui/theme-defs/monocai/styles/info/color -> monocai.json:33 -/ui/theme-defs/monocai/styles/invalid-msg/color -> monocai.json:64 -/ui/theme-defs/monocai/styles/list-glyph/color -> monocai.json:109 -/ui/theme-defs/monocai/styles/offset-time/color -> monocai.json:61 +/ui/theme-defs/monocai/styles/invalid-msg/color -> monocai.json:63 +/ui/theme-defs/monocai/styles/list-glyph/color -> monocai.json:108 +/ui/theme-defs/monocai/styles/offset-time/color -> monocai.json:60 /ui/theme-defs/monocai/styles/ok/bold -> monocai.json:30 /ui/theme-defs/monocai/styles/ok/color -> monocai.json:29 -/ui/theme-defs/monocai/styles/popup/background-color -> monocai.json:76 -/ui/theme-defs/monocai/styles/popup/color -> monocai.json:75 -/ui/theme-defs/monocai/styles/quote-border/background-color -> monocai.json:122 -/ui/theme-defs/monocai/styles/quote-border/color -> monocai.json:121 -/ui/theme-defs/monocai/styles/quoted-text/background-color -> monocai.json:125 -/ui/theme-defs/monocai/styles/scrollbar/background-color -> monocai.json:80 -/ui/theme-defs/monocai/styles/scrollbar/color -> monocai.json:79 -/ui/theme-defs/monocai/styles/skewed-time/color -> monocai.json:58 -/ui/theme-defs/monocai/styles/snippet-border/color -> monocai.json:136 -/ui/theme-defs/monocai/styles/table-border/color -> monocai.json:115 -/ui/theme-defs/monocai/styles/table-header/bold -> monocai.json:118 +/ui/theme-defs/monocai/styles/popup/background-color -> monocai.json:75 +/ui/theme-defs/monocai/styles/popup/color -> monocai.json:74 +/ui/theme-defs/monocai/styles/quote-border/background-color -> monocai.json:121 +/ui/theme-defs/monocai/styles/quote-border/color -> monocai.json:120 +/ui/theme-defs/monocai/styles/quoted-text/background-color -> monocai.json:124 +/ui/theme-defs/monocai/styles/scrollbar/background-color -> monocai.json:79 +/ui/theme-defs/monocai/styles/scrollbar/color -> monocai.json:78 +/ui/theme-defs/monocai/styles/skewed-time/color -> monocai.json:57 +/ui/theme-defs/monocai/styles/snippet-border/color -> monocai.json:135 +/ui/theme-defs/monocai/styles/table-border/color -> monocai.json:114 +/ui/theme-defs/monocai/styles/table-header/bold -> monocai.json:117 /ui/theme-defs/monocai/styles/text/background-color -> monocai.json:23 /ui/theme-defs/monocai/styles/text/color -> monocai.json:22 /ui/theme-defs/monocai/styles/warning/bold -> monocai.json:42 /ui/theme-defs/monocai/styles/warning/color -> monocai.json:41 -/ui/theme-defs/monocai/syntax-styles/code-border/background-color -> monocai.json:146 -/ui/theme-defs/monocai/syntax-styles/code-border/color -> monocai.json:145 -/ui/theme-defs/monocai/syntax-styles/comment/color -> monocai.json:157 -/ui/theme-defs/monocai/syntax-styles/diff-add/color -> monocai.json:178 -/ui/theme-defs/monocai/syntax-styles/diff-delete/color -> monocai.json:175 -/ui/theme-defs/monocai/syntax-styles/diff-section/color -> monocai.json:181 -/ui/theme-defs/monocai/syntax-styles/doc-directive/color -> monocai.json:160 -/ui/theme-defs/monocai/syntax-styles/file/color -> monocai.json:193 -/ui/theme-defs/monocai/syntax-styles/keyword/bold -> monocai.json:150 -/ui/theme-defs/monocai/syntax-styles/keyword/color -> monocai.json:149 -/ui/theme-defs/monocai/syntax-styles/number/bold -> monocai.json:196 -/ui/theme-defs/monocai/syntax-styles/quoted-code/background-color -> monocai.json:142 -/ui/theme-defs/monocai/syntax-styles/quoted-code/color -> monocai.json:141 -/ui/theme-defs/monocai/syntax-styles/re-repeat/color -> monocai.json:172 -/ui/theme-defs/monocai/syntax-styles/re-special/color -> monocai.json:169 -/ui/theme-defs/monocai/syntax-styles/spectrogram-high/background-color -> monocai.json:190 -/ui/theme-defs/monocai/syntax-styles/spectrogram-low/background-color -> monocai.json:184 -/ui/theme-defs/monocai/syntax-styles/spectrogram-medium/background-color -> monocai.json:187 -/ui/theme-defs/monocai/syntax-styles/string/bold -> monocai.json:154 -/ui/theme-defs/monocai/syntax-styles/string/color -> monocai.json:153 -/ui/theme-defs/monocai/syntax-styles/symbol/color -> monocai.json:166 -/ui/theme-defs/monocai/syntax-styles/variable/color -> monocai.json:163 +/ui/theme-defs/monocai/syntax-styles/code-border/background-color -> monocai.json:145 +/ui/theme-defs/monocai/syntax-styles/code-border/color -> monocai.json:144 +/ui/theme-defs/monocai/syntax-styles/comment/color -> monocai.json:156 +/ui/theme-defs/monocai/syntax-styles/diff-add/color -> monocai.json:177 +/ui/theme-defs/monocai/syntax-styles/diff-delete/color -> monocai.json:174 +/ui/theme-defs/monocai/syntax-styles/diff-section/color -> monocai.json:180 +/ui/theme-defs/monocai/syntax-styles/doc-directive/color -> monocai.json:159 +/ui/theme-defs/monocai/syntax-styles/file/color -> monocai.json:192 +/ui/theme-defs/monocai/syntax-styles/keyword/bold -> monocai.json:149 +/ui/theme-defs/monocai/syntax-styles/keyword/color -> monocai.json:148 +/ui/theme-defs/monocai/syntax-styles/number/bold -> monocai.json:195 +/ui/theme-defs/monocai/syntax-styles/quoted-code/background-color -> monocai.json:141 +/ui/theme-defs/monocai/syntax-styles/quoted-code/color -> monocai.json:140 +/ui/theme-defs/monocai/syntax-styles/re-repeat/color -> monocai.json:171 +/ui/theme-defs/monocai/syntax-styles/re-special/color -> monocai.json:168 +/ui/theme-defs/monocai/syntax-styles/spectrogram-high/background-color -> monocai.json:189 +/ui/theme-defs/monocai/syntax-styles/spectrogram-low/background-color -> monocai.json:183 +/ui/theme-defs/monocai/syntax-styles/spectrogram-medium/background-color -> monocai.json:186 +/ui/theme-defs/monocai/syntax-styles/string/bold -> monocai.json:153 +/ui/theme-defs/monocai/syntax-styles/string/color -> monocai.json:152 +/ui/theme-defs/monocai/syntax-styles/symbol/color -> monocai.json:165 +/ui/theme-defs/monocai/syntax-styles/variable/color -> monocai.json:162 /ui/theme-defs/monocai/vars/black -> monocai.json:7 /ui/theme-defs/monocai/vars/blue -> monocai.json:11 /ui/theme-defs/monocai/vars/cyan -> monocai.json:13 diff --git a/test/expected/test_sessions.sh_9978aaa475513f9981840e612f853a7707ffcf90.out b/test/expected/test_sessions.sh_9978aaa475513f9981840e612f853a7707ffcf90.out index 3c84ede3..20f826bd 100644 --- a/test/expected/test_sessions.sh_9978aaa475513f9981840e612f853a7707ffcf90.out +++ b/test/expected/test_sessions.sh_9978aaa475513f9981840e612f853a7707ffcf90.out @@ -9,3 +9,4 @@ db schema pretty spectro +gantt diff --git a/test/test_sql_views_vtab.sh b/test/test_sql_views_vtab.sh index c25b5a42..38eb9671 100644 --- a/test/test_sql_views_vtab.sh +++ b/test/test_sql_views_vtab.sh @@ -121,7 +121,7 @@ run_test ${lnav_test} -n \ check_output "delete from lnav_views table works?" <