|
|
|
/**
|
|
|
|
* 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 "lnav.hh"
|
|
|
|
#include "logfile_sub_source.hh"
|
|
|
|
|
|
|
|
class filtered_sub_source
|
|
|
|
: public text_sub_source
|
|
|
|
, public text_time_translator {
|
|
|
|
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_delegate->text_value_for_line(
|
|
|
|
tc, this->fss_lines[line], value_out, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t text_size_for_line(textview_curses& tc,
|
|
|
|
int line,
|
|
|
|
line_flags_t raw) override
|
|
|
|
{
|
|
|
|
return this->fss_delegate->text_size_for_line(
|
|
|
|
tc, this->fss_lines[line], raw);
|
|
|
|
}
|
|
|
|
|
|
|
|
void text_attrs_for_line(textview_curses& tc,
|
|
|
|
int line,
|
|
|
|
string_attrs_t& value_out) override
|
|
|
|
{
|
|
|
|
this->fss_delegate->text_attrs_for_line(
|
|
|
|
tc, this->fss_lines[line], value_out);
|
|
|
|
}
|
|
|
|
|
|
|
|
nonstd::optional<vis_line_t> row_for_time(
|
|
|
|
struct timeval time_bucket) override
|
|
|
|
{
|
|
|
|
return dynamic_cast<text_time_translator*>(this->fss_delegate)
|
|
|
|
->row_for_time(time_bucket);
|
|
|
|
}
|
|
|
|
|
|
|
|
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override
|
|
|
|
{
|
|
|
|
return dynamic_cast<text_time_translator*>(this->fss_delegate)
|
|
|
|
->time_for_row(this->fss_lines[row]);
|
|
|
|
}
|
|
|
|
|
|
|
|
text_sub_source* fss_delegate;
|
|
|
|
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.begin(),
|
|
|
|
values.end(),
|
|
|
|
logline_value_cmp(&this->lsvs_colname));
|
|
|
|
|
|
|
|
if (lv_iter != 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;
|
|
|
|
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.begin(),
|
|
|
|
values.end(),
|
|
|
|
logline_value_cmp(&this->lsvs_colname));
|
|
|
|
|
|
|
|
if (lv_iter != 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());
|
|
|
|
std::vector<logline_value> 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();
|
|
|
|
shared_buffer_ref sbr;
|
|
|
|
|
|
|
|
if (!ll->is_message()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
lf->read_full_message(ll, sbr);
|
|
|
|
sa.clear();
|
|
|
|
values.clear();
|
|
|
|
format->annotate(cl, sbr, sa, values, false);
|
|
|
|
|
|
|
|
auto lv_iter = find_if(values.begin(),
|
|
|
|
values.end(),
|
|
|
|
logline_value_cmp(&this->lsvs_colname));
|
|
|
|
|
|
|
|
if (lv_iter != 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();
|
|
|
|
|
|
|
|
db_label_source& dls = lnav_data.ld_db_row_source;
|
|
|
|
stacked_bar_chart<std::string>& chart = dls.dls_chart;
|
|
|
|
date_time_scanner dts;
|
|
|
|
|
|
|
|
this->dsvs_column_index = dls.column_name_to_index(this->dsvs_colname);
|
|
|
|
|
|
|
|
if (!dls.has_log_time_column()) {
|
|
|
|
this->dsvs_error_msg
|
|
|
|
= "no 'log_time' column found or not in ascending order, "
|
|
|
|
"unable to create spectrogram";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this->dsvs_column_index) {
|
|
|
|
this->dsvs_error_msg = "unknown column -- " + this->dsvs_colname;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dls.dls_headers[this->dsvs_column_index.value()].hm_graphable) {
|
|
|
|
this->dsvs_error_msg = "column is not numeric -- " + this->dsvs_colname;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dls.dls_rows.empty()) {
|
|
|
|
this->dsvs_error_msg = "empty result set";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stacked_bar_chart<std::string>::bucket_stats_t 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) {
|
|
|
|
double value = 0.0;
|
|
|
|
|
|
|
|
sscanf(
|
|
|
|
dls.dls_rows[lpc][this->dsvs_column_index.value()], "%lf", &value);
|
|
|
|
|
|
|
|
row_out.add_value(sr, 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;
|
|
|
|
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) {
|
|
|
|
double value = 0.0;
|
|
|
|
|
|
|
|
sscanf(dls.dls_rows[lpc][this->dsvs_column_index.value()],
|
|
|
|
"%lf",
|
|
|
|
&value);
|
|
|
|
if (range_min <= value && value < range_max) {
|
|
|
|
retval->fss_lines.emplace_back(lpc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
};
|
|
|
|
}
|