[gantt] support filtering

pull/1179/head
Tim Stack 10 months ago
parent d4aa72aec2
commit 14e61acda7

@ -479,6 +479,7 @@ add_library(
fstat_vtab.hh
fts_fuzzy_match.hh
gantt_source.hh
gantt_status_source.hh
grep_highlighter.hh
hasher.hh
help_text.hh

@ -215,6 +215,7 @@ noinst_HEADERS = \
fstat_vtab.hh \
fts_fuzzy_match.hh \
gantt_source.hh \
gantt_status_source.hh \
grep_highlighter.hh \
grep_proc.hh \
hasher.hh \

@ -70,4 +70,70 @@ abs_diff(T a, T b)
return a > b ? a - b : b - a;
}
template<typename T>
class clamped {
public:
static clamped from(T value, T min, T max) { return {value, min, max}; }
clamped& operator+=(T rhs)
{
if (rhs < 0) {
return this->operator-=(-rhs);
}
if (this->c_value + rhs < this->c_max) {
this->c_value += rhs;
} else {
this->c_value = this->c_max;
}
return *this;
}
clamped& operator-=(T rhs)
{
if (rhs < 0) {
return this->operator+=(-rhs);
}
if (this->c_value - rhs > this->c_min) {
this->c_value -= rhs;
} else {
this->c_value = this->c_min;
}
return *this;
}
bool available_to_consume(T rhs) const
{
return (this->c_value - rhs > this->c_min);
}
bool try_consume(T rhs)
{
if (this->c_value - rhs > this->c_min) {
this->c_value -= rhs;
return true;
}
return false;
}
operator T() const { return this->c_value; }
bool is_min() const { return this->c_value == this->c_min; }
T get_min() const { return this->c_min; }
T get_max() const { return this->c_max; }
private:
clamped(T value, T min, T max) : c_value(value), c_min(min), c_max(max) {}
T c_value;
T c_min;
T c_max;
};
#endif

@ -138,6 +138,10 @@ files_sub_source::list_input_handle_key(listview_curses& lv, int ch)
auto tss = top_view->get_sub_source();
if (tss != nullptr) {
if (tss != &lss) {
lss.text_filters_changed();
lnav_data.ld_views[LNV_LOG].reload_data();
}
tss->text_filters_changed();
top_view->reload_data();
}

@ -275,12 +275,15 @@ size_t
filter_sub_source::text_line_count()
{
return (lnav_data.ld_view_stack.top() |
[](auto tc) {
text_sub_source* tss = tc->get_sub_source();
filter_stack& fs = tss->get_filters();
return nonstd::make_optional(fs.size());
})
[](auto tc) -> nonstd::optional<size_t> {
text_sub_source* tss = tc->get_sub_source();
if (tss == nullptr) {
return nonstd::nullopt;
}
auto& fs = tss->get_filters();
return nonstd::make_optional(fs.size());
})
.value_or(0);
}

