You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lnav/src/hist_source.hh

411 lines
12 KiB
C++

/**
* Copyright (c) 2007-2012, 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.
*
* @file hist_source.hh
*/
#ifndef hist_source_hh
#define hist_source_hh
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include "base/lnav_log.hh"
#include "mapbox/variant.hpp"
#include "strong_int.hh"
#include "textview_curses.hh"
/** Type for indexes into a group of buckets. */
STRONG_INT_TYPE(int, bucket_group);
/** Type used to differentiate values added to the same row in the histogram */
STRONG_INT_TYPE(int, bucket_type);
struct stacked_bar_chart_base {
virtual ~stacked_bar_chart_base() = default;
struct show_none {
};
struct show_all {
};
struct show_one {
size_t so_index;
explicit show_one(int so_index) : so_index(so_index) {}
};
using show_state = mapbox::util::variant<show_none, show_all, show_one>;
enum class direction {
forward,
backward,
};
};
template<typename T>
class stacked_bar_chart : public stacked_bar_chart_base {
public:
stacked_bar_chart& with_stacking_enabled(bool enabled)
{
this->sbc_do_stacking = enabled;
return *this;
}
stacked_bar_chart& with_attrs_for_ident(const T& ident, int attrs)
{
struct chart_ident& ci = this->find_ident(ident);
ci.ci_attrs = attrs;
return *this;
}
stacked_bar_chart& with_margins(unsigned long left, unsigned long right)
{
this->sbc_left = left;
this->sbc_right = right;
return *this;
}
show_state show_next_ident(direction dir)
{
bool single_ident = this->sbc_idents.size() == 1;
if (this->sbc_idents.empty()) {
return this->sbc_show_state;
}
this->sbc_show_state = this->sbc_show_state.match(
[&](show_none) -> show_state {
switch (dir) {
case direction::forward:
if (single_ident) {
return show_all();
}
return show_one(0);
case direction::backward:
return show_all();
}
return show_all();
},
[&](show_one& one) -> show_state {
switch (dir) {
case direction::forward:
if (one.so_index + 1 == this->sbc_idents.size()) {
return show_all();
}
return show_one(one.so_index + 1);
case direction::backward:
if (one.so_index == 0) {
return show_none();
}
return show_one(one.so_index - 1);
}
return show_all();
},
[&](show_all) -> show_state {
switch (dir) {
case direction::forward:
return show_none();
case direction::backward:
if (single_ident) {
return show_none();
}
return show_one(this->sbc_idents.size() - 1);
}
return show_all();
});
return this->sbc_show_state;
}
void get_ident_to_show(T& ident_out) const
{
this->sbc_show_state.match(
[](const show_none) {},
[](const show_all) {},
[&](const show_one& one) {
ident_out = this->sbc_idents[one.so_index].ci_ident;
});
}
void chart_attrs_for_value(const listview_curses& lc,
int& left,
const T& ident,
double value,
string_attrs_t& value_out) const
{
auto ident_iter = this->sbc_ident_lookup.find(ident);
require(ident_iter != this->sbc_ident_lookup.end());
size_t ident_index = ident_iter->second;
unsigned long width, avail_width;
bucket_stats_t overall_stats;
struct line_range lr;
vis_line_t height;
size_t ident_to_show = this->sbc_show_state.match(
[](const show_none) { return -1; },
[ident_index](const show_all) { return ident_index; },
[](const show_one& one) { return one.so_index; });
if (ident_to_show != ident_index) {
return;
}
lc.get_dimensions(height, width);
for (size_t lpc = 0; lpc < this->sbc_idents.size(); lpc++) {
if (this->sbc_show_state.template is<show_all>()
|| lpc == (size_t) ident_to_show)
{
overall_stats.merge(this->sbc_idents[lpc].ci_stats,
this->sbc_do_stacking);
}
}
if (this->sbc_show_state.template is<show_all>()) {
avail_width = width - this->sbc_idents.size();
} else {
avail_width = width - 1;
}
avail_width -= this->sbc_left + this->sbc_right;
lr.lr_start = left;
const struct chart_ident& ci = this->sbc_idents[ident_index];
int amount;
if (value == 0.0) {
amount = 0;
} else if ((overall_stats.bs_max_value - 0.01) <= value
&& value <= (overall_stats.bs_max_value + 0.01))
{
amount = avail_width;
} else {
double percent
= (value - overall_stats.bs_min_value) / overall_stats.width();
amount = (int) rint(percent * avail_width);
amount = std::max(1, amount);
}
lr.lr_end = left = lr.lr_start + amount;
if (ci.ci_attrs != 0) {
value_out.emplace_back(
lr, VC_STYLE.value(ci.ci_attrs | A_REVERSE));
}
}
void clear()
{
this->sbc_idents.clear();
this->sbc_ident_lookup.clear();
this->sbc_show_state = show_all();
}
void add_value(const T& ident, double amount = 1.0)
{
struct chart_ident& ci = this->find_ident(ident);
ci.ci_stats.update(amount);
}
struct bucket_stats_t {
bucket_stats_t()
: bs_min_value(std::numeric_limits<double>::max()),
bs_max_value(0){};
void merge(const bucket_stats_t& rhs, bool do_stacking)
{
this->bs_min_value = std::min(this->bs_min_value, rhs.bs_min_value);
if (do_stacking) {
this->bs_max_value += rhs.bs_max_value;
} else {
this->bs_max_value
= std::max(this->bs_max_value, rhs.bs_max_value);
}
};
double width() const
{
return std::fabs(this->bs_max_value - this->bs_min_value);
};
void update(double value)
{
this->bs_max_value = std::max(this->bs_max_value, value);
this->bs_min_value = std::min(this->bs_min_value, value);
};
double bs_min_value;
double bs_max_value;
};
const bucket_stats_t& get_stats_for(const T& ident)
{
const chart_ident& ci = this->find_ident(ident);
return ci.ci_stats;
}
protected:
struct chart_ident {
chart_ident(const T& ident) : ci_ident(ident) {}
T ci_ident;
int ci_attrs{0};
bucket_stats_t ci_stats;
};
struct chart_ident& find_ident(const T& ident)
{
typename std::map<T, unsigned int>::iterator iter;
iter = this->sbc_ident_lookup.find(ident);
if (iter == this->sbc_ident_lookup.end()) {
this->sbc_ident_lookup[ident] = this->sbc_idents.size();
this->sbc_idents.push_back(ident);
return this->sbc_idents.back();
}
return this->sbc_idents[iter->second];
}
bool sbc_do_stacking{true};
unsigned long sbc_left{0}, sbc_right{0};
std::vector<struct chart_ident> sbc_idents;
std::map<T, unsigned int> sbc_ident_lookup;
show_state sbc_show_state{show_all()};
};
class hist_source2
: public text_sub_source
, public text_time_translator {
public:
typedef enum {
HT_NORMAL,
HT_WARNING,
HT_ERROR,
HT_MARK,
HT__MAX
} hist_type_t;
hist_source2()
{
this->clear();
}
~hist_source2() override = default;
void init();
void set_time_slice(int64_t slice)
{
this->hs_time_slice = slice;
}
int64_t get_time_slice() const
{
return this->hs_time_slice;
}
size_t text_line_count() override
{
return this->hs_line_count;
}
size_t text_line_width(textview_curses& curses) override
{
return 48 + 8 * 4;
}
void clear();
void add_value(time_t row, hist_type_t htype, double value = 1.0);
void end_of_row();
void text_value_for_line(textview_curses& tc,
int row,
std::string& value_out,
line_flags_t flags) override;
void text_attrs_for_line(textview_curses& tc,
int row,
string_attrs_t& value_out) override;
size_t text_size_for_line(textview_curses& tc,
int row,
line_flags_t flags) override
{
return 0;
}
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override;
nonstd::optional<vis_line_t> row_for_time(
struct timeval tv_bucket) override;
private:
struct hist_value {
double hv_value;
};
struct bucket_t {
time_t b_time;
hist_value b_values[HT__MAX];
};
static const int64_t BLOCK_SIZE = 100;
struct bucket_block {
bucket_block()
{
memset(this->bb_buckets, 0, sizeof(this->bb_buckets));
}
unsigned int bb_used{0};
bucket_t bb_buckets[BLOCK_SIZE];
};
bucket_t& find_bucket(int64_t index);
int64_t hs_time_slice{10 * 60};
int64_t hs_line_count;
int64_t hs_last_bucket;
time_t hs_last_row;
std::map<int64_t, struct bucket_block> hs_blocks;
stacked_bar_chart<hist_type_t> hs_chart;
};
#endif