/** * 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 #include #include #include #include #include "mapbox/variant.hpp" #include "lnav_log.hh" #include "strong_int.hh" #include "textview_curses.hh" typedef float bucket_count_t; /** 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_state; enum class direction { forward, backward, }; }; template 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() || 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()) { 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.push_back(string_attr(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::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::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 sbc_idents; std::map 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) { bucket_t &bucket = this->find_bucket(row); struct tm bucket_tm; char tm_buffer[128]; char line[256]; if (gmtime_r(&bucket.b_time, &bucket_tm) != NULL) { strftime(tm_buffer, sizeof(tm_buffer), " %a %b %d %H:%M:%S ", &bucket_tm); } else { log_error("no time?"); tm_buffer[0] = '\0'; } snprintf(line, sizeof(line), LINE_FORMAT, (int) rint(bucket.b_values[HT_NORMAL].hv_value), (int) rint(bucket.b_values[HT_ERROR].hv_value), (int) rint(bucket.b_values[HT_WARNING].hv_value), (int) rint(bucket.b_values[HT_MARK].hv_value)); value_out.clear(); value_out.append(tm_buffer); value_out.append(line); }; void text_attrs_for_line(textview_curses &tc, int row, string_attrs_t &value_out) { bucket_t &bucket = this->find_bucket(row); int left = 0; for (int lpc = 0; lpc < HT__MAX; lpc++) { this->hs_chart.chart_attrs_for_value( tc, left, (const hist_type_t) lpc, bucket.b_values[lpc].hv_value, 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) { std::map::iterator iter; int retval = 0; time_t time_bucket = rounddown(tv_bucket.tv_sec, this->hs_time_slice); for (iter = this->hs_blocks.begin(); iter != this->hs_blocks.end(); ++iter) { struct bucket_block &bb = iter->second; if (time_bucket < bb.bb_buckets[0].b_time) { break; } if (time_bucket > bb.bb_buckets[bb.bb_used].b_time) { retval += bb.bb_used + 1; continue; } for (unsigned int lpc = 0; lpc <= bb.bb_used; lpc++, retval++) { if (time_bucket <= bb.bb_buckets[lpc].b_time) { return retval; } } } return retval; }; 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 unsigned int 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 / this->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 hs_blocks; stacked_bar_chart hs_chart; }; #endif