@ -145,10 +145,11 @@ gantt_header_overlay::list_static_overlay(const listview_curses& lv,
gantt_source::gantt_source(textview_curses& log_view,
logfile_sub_source& lss,
plain_text_source& preview_source,
preview_status_source& preview_status_source)
gantt_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)
{
this->tss_supports_filtering = true;
}
std::pair<timeval, timeval>
@ -312,6 +313,9 @@ gantt_source::rebuild_indexes()
this->gs_upper_bound = {};
this->gs_opid_width = 0;
this->gs_total_width = 0;
this->gs_filtered_count = 0;
this->gs_preview_source.clear();
this->gs_preview_status_source.get_description().clear();
auto max_desc_width = size_t{0};
@ -320,6 +324,9 @@ gantt_source::rebuild_indexes()
if (ld->get_file_ptr() == nullptr) {
continue;
}
if (!ld->is_visible()) {
continue;
}
safe::ReadAccess<logfile::safe_opid_map> r_opid_map(
ld->get_file_ptr()->get_opids());
@ -361,7 +368,56 @@ gantt_source::rebuild_indexes()
opid_row{pair.first, pair.second});
}
this->gs_time_order.clear();
size_t filtered_in_count = 0;
for (const auto& filt : this->tss_filters) {
if (!filt->is_enabled()) {
continue;
}
if (filt->get_type() == text_filter::INCLUDE) {
filtered_in_count += 1;
}
}
this->gs_filter_hits = {};
for (const auto& pair : time_order_map) {
std::string full_desc;
for (const auto& desc : pair.second.or_value.otr_description) {
full_desc.append(" ");
full_desc.append(desc.second);
}
shared_buffer sb;
shared_buffer_ref sbr;
sbr.share(sb, full_desc.c_str(), full_desc.length());
if (this->tss_apply_filters) {
auto filtered_in = false;
auto filtered_out = false;
for (const auto& filt : this->tss_filters) {
if (!filt->is_enabled()) {
continue;
}
if (filt->matches(nonstd::nullopt, sbr)) {
this->gs_filter_hits[filt->get_index()] += 1;
switch (filt->get_type()) {
case text_filter::INCLUDE:
filtered_in = true;
break;
case text_filter::EXCLUDE:
filtered_out = true;
break;
default:
break;
}
}
}
if ((filtered_in_count > 0 && !filtered_in) || filtered_out) {
this->gs_filtered_count += 1;
continue;
}
}
if (full_desc.size() > max_desc_width) {
max_desc_width = full_desc.size();
}
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
@ -370,16 +426,11 @@ gantt_source::rebuild_indexes()
{
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;
this->gs_total_width = 22 + this->gs_opid_width + max_desc_width;
this->tss_view->set_needs_update();
}
nonstd::optional<vis_line_t>
@ -475,5 +526,40 @@ gantt_source::text_selection_changed(textview_curses& tc)
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());
" OPID %.*s", row.or_name.length(), row.or_name.data());
auto err_count = row.or_value.get_error_count();
if (err_count == 0) {
this->gs_preview_status_source
.statusview_value_for_field(gantt_status_source::TSF_ERRORS)
.set_value("");
} else if (err_count > 1) {
this->gs_preview_status_source
.statusview_value_for_field(gantt_status_source::TSF_ERRORS)
.set_value("%'d errors", err_count);
} else {
this->gs_preview_status_source
.statusview_value_for_field(gantt_status_source::TSF_ERRORS)
.set_value("%'d error", err_count);
}
this->gs_preview_status_source
.statusview_value_for_field(gantt_status_source::TSF_TOTAL)
.set_value("%'d messages ", row.or_value.get_total_msgs());
}
void
gantt_source::text_filters_changed()
{
this->rebuild_indexes();
}
int
gantt_source::get_filtered_count() const
{
return this->gs_filtered_count;
}
int
gantt_source::get_filtered_count_for(size_t filter_index) const
{
return this->gs_filter_hits[filter_index];
}

