lnav/src/hist_source.hh

464 lines
14 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 <map>
#include <cmath>
#include <limits>
#include <string>
#include <vector>
#include "mapbox/variant.hpp"
#include "lnav_log.hh"
#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 {
struct show_none {};
struct show_all {};
struct show_one {
int so_index;
show_one(int so_index) : so_index(so_index) {
}
};
typedef mapbox::util::variant<show_none, show_all, show_one> show_state;
enum class direction {
forward,
backward,
};
};
template<typename T>
class stacked_bar_chart : public stacked_bar_chart_base {
public:
stacked_bar_chart()
: sbc_do_stacking(true), sbc_left(0), sbc_right(0), sbc_show_state(show_all()) {
};
virtual ~stacked_bar_chart() { };
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());
int ident_index = ident_iter->second;
unsigned long width, avail_width;
bucket_stats_t overall_stats;
struct line_range lr;
vis_line_t height;
int 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, &view_curses::VC_STYLE, 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;
unsigned long sbc_left, sbc_right;
std::vector<struct chart_ident> sbc_idents;
std::map<T, unsigned int> sbc_ident_lookup;
show_state sbc_show_state;
};
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() : hs_time_slice(10 * 60) {
this->clear();
};
void init() {
view_colors &vc = view_colors::singleton();
this->hs_chart
.with_attrs_for_ident(HT_NORMAL,
vc.attrs_for_role(view_colors::VCR_TEXT))
.with_attrs_for_ident(HT_WARNING,
vc.attrs_for_role(view_colors::VCR_WARNING))
.with_attrs_for_ident(HT_ERROR,
vc.attrs_for_role(view_colors::VCR_ERROR))
.with_attrs_for_ident(HT_MARK,
vc.attrs_for_role(view_colors::VCR_KEYWORD));
};
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() {
return this->hs_line_count;
};
size_t text_line_width(textview_curses &curses) {
return strlen(LINE_FORMAT) + 8 * 4;
};
void clear() {
this->hs_line_count = 0;
this->hs_last_bucket = -1;
this->hs_last_row = -1;
this->hs_blocks.clear();
this->hs_chart.clear();
this->init();
};
void add_value(time_t row, hist_type_t htype, double value = 1.0) {
require(row >= this->hs_last_row);
row = rounddown(row, this->hs_time_slice);
if (row != this->hs_last_row) {
this->end_of_row();
this->hs_last_bucket += 1;
this->hs_last_row = row;
}
bucket_t &bucket = this->find_bucket(this->hs_last_bucket);
bucket.b_time = row;
bucket.b_values[htype].hv_value += value;
};
void end_of_row() {
if (this->hs_last_bucket >= 0) {
bucket_t &last_bucket = this->find_bucket(this->hs_last_bucket);
for (int lpc = 0; lpc < HT__MAX; lpc++) {
this->hs_chart.add_value(
(const hist_type_t) lpc,
last_bucket.b_values[lpc].hv_value);
}
}
};
void text_value_for_line(textview_curses &tc,
int row,
std::string &value_out,
line_flags_t flags);;
void text_attrs_for_line(textview_curses &tc,
int row,
string_attrs_t &value_out);;
size_t text_size_for_line(textview_curses &tc, int row, line_flags_t flags) {
return 0;
};
struct timeval time_for_row(int row) {
require(row >= 0);
require(row < this->hs_line_count);
bucket_t &bucket = this->find_bucket(row);
return { bucket.b_time, 0 };
};
int row_for_time(struct timeval tv_bucket);;
private:
static const char *LINE_FORMAT;
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() : bb_used(0) {
memset(this->bb_buckets, 0, sizeof(this->bb_buckets));
};
unsigned int bb_used;
bucket_t bb_buckets[BLOCK_SIZE];
};
bucket_t &find_bucket(int64_t index) {
struct bucket_block &bb = this->hs_blocks[index / BLOCK_SIZE];
unsigned int intra_block_index = index % BLOCK_SIZE;
bb.bb_used = std::max(intra_block_index, bb.bb_used);
this->hs_line_count = std::max(this->hs_line_count, index + 1);
return bb.bb_buckets[intra_block_index];
};
int64_t hs_time_slice;
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