summaryrefslogtreecommitdiffstats
path: root/src/spectro_impls.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/spectro_impls.cc518
1 files changed, 518 insertions, 0 deletions
diff --git a/src/spectro_impls.cc b/src/spectro_impls.cc
new file mode 100644
index 0000000..c9f9412
--- /dev/null
+++ b/src/spectro_impls.cc
@@ -0,0 +1,518 @@
+/**
+ * Copyright (c) 2022, 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 "spectro_impls.hh"
+
+#include "base/itertools.hh"
+#include "lnav.hh"
+#include "logfile_sub_source.hh"
+#include "scn/scn.h"
+
+using namespace lnav::roles::literals;
+
+class filtered_sub_source
+ : public text_sub_source
+ , public text_time_translator
+ , public list_overlay_source {
+public:
+ size_t text_line_count() override { return this->fss_lines.size(); }
+
+ void text_value_for_line(textview_curses& tc,
+ int line,
+ std::string& value_out,
+ line_flags_t flags) override
+ {
+ this->fss_lines | lnav::itertools::nth(line)
+ | lnav::itertools::for_each([&](const auto row) {
+ this->fss_delegate->text_value_for_line(
+ tc, *row, value_out, flags);
+ });
+ }
+
+ size_t text_size_for_line(textview_curses& tc,
+ int line,
+ line_flags_t raw) override
+ {
+ return this->fss_lines | lnav::itertools::nth(line)
+ | lnav::itertools::map([&](const auto row) {
+ return this->fss_delegate->text_size_for_line(tc, *row, raw);
+ })
+ | lnav::itertools::unwrap_or(size_t{0});
+ }
+
+ void text_attrs_for_line(textview_curses& tc,
+ int line,
+ string_attrs_t& value_out) override
+ {
+ this->fss_lines | lnav::itertools::nth(line)
+ | lnav::itertools::for_each([&](const auto row) {
+ this->fss_delegate->text_attrs_for_line(tc, *row, value_out);
+ });
+ }
+
+ nonstd::optional<vis_line_t> row_for_time(
+ struct timeval time_bucket) override
+ {
+ return this->fss_time_delegate->row_for_time(time_bucket);
+ }
+
+ nonstd::optional<struct timeval> time_for_row(vis_line_t row) override
+ {
+ return this->fss_lines | lnav::itertools::nth(row)
+ | lnav::itertools::flat_map([this](const auto row) {
+ return this->fss_time_delegate->time_for_row(*row);
+ });
+ }
+
+ bool list_value_for_overlay(const listview_curses& lv,
+ int y,
+ int bottom,
+ vis_line_t line,
+ attr_line_t& value_out) override
+ {
+ if (this->fss_overlay_delegate != nullptr) {
+ return this->fss_overlay_delegate->list_value_for_overlay(
+ lv, y, bottom, line, value_out);
+ }
+ return false;
+ }
+
+ text_sub_source* fss_delegate;
+ text_time_translator* fss_time_delegate;
+ list_overlay_source* fss_overlay_delegate{nullptr};
+ std::vector<vis_line_t> fss_lines;
+};
+
+log_spectro_value_source::log_spectro_value_source(intern_string_t colname)
+ : lsvs_colname(colname)
+{
+ this->update_stats();
+}
+
+void
+log_spectro_value_source::update_stats()
+{
+ auto& lss = lnav_data.ld_log_source;
+
+ this->lsvs_begin_time = 0;
+ this->lsvs_end_time = 0;
+ this->lsvs_stats.clear();
+ for (auto& ls : lss) {
+ auto* lf = ls->get_file_ptr();
+
+ if (lf == nullptr) {
+ continue;
+ }
+
+ auto format = lf->get_format();
+ const auto* stats = format->stats_for_value(this->lsvs_colname);
+
+ if (stats == nullptr) {
+ continue;
+ }
+
+ auto ll = lf->begin();
+
+ if (this->lsvs_begin_time == 0
+ || ll->get_time() < this->lsvs_begin_time)
+ {
+ this->lsvs_begin_time = ll->get_time();
+ }
+ ll = lf->end();
+ --ll;
+ if (ll->get_time() > this->lsvs_end_time) {
+ this->lsvs_end_time = ll->get_time();
+ }
+
+ this->lsvs_found = true;
+ this->lsvs_stats.merge(*stats);
+ }
+
+ if (this->lsvs_begin_time) {
+ time_t filtered_begin_time = lss.find_line(lss.at(0_vl))->get_time();
+ time_t filtered_end_time
+ = lss.find_line(lss.at(vis_line_t(lss.text_line_count() - 1)))
+ ->get_time();
+
+ if (filtered_begin_time > this->lsvs_begin_time) {
+ this->lsvs_begin_time = filtered_begin_time;
+ }
+ if (filtered_end_time < this->lsvs_end_time) {
+ this->lsvs_end_time = filtered_end_time;
+ }
+ }
+}
+
+void
+log_spectro_value_source::spectro_bounds(spectrogram_bounds& sb_out)
+{
+ auto& lss = lnav_data.ld_log_source;
+
+ if (lss.text_line_count() == 0) {
+ return;
+ }
+
+ this->update_stats();
+
+ sb_out.sb_begin_time = this->lsvs_begin_time;
+ sb_out.sb_end_time = this->lsvs_end_time;
+ sb_out.sb_min_value_out = this->lsvs_stats.lvs_min_value;
+ sb_out.sb_max_value_out = this->lsvs_stats.lvs_max_value;
+ sb_out.sb_count = this->lsvs_stats.lvs_count;
+}
+
+void
+log_spectro_value_source::spectro_row(spectrogram_request& sr,
+ spectrogram_row& row_out)
+{
+ auto& lss = lnav_data.ld_log_source;
+ auto begin_line = lss.find_from_time(sr.sr_begin_time).value_or(0_vl);
+ auto end_line
+ = lss.find_from_time(sr.sr_end_time).value_or(lss.text_line_count());
+
+ for (const auto& msg_info : lss.window_at(begin_line, end_line)) {
+ const auto& ll = msg_info.get_logline();
+ if (ll.get_time() >= sr.sr_end_time) {
+ break;
+ }
+
+ const auto& values = msg_info.get_values();
+ auto lv_iter = find_if(values.lvv_values.begin(),
+ values.lvv_values.end(),
+ logline_value_cmp(&this->lsvs_colname));
+
+ if (lv_iter != values.lvv_values.end()) {
+ switch (lv_iter->lv_meta.lvm_kind) {
+ case value_kind_t::VALUE_FLOAT:
+ row_out.add_value(sr, lv_iter->lv_value.d, ll.is_marked());
+ break;
+ case value_kind_t::VALUE_INTEGER: {
+ row_out.add_value(sr, lv_iter->lv_value.i, ll.is_marked());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ row_out.sr_details_source_provider = [this](const spectrogram_request& sr,
+ double range_min,
+ double range_max) {
+ auto& lss = lnav_data.ld_log_source;
+ auto retval = std::make_unique<filtered_sub_source>();
+ auto begin_line = lss.find_from_time(sr.sr_begin_time).value_or(0_vl);
+ auto end_line = lss.find_from_time(sr.sr_end_time)
+ .value_or(lss.text_line_count());
+
+ retval->fss_delegate = &lss;
+ retval->fss_time_delegate = &lss;
+ retval->fss_overlay_delegate = nullptr;
+ for (const auto& msg_info : lss.window_at(begin_line, end_line)) {
+ const auto& ll = msg_info.get_logline();
+ if (ll.get_time() >= sr.sr_end_time) {
+ break;
+ }
+
+ const auto& values = msg_info.get_values();
+ auto lv_iter = find_if(values.lvv_values.begin(),
+ values.lvv_values.end(),
+ logline_value_cmp(&this->lsvs_colname));
+
+ if (lv_iter != values.lvv_values.end()) {
+ switch (lv_iter->lv_meta.lvm_kind) {
+ case value_kind_t::VALUE_FLOAT:
+ if (range_min <= lv_iter->lv_value.d
+ && lv_iter->lv_value.d < range_max)
+ {
+ retval->fss_lines.emplace_back(
+ msg_info.get_vis_line());
+ }
+ break;
+ case value_kind_t::VALUE_INTEGER:
+ if (range_min <= lv_iter->lv_value.i
+ && lv_iter->lv_value.i < range_max)
+ {
+ retval->fss_lines.emplace_back(
+ msg_info.get_vis_line());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return retval;
+ };
+}
+
+void
+log_spectro_value_source::spectro_mark(textview_curses& tc,
+ time_t begin_time,
+ time_t end_time,
+ double range_min,
+ double range_max)
+{
+ // XXX need to refactor this and the above method
+ auto& log_tc = lnav_data.ld_views[LNV_LOG];
+ auto& lss = lnav_data.ld_log_source;
+ vis_line_t begin_line = lss.find_from_time(begin_time).value_or(0_vl);
+ vis_line_t end_line
+ = lss.find_from_time(end_time).value_or(lss.text_line_count());
+ logline_value_vector values;
+ string_attrs_t sa;
+
+ for (vis_line_t curr_line = begin_line; curr_line < end_line; ++curr_line) {
+ content_line_t cl = lss.at(curr_line);
+ std::shared_ptr<logfile> lf = lss.find(cl);
+ auto ll = lf->begin() + cl;
+ auto format = lf->get_format();
+
+ if (!ll->is_message()) {
+ continue;
+ }
+
+ values.clear();
+ lf->read_full_message(ll, values.lvv_sbr);
+ values.lvv_sbr.erase_ansi();
+ sa.clear();
+ format->annotate(cl, sa, values, false);
+
+ auto lv_iter = find_if(values.lvv_values.begin(),
+ values.lvv_values.end(),
+ logline_value_cmp(&this->lsvs_colname));
+
+ if (lv_iter != values.lvv_values.end()) {
+ switch (lv_iter->lv_meta.lvm_kind) {
+ case value_kind_t::VALUE_FLOAT:
+ if (range_min <= lv_iter->lv_value.d
+ && lv_iter->lv_value.d <= range_max)
+ {
+ log_tc.toggle_user_mark(&textview_curses::BM_USER,
+ curr_line);
+ }
+ break;
+ case value_kind_t::VALUE_INTEGER:
+ if (range_min <= lv_iter->lv_value.i
+ && lv_iter->lv_value.i <= range_max)
+ {
+ log_tc.toggle_user_mark(&textview_curses::BM_USER,
+ curr_line);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+db_spectro_value_source::db_spectro_value_source(std::string colname)
+ : dsvs_colname(std::move(colname))
+{
+ this->update_stats();
+}
+
+void
+db_spectro_value_source::update_stats()
+{
+ this->dsvs_begin_time = 0;
+ this->dsvs_end_time = 0;
+ this->dsvs_stats.clear();
+
+ auto& dls = lnav_data.ld_db_row_source;
+ auto& chart = dls.dls_chart;
+
+ this->dsvs_column_index = dls.column_name_to_index(this->dsvs_colname);
+
+ if (!dls.has_log_time_column()) {
+ if (dls.dls_time_column_invalidated_at) {
+ static const auto order_by_help = attr_line_t()
+ .append("ORDER BY"_keyword)
+ .append(" ")
+ .append("log_time"_variable)
+ .append(" ")
+ .append("ASC"_keyword);
+
+ this->dsvs_error_msg
+ = lnav::console::user_message::error(
+ "Cannot generate spectrogram for database results")
+ .with_reason(
+ attr_line_t()
+ .append("The ")
+ .append_quoted("log_time"_variable)
+ .appendf(
+ FMT_STRING(" column is not in ascending "
+ "order between rows {} and {}"),
+ dls.dls_time_column_invalidated_at.value()
+ - 1,
+ dls.dls_time_column_invalidated_at.value()))
+ .with_note(
+ attr_line_t("An ascending ")
+ .append_quoted("log_time"_variable)
+ .append(
+ " column is needed to render a spectrogram"))
+ .with_help(attr_line_t("Add an ")
+ .append_quoted(order_by_help)
+ .append(" clause to your ")
+ .append("SELECT"_keyword)
+ .append(" statement"));
+ } else {
+ this->dsvs_error_msg
+ = lnav::console::user_message::error(
+ "Cannot generate spectrogram for database results")
+ .with_reason(
+ attr_line_t()
+ .append("No ")
+ .append_quoted("log_time"_variable)
+ .append(" column found in the result set"))
+ .with_note(
+ attr_line_t("An ascending ")
+ .append_quoted("log_time"_variable)
+ .append(
+ " column is needed to render a spectrogram"))
+ .with_help(
+ attr_line_t("Include a ")
+ .append_quoted("log_time"_variable)
+ .append(" column in your ")
+ .append(" statement. Use an ")
+ .append("AS"_keyword)
+ .append(
+ " directive to alias a computed timestamp"));
+ }
+ return;
+ }
+
+ if (!this->dsvs_column_index) {
+ this->dsvs_error_msg
+ = lnav::console::user_message::error(
+ "Cannot generate spectrogram for database results")
+ .with_reason(attr_line_t("unknown column -- ")
+ .append_quoted(lnav::roles::variable(
+ this->dsvs_colname)))
+ .with_help("Expecting a numeric column to visualize");
+ return;
+ }
+
+ if (!dls.dls_headers[this->dsvs_column_index.value()].hm_graphable) {
+ this->dsvs_error_msg
+ = lnav::console::user_message::error(
+ "Cannot generate spectrogram for database results")
+ .with_reason(attr_line_t()
+ .append_quoted(lnav::roles::variable(
+ this->dsvs_colname))
+ .append(" is not a numeric column"))
+ .with_help("Only numeric columns can be visualized");
+ return;
+ }
+
+ if (dls.dls_rows.empty()) {
+ this->dsvs_error_msg
+ = lnav::console::user_message::error(
+ "Cannot generate spectrogram for database results")
+ .with_reason("Result set is empty");
+ return;
+ }
+
+ auto bs = chart.get_stats_for(this->dsvs_colname);
+
+ this->dsvs_begin_time = dls.dls_time_column.front().tv_sec;
+ this->dsvs_end_time = dls.dls_time_column.back().tv_sec;
+ this->dsvs_stats.lvs_min_value = bs.bs_min_value;
+ this->dsvs_stats.lvs_max_value = bs.bs_max_value;
+ this->dsvs_stats.lvs_count = dls.dls_rows.size();
+}
+
+void
+db_spectro_value_source::spectro_bounds(spectrogram_bounds& sb_out)
+{
+ auto& dls = lnav_data.ld_db_row_source;
+
+ if (dls.text_line_count() == 0) {
+ return;
+ }
+
+ this->update_stats();
+
+ sb_out.sb_begin_time = this->dsvs_begin_time;
+ sb_out.sb_end_time = this->dsvs_end_time;
+ sb_out.sb_min_value_out = this->dsvs_stats.lvs_min_value;
+ sb_out.sb_max_value_out = this->dsvs_stats.lvs_max_value;
+ sb_out.sb_count = this->dsvs_stats.lvs_count;
+}
+
+void
+db_spectro_value_source::spectro_row(spectrogram_request& sr,
+ spectrogram_row& row_out)
+{
+ auto& dls = lnav_data.ld_db_row_source;
+ auto begin_row = dls.row_for_time({sr.sr_begin_time, 0}).value_or(0_vl);
+ auto end_row
+ = dls.row_for_time({sr.sr_end_time, 0}).value_or(dls.dls_rows.size());
+
+ for (auto lpc = begin_row; lpc < end_row; ++lpc) {
+ auto scan_res = scn::scan_value<double>(scn::string_view{
+ dls.dls_rows[lpc][this->dsvs_column_index.value()]});
+
+ if (scan_res) {
+ row_out.add_value(sr, scan_res.value(), false);
+ }
+ }
+
+ row_out.sr_details_source_provider = [this](const spectrogram_request& sr,
+ double range_min,
+ double range_max) {
+ auto& dls = lnav_data.ld_db_row_source;
+ auto retval = std::make_unique<filtered_sub_source>();
+
+ retval->fss_delegate = &dls;
+ retval->fss_time_delegate = &dls;
+ retval->fss_overlay_delegate = &lnav_data.ld_db_overlay;
+ auto begin_row = dls.row_for_time({sr.sr_begin_time, 0}).value_or(0_vl);
+ auto end_row = dls.row_for_time({sr.sr_end_time, 0})
+ .value_or(dls.dls_rows.size());
+
+ for (auto lpc = begin_row; lpc < end_row; ++lpc) {
+ auto scan_res = scn::scan_value<double>(scn::string_view{
+ dls.dls_rows[lpc][this->dsvs_column_index.value()]});
+ if (!scan_res) {
+ continue;
+ }
+ auto value = scan_res.value();
+ if ((range_min == value)
+ || (range_min < value && value < range_max))
+ {
+ retval->fss_lines.emplace_back(lpc);
+ }
+ }
+
+ return retval;
+ };
+}