@ -30,9 +30,9 @@
#ifndef lnav_gantt_source_hh
#define lnav_gantt_source_hh
#include "gantt_status_source.hh"
#include "logfile_sub_source.hh"
#include "plain_text_source.hh"
#include "preview_status_source.hh"
#include "textview_curses.hh"
class gantt_source
@ -42,7 +42,7 @@ public:
explicit gantt_source(textview_curses& log_view,
logfile_sub_source& lss,
plain_text_source& preview_source,
preview_status_source& preview_status_source);
gantt_status_source& preview_status_source);
size_t text_line_count() override;
@ -63,6 +63,10 @@ public:
void text_selection_changed(textview_curses& tc) override;
void text_filters_changed() override;
int get_filtered_count() const override;
int get_filtered_count_for(size_t filter_index) const override;
nonstd::optional<vis_line_t> row_for_time(
struct timeval time_bucket) override;
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override;
@ -74,7 +78,7 @@ public:
textview_curses& gs_log_view;
logfile_sub_source& gs_lss;
plain_text_source& gs_preview_source;
preview_status_source& gs_preview_status_source;
gantt_status_source& gs_preview_status_source;
ArenaAlloc::Alloc<char> gs_allocator{64 * 1024};
struct opid_description_defs {};
@ -90,6 +94,7 @@ public:
struct opid_row {
string_fragment or_name;
opid_time_range or_value;
std::string or_description;
};
attr_line_t gs_rendered_line;
@ -98,6 +103,8 @@ public:
std::vector<opid_row> gs_time_order;
struct timeval gs_lower_bound {};
struct timeval gs_upper_bound {};
size_t gs_filtered_count{0};
std::array<size_t, logfile_filter_state::MAX_FILTERS> gs_filter_hits{};
};
class gantt_header_overlay : public list_overlay_source {

@ -0,0 +1,82 @@
/**
* 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_status_source_hh
#define lnav_gantt_status_source_hh
#include <string>
#include "statusview_curses.hh"
class gantt_status_source : public status_data_source {
public:
typedef enum {
TSF_TITLE,
TSF_STITCH_TITLE,
TSF_DESCRIPTION,
TSF_TOTAL,
TSF_ERRORS,
TSF__MAX
} field_t;
gantt_status_source()
{
this->tss_fields[TSF_TITLE].set_width(16);
this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE);
this->tss_fields[TSF_TITLE].set_value(" Operation Logs ");
this->tss_fields[TSF_STITCH_TITLE].set_width(2);
this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(
role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
this->tss_fields[TSF_DESCRIPTION].set_share(1);
this->tss_fields[TSF_ERRORS].right_justify(true);
this->tss_fields[TSF_ERRORS].set_role(role_t::VCR_ALERT_STATUS);
this->tss_fields[TSF_ERRORS].set_width(16);
this->tss_fields[TSF_TOTAL].right_justify(true);
this->tss_fields[TSF_TOTAL].set_width(20);
}
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];
};
#endif

@ -49,12 +49,7 @@ not have to manually specify the log file format. The currently
supported formats are: syslog, apache, strace, tcsh history, and
generic log files with timestamps.
Lnav will also display data piped in on the standard input. The
following options are available when doing so:
* `-t` Prepend timestamps to the lines of data being read in
on the standard input.
* `-w file` Write the contents of the standard input to this file.
Lnav will also display data piped in on the standard input.
To automatically execute queries or lnav commands after the files
have been loaded, you can use the following options:

@ -283,7 +283,8 @@ listview_curses::get_overlay_top(vis_line_t row, size_t count, size_t total)
void
listview_curses::do_update()
{
if (this->lv_window == nullptr || this->lv_height == 0) {
if (this->lv_window == nullptr || this->lv_height == 0 || !this->vc_visible)
{
view_curses::do_update();
return;
}

@ -1399,6 +1399,7 @@ looper()
lnav_data.ld_user_message_view.set_window(lnav_data.ld_window);
lnav_data.ld_spectro_details_view.set_title("spectro-details");
lnav_data.ld_spectro_details_view.set_window(lnav_data.ld_window);
lnav_data.ld_spectro_details_view.set_show_scrollbar(true);
lnav_data.ld_spectro_details_view.set_height(5_vl);
@ -1417,6 +1418,13 @@ looper()
lnav_data.ld_spectro_source->ss_exec_context
= &lnav_data.ld_exec_context;
lnav_data.ld_gantt_details_view.set_title("gantt-details");
lnav_data.ld_gantt_details_view.set_window(lnav_data.ld_window);
lnav_data.ld_gantt_details_view.set_show_scrollbar(false);
lnav_data.ld_gantt_details_view.set_height(5_vl);
lnav_data.ld_gantt_details_view.set_sub_source(
&lnav_data.ld_gantt_details_source);
auto top_status_lifetime
= injector::bind<top_status_source>::to_scoped_singleton();
@ -1444,6 +1452,8 @@ looper()
= std::make_unique<spectro_status_source>();
lnav_data.ld_status[LNS_SPECTRO].set_data_source(
lnav_data.ld_spectro_status_source.get());
lnav_data.ld_status[LNS_GANTT].set_data_source(
&lnav_data.ld_gantt_status_source);
lnav_data.ld_match_view.set_show_bottom_border(true);
lnav_data.ld_user_message_view.set_show_bottom_border(true);
@ -1649,6 +1659,7 @@ looper()
lnav_data.ld_match_view.do_update();
lnav_data.ld_preview_view.do_update();
lnav_data.ld_spectro_details_view.do_update();
lnav_data.ld_gantt_details_view.do_update();
lnav_data.ld_user_message_view.do_update();
if (ui_clock::now() >= next_status_update_time) {
echo_views_stmt.execute();
@ -2748,8 +2759,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
auto gantt_view_source
= std::make_shared<gantt_source>(lnav_data.ld_views[LNV_LOG],
lnav_data.ld_log_source,
lnav_data.ld_preview_source,
lnav_data.ld_preview_status_source);
lnav_data.ld_gantt_details_source,
lnav_data.ld_gantt_status_source);
auto gantt_header_source
= std::make_shared<gantt_header_overlay>(gantt_view_source);
lnav_data.ld_views[LNV_GANTT]

@ -55,6 +55,7 @@
#include "file_collection.hh"
#include "files_sub_source.hh"
#include "filter_status_source.hh"
#include "gantt_status_source.hh"
#include "grep_highlighter.hh"
#include "hist_source.hh"
#include "input_dispatcher.hh"
@ -86,6 +87,7 @@ typedef enum {
LNS_DOC,
LNS_PREVIEW,
LNS_SPECTRO,
LNS_GANTT,
LNS__MAX
} lnav_status_t;
@ -189,6 +191,7 @@ struct lnav_data_t {
doc_status_source ld_doc_status_source;
preview_status_source ld_preview_status_source;
std::unique_ptr<spectro_status_source> ld_spectro_status_source;
gantt_status_source ld_gantt_status_source;
bool ld_preview_hidden;
int64_t ld_preview_generation{0};
action_broadcaster<listview_curses> ld_scroll_broadcaster;
@ -214,6 +217,8 @@ struct lnav_data_t {
ld_user_message_expiration;
textview_curses ld_spectro_details_view;
plain_text_source ld_spectro_no_details_source;
textview_curses ld_gantt_details_view;
plain_text_source ld_gantt_details_source;
view_stack<textview_curses> ld_view_stack;
textview_curses* ld_last_view;

@ -278,6 +278,11 @@ rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
}
if (!closed_files.empty()) {
lnav_data.ld_active_files.close_files(closed_files);
auto* gantt_source = lnav_data.ld_views[LNV_GANTT].get_sub_source();
if (gantt_source != nullptr) {
gantt_source->text_filters_changed();
}
}
auto result = lss.rebuild_index(deadline);
@ -352,7 +357,8 @@ rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
}
lnav_data.ld_view_stack.top() | [](auto tc) {
lnav_data.ld_filter_status_source.update_filtered(tc->get_sub_source());
auto* tss = tc->get_sub_source();
lnav_data.ld_filter_status_source.update_filtered(tss);
lnav_data.ld_scroll_broadcaster(tc);
};

@ -586,7 +586,8 @@ com_relative_goto(exec_context& ec,
retval = "info: shifting top by " + std::to_string(line_offset)
+ " lines";
} else {
tc->shift_top(vis_line_t(line_offset), true);
tc->set_selection(tc->get_selection()
+ vis_line_t(line_offset));
retval = "";
}

@ -106,6 +106,9 @@ opid_time_range::operator|=(const opid_time_range& rhs)
if (this->otr_description.size() < rhs.otr_description.size()) {
this->otr_description = rhs.otr_description;
}
for (size_t lpc = 0; lpc < this->otr_level_counts.size(); lpc++) {
this->otr_level_counts[lpc] += rhs.otr_level_counts[lpc];
}
return *this;
}

@ -778,10 +778,10 @@ logfile::rebuild_index(nonstd::optional<ui_clock::time_point> deadline)
.ignore_error()
.has_value())
{
curr_ll->set_mark(true);
while (curr_ll->is_continued()) {
--curr_ll;
}
curr_ll->set_mark(true);
auto line_number = static_cast<uint32_t>(
std::distance(this->begin(), curr_ll));

@ -1959,25 +1959,30 @@ log_location_history::loc_history_forward(vis_line_t current_top)
}
bool
sql_filter::matches(const logfile& lf,
logfile::const_iterator ll,
sql_filter::matches(nonstd::optional<line_source> ls_opt,
const shared_buffer_ref& line)
{
if (!ll->is_message()) {
if (!ls_opt) {
return false;
}
auto ls = ls_opt;
if (!ls->ls_line->is_message()) {
return false;
}
if (this->sf_filter_stmt == nullptr) {
return false;
}
auto lfp = lf.shared_from_this();
auto lfp = ls->ls_file.shared_from_this();
auto ld = this->sf_log_source.find_data_i(lfp);
if (ld == this->sf_log_source.end()) {
return false;
}
auto eval_res
= this->sf_log_source.eval_sql_filter(this->sf_filter_stmt, ld, ll);
auto eval_res = this->sf_log_source.eval_sql_filter(
this->sf_filter_stmt, ld, ls->ls_line);
if (eval_res.unwrapOr(true)) {
return false;
}
@ -2041,8 +2046,8 @@ vis_line_t
logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start,
vis_line_t highest)
{
vis_bookmarks& bm = this->lmg_source.tss_view->get_bookmarks();
bookmark_vector<vis_line_t>& bv = bm[&textview_curses::BM_META];
auto& bm = this->lmg_source.tss_view->get_bookmarks();
auto& bv = bm[&textview_curses::BM_META];
if (bv.empty()) {
return -1_vl;
@ -2053,8 +2058,8 @@ logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start,
void
logfile_sub_source::meta_grepper::grep_next_line(vis_line_t& line)
{
vis_bookmarks& bm = this->lmg_source.tss_view->get_bookmarks();
bookmark_vector<vis_line_t>& bv = bm[&textview_curses::BM_META];
auto& bm = this->lmg_source.tss_view->get_bookmarks();
auto& bv = bm[&textview_curses::BM_META];
auto line_opt = bv.next(vis_line_t(line));
if (!line_opt) {

@ -80,39 +80,6 @@ public:
virtual void index_complete(logfile_sub_source& lss) {}
};
class pcre_filter : public text_filter {
public:
pcre_filter(type_t type,
const std::string& id,
size_t index,
std::shared_ptr<lnav::pcre2pp::code> code)
: text_filter(type, filter_lang_t::REGEX, id, index),
pf_pcre(std::move(code))
{
}
~pcre_filter() override = default;
bool matches(const logfile& lf,
logfile::const_iterator ll,
const shared_buffer_ref& line) override
{
return this->pf_pcre->find_in(line.to_string_fragment())
.ignore_error()
.has_value();
}
std::string to_command() const override
{
return (this->lf_type == text_filter::INCLUDE ? "filter-in "
: "filter-out ")
+ this->lf_id;
}
protected:
std::shared_ptr<lnav::pcre2pp::code> pf_pcre;
};
class sql_filter : public text_filter {
public:
sql_filter(logfile_sub_source& lss,
@ -124,8 +91,7 @@ public:
this->sf_filter_stmt = stmt;
}
bool matches(const logfile& lf,
logfile::const_iterator ll,
bool matches(nonstd::optional<line_source> ls,
const shared_buffer_ref& line) override;
std::string to_command() const override;

@ -91,6 +91,9 @@ plain_text_source::replace_with(const attr_line_t& text_lines)
off += line_len;
}
this->tds_longest_line = this->compute_longest_line();
if (this->tss_view != nullptr) {
this->tss_view->set_needs_update();
}
return *this;
}
@ -103,6 +106,9 @@ plain_text_source::replace_with(const std::vector<std::string>& text_lines)
off += str.length() + 1;
}
this->tds_longest_line = this->compute_longest_line();
if (this->tss_view != nullptr) {
this->tss_view->set_needs_update();
}
return *this;
}
@ -112,6 +118,9 @@ plain_text_source::clear()
this->tds_lines.clear();
this->tds_longest_line = 0;
this->tds_text_format = text_format_t::TF_UNKNOWN;
if (this->tss_view != nullptr) {
this->tss_view->set_needs_update();
}
}
plain_text_source&
@ -120,6 +129,9 @@ plain_text_source::truncate_to(size_t max_lines)
while (this->tds_lines.size() > max_lines) {
this->tds_lines.pop_back();
}
if (this->tss_view != nullptr) {
this->tss_view->set_needs_update();
}
return *this;
}

@ -903,11 +903,11 @@ rl_focus(readline_curses* rc)
void
rl_blur(readline_curses* rc)
{
auto* tc = *lnav_data.ld_view_stack.top();
auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
.get_overlay_source();
fos->fos_contexts.pop();
lnav_data.ld_views[LNV_LOG].set_sync_selection_and_top(
fos->fos_contexts.top().c_show);
tc->set_sync_selection_and_top(fos->fos_contexts.top().c_show);
lnav_data.ld_preview_generation += 1;
}

@ -403,6 +403,12 @@ spectrogram_source::text_attrs_for_line(textview_curses& tc,
}
value_out.emplace_back(line_range(lpc, lpc + 1), VC_ROLE.value(role));
}
auto alt_row_index = row % 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));
}
}
void

@ -82,7 +82,7 @@ text_filter::add_line(logfile_filter_state& lfs,
logfile::const_iterator ll,
const shared_buffer_ref& line)
{
bool match_state = this->matches(*lfs.tfs_logfile, ll, line);
bool match_state = this->matches(line_source{*lfs.tfs_logfile, ll}, line);
if (ll->is_message()) {
this->end_of_message(lfs);
@ -904,8 +904,7 @@ text_time_translator::data_reloaded(textview_curses* tc)
template class bookmark_vector<vis_line_t>;
bool
empty_filter::matches(const logfile& lf,
logfile::const_iterator ll,
empty_filter::matches(nonstd::optional<line_source> ls,
const shared_buffer_ref& line)
{
return false;

@ -89,13 +89,8 @@ enum class filter_lang_t : int {
class text_filter {
public:
typedef enum {
MAYBE,
INCLUDE,
EXCLUDE,
LFT__MAX,
LFT__MASK = (MAYBE | INCLUDE | EXCLUDE)
} type_t;
text_filter(type_t type, filter_lang_t lang, std::string id, size_t index)
@ -124,8 +119,12 @@ public:
void end_of_message(logfile_filter_state& lfs);
virtual bool matches(const logfile& lf,
logfile_const_iterator ll,
struct line_source {
const logfile& ls_file;
logfile_const_iterator ls_line;
};
virtual bool matches(nonstd::optional<line_source> ls,
const shared_buffer_ref& line)
= 0;
@ -150,13 +149,44 @@ public:
{
}
bool matches(const logfile& lf,
logfile_const_iterator ll,
bool matches(nonstd::optional<line_source> ls,
const shared_buffer_ref& line) override;
std::string to_command() const override;
};
class pcre_filter : public text_filter {
public:
pcre_filter(type_t type,
const std::string& id,
size_t index,
std::shared_ptr<lnav::pcre2pp::code> code)
: text_filter(type, filter_lang_t::REGEX, id, index),
pf_pcre(std::move(code))
{
}
~pcre_filter() override = default;
bool matches(nonstd::optional<line_source> ls,
const shared_buffer_ref& line) override
{
return this->pf_pcre->find_in(line.to_string_fragment())
.ignore_error()
.has_value();
}
std::string to_command() const override
{
return (this->lf_type == text_filter::INCLUDE ? "filter-in "
: "filter-out ")
+ this->lf_id;
}
protected:
std::shared_ptr<lnav::pcre2pp::code> pf_pcre;
};
class filter_stack {
public:
using iterator = std::vector<std::shared_ptr<text_filter>>::iterator;

@ -307,6 +307,11 @@ open_pretty_view()
auto* pretty_tc = &lnav_data.ld_views[LNV_PRETTY];
auto* log_tc = &lnav_data.ld_views[LNV_LOG];
auto* text_tc = &lnav_data.ld_views[LNV_TEXT];
if (top_tc != log_tc && top_tc != text_tc) {
return;
}
attr_line_t full_text;
delete pretty_tc->get_sub_source();
@ -571,6 +576,7 @@ handle_winch()
lnav_data.ld_filter_view.set_needs_update();
lnav_data.ld_files_view.set_needs_update();
lnav_data.ld_spectro_details_view.set_needs_update();
lnav_data.ld_gantt_details_view.set_needs_update();
lnav_data.ld_user_message_view.set_needs_update();
return true;
@ -579,27 +585,29 @@ handle_winch()
void
layout_views()
{
unsigned long width, height;
int width, height;
getmaxyx(lnav_data.ld_window, height, width);
int doc_height;
bool doc_side_by_side = width > (90 + 60);
bool preview_status_open
bool preview_open
= !lnav_data.ld_preview_status_source.get_description().empty();
bool filter_status_open = false;
bool filters_supported = false;
auto is_spectro = false;
auto is_gantt = false;
lnav_data.ld_view_stack.top() | [&](auto tc) {
is_spectro = (tc == &lnav_data.ld_views[LNV_SPECTRO]);
is_gantt = (tc == &lnav_data.ld_views[LNV_GANTT]);
text_sub_source* tss = tc->get_sub_source();
auto* tss = tc->get_sub_source();
if (tss == nullptr) {
return;
}
if (tss->tss_supports_filtering) {
filter_status_open = true;
filters_supported = true;
}
};
@ -614,9 +622,9 @@ layout_views()
int preview_height = lnav_data.ld_preview_hidden
? 0
: lnav_data.ld_preview_source.text_line_count();
int match_rows = lnav_data.ld_match_source.text_line_count();
int match_height = std::min((unsigned long) match_rows, (height - 4) / 2);
int match_rows = lnav_data.ld_match_source.text_line_count();
int match_height = std::min(match_rows, (height - 4) / 2);
lnav_data.ld_match_view.set_height(vis_line_t(match_height));
int um_rows = lnav_data.ld_user_message_source.text_line_count();
@ -627,105 +635,127 @@ layout_views()
lnav_data.ld_user_message_source.clear();
um_rows = 0;
}
int um_height = std::min((unsigned long) um_rows, (height - 4) / 2);
auto um_height = std::min(um_rows, (height - 4) / 2);
lnav_data.ld_user_message_view.set_height(vis_line_t(um_height));
if (doc_height + 14
> ((int) height - match_height - um_height - preview_height - 2))
{
preview_height = 0;
preview_status_open = false;
}
if (doc_height + 14 > ((int) height - match_height - um_height - 2)) {
doc_height = lnav_data.ld_doc_source.text_line_count();
if (doc_height + 14 > ((int) height - match_height - um_height - 2)) {
doc_height = 0;
}
}
bool doc_open = doc_height > 0;
bool filters_open = (lnav_data.ld_mode == ln_mode_t::FILTER
auto filters_open = (lnav_data.ld_mode == ln_mode_t::FILTER
|| lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES)
&& !preview_status_open && !doc_open;
bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS);
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES);
int filter_height = filters_open ? 5 : 0;
int bottom_height = (doc_open ? 1 : 0) + doc_height
+ (preview_status_open ? 1 : 0) + preview_height + 1 // bottom status
+ match_height + um_height + lnav_data.ld_rl_view->get_height()
+ (is_spectro && !doc_open ? 5 : 0);
bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS);
for (auto& tc : lnav_data.ld_views) {
tc.set_height(vis_line_t(-(bottom_height + (filter_status_open ? 1 : 0)
+ (filters_open ? 1 : 0) + filter_height)));
}
lnav_data.ld_status[LNS_FILTER].set_visible(filter_status_open);
lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open);
lnav_data.ld_status[LNS_FILTER].set_top(
-(bottom_height + filter_height + 1 + (filters_open ? 1 : 0)));
lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open);
lnav_data.ld_status[LNS_FILTER_HELP].set_top(
-(bottom_height + filter_height + 1));
lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + um_height + 2));
auto bottom_min = std::min(2 + 3, height);
auto bottom = clamped<int>::from(height, bottom_min, height);
bottom -= lnav_data.ld_rl_view->get_height();
lnav_data.ld_rl_view->set_width(width);
bool vis;
vis = bottom.try_consume(lnav_data.ld_match_view.get_height());
lnav_data.ld_match_view.set_y(bottom);
lnav_data.ld_match_view.set_visible(vis);
vis = bottom.try_consume(um_height);
lnav_data.ld_user_message_view.set_y(bottom);
lnav_data.ld_user_message_view.set_visible(vis);
bottom -= 1;
lnav_data.ld_status[LNS_BOTTOM].set_top(bottom);
lnav_data.ld_status[LNS_BOTTOM].set_enabled(!filters_open
&& !breadcrumb_open);
lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height);
lnav_data.ld_status[LNS_DOC].set_visible(doc_open);
lnav_data.ld_status[LNS_PREVIEW].set_top(height - bottom_height
+ (doc_open ? 1 : 0) + doc_height);
lnav_data.ld_status[LNS_PREVIEW].set_visible(preview_status_open);
lnav_data.ld_status[LNS_SPECTRO].set_top(height - bottom_height - 1);
lnav_data.ld_status[LNS_SPECTRO].set_visible(is_spectro);
lnav_data.ld_status[LNS_SPECTRO].set_enabled(lnav_data.ld_mode
== ln_mode_t::SPECTRO_DETAILS);
if (!doc_open || doc_side_by_side) {
lnav_data.ld_doc_view.set_height(vis_line_t(doc_height));
vis = preview_open && bottom.try_consume(preview_height + 1);
lnav_data.ld_preview_view.set_height(vis_line_t(preview_height));
lnav_data.ld_preview_view.set_y(bottom + 1);
lnav_data.ld_preview_view.set_visible(vis);
lnav_data.ld_status[LNS_PREVIEW].set_top(bottom);
lnav_data.ld_status[LNS_PREVIEW].set_visible(vis);
if (doc_side_by_side && doc_height > 0) {
vis = bottom.try_consume(doc_height + 1);
lnav_data.ld_example_view.set_height(vis_line_t(doc_height));
lnav_data.ld_example_view.set_x(90);
lnav_data.ld_example_view.set_y(bottom + 1);
} else if (doc_height > 0 && bottom.available_to_consume(doc_height + 1)) {
lnav_data.ld_example_view.set_height(
vis_line_t(lnav_data.ld_example_source.text_line_count()));
vis = bottom.try_consume(lnav_data.ld_example_view.get_height());
lnav_data.ld_example_view.set_x(0);
lnav_data.ld_example_view.set_y(bottom);
} else {
vis = false;
lnav_data.ld_example_view.set_height(0_vl);
}
lnav_data.ld_example_view.set_visible(vis);
if (doc_side_by_side) {
lnav_data.ld_doc_view.set_height(vis_line_t(doc_height));
lnav_data.ld_doc_view.set_y(bottom + 1);
} else if (doc_height > 0) {
lnav_data.ld_doc_view.set_height(
vis_line_t(lnav_data.ld_doc_source.text_line_count()));
vis = bottom.try_consume(lnav_data.ld_doc_view.get_height() + 1);
lnav_data.ld_doc_view.set_y(bottom + 1);
} else {
vis = false;
}
lnav_data.ld_doc_view.set_y(height - bottom_height + 1);
lnav_data.ld_doc_view.set_visible(vis);
if (!doc_open || doc_side_by_side) {
lnav_data.ld_example_view.set_height(vis_line_t(doc_height));
lnav_data.ld_example_view.set_x(doc_open ? 90 : 0);
lnav_data.ld_example_view.set_y(height - bottom_height + 1);
auto has_doc = lnav_data.ld_example_view.get_height() > 0_vl
|| lnav_data.ld_doc_view.get_height() > 0_vl;
lnav_data.ld_status[LNS_DOC].set_top(bottom);
lnav_data.ld_status[LNS_DOC].set_visible(has_doc && vis);
if (is_gantt) {
vis = bottom.try_consume(lnav_data.ld_gantt_details_view.get_height()
+ 1);
} else {
lnav_data.ld_example_view.set_height(
vis_line_t(lnav_data.ld_example_source.text_line_count()));
lnav_data.ld_example_view.set_x(0);
lnav_data.ld_example_view.set_y(
height - bottom_height + lnav_data.ld_doc_view.get_height() + 1);
vis = false;
}
lnav_data.ld_gantt_details_view.set_y(bottom + 1);
lnav_data.ld_gantt_details_view.set_width(width);
lnav_data.ld_gantt_details_view.set_visible(vis);
lnav_data.ld_status[LNS_GANTT].set_top(bottom);
lnav_data.ld_status[LNS_GANTT].set_visible(vis);
vis = bottom.try_consume(filter_height + (filters_open ? 1 : 0)
+ (filters_supported ? 1 : 0));
lnav_data.ld_filter_view.set_height(vis_line_t(filter_height));
lnav_data.ld_filter_view.set_y(height - bottom_height - filter_height);
lnav_data.ld_filter_view.set_y(bottom + 2);
lnav_data.ld_filter_view.set_width(width);
lnav_data.ld_filter_view.set_visible(filters_open && vis);
lnav_data.ld_files_view.set_height(vis_line_t(filter_height));
lnav_data.ld_files_view.set_y(height - bottom_height - filter_height);
lnav_data.ld_files_view.set_y(bottom + 2);
lnav_data.ld_files_view.set_width(width);
lnav_data.ld_files_view.set_visible(filters_open && vis);
lnav_data.ld_preview_view.set_height(vis_line_t(preview_height));
lnav_data.ld_preview_view.set_y(height - bottom_height + 1
+ (doc_open ? 1 : 0) + doc_height);
lnav_data.ld_user_message_view.set_y(
height - lnav_data.ld_rl_view->get_height() - match_height - um_height);
lnav_data.ld_spectro_details_view.set_y(height - bottom_height);
lnav_data.ld_spectro_details_view.set_height(
is_spectro && !doc_open ? 5_vl : 0_vl);
lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open && vis);
lnav_data.ld_status[LNS_FILTER_HELP].set_top(bottom + 1);
lnav_data.ld_status[LNS_FILTER].set_visible(vis);
lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open);
lnav_data.ld_status[LNS_FILTER].set_top(bottom);
vis = is_spectro && bottom.try_consume(5 + 1);
lnav_data.ld_spectro_details_view.set_y(bottom + 1);
lnav_data.ld_spectro_details_view.set_height(5_vl);
lnav_data.ld_spectro_details_view.set_width(width);
lnav_data.ld_spectro_details_view.set_title("spectro-details");
lnav_data.ld_spectro_details_view.set_visible(vis);
lnav_data.ld_match_view.set_y(height - lnav_data.ld_rl_view->get_height()
- match_height);
lnav_data.ld_rl_view->set_width(width);
lnav_data.ld_status[LNS_SPECTRO].set_top(bottom);
lnav_data.ld_status[LNS_SPECTRO].set_visible(vis);
lnav_data.ld_status[LNS_SPECTRO].set_enabled(lnav_data.ld_mode
== ln_mode_t::SPECTRO_DETAILS);
auto bottom_used = bottom - height;
for (auto& tc : lnav_data.ld_views) {
tc.set_height(vis_line_t(bottom_used));
}
}
void
@ -969,7 +999,7 @@ toggle_view(textview_curses* toggle_tc)
bool
ensure_view(textview_curses* expected_tc)
{
textview_curses* tc = lnav_data.ld_view_stack.top().value_or(nullptr);
auto* tc = lnav_data.ld_view_stack.top().value_or(nullptr);
bool retval = true;
if (tc != expected_tc) {

@ -51,13 +51,7 @@ not have to manually specify the log file format. The currently
supported formats are: syslog, apache, strace, tcsh history, and
generic log files with timestamps.
Lnav will also display data piped in on the standard input. The
following options are available when doing so:
•  -t  Prepend timestamps to the lines of data being read
in on the standard input.
•  -w file  Write the contents of the standard input to
this file.
Lnav will also display data piped in on the standard input.
To automatically execute queries or lnav commands after the files have
been loaded, you can use the following options:

Loading…
Cancel
Save