"Fossies" - the Fresh Open Source Software Archive

Member "tcpflow-1.6.1/src/netviz/time_histogram_view.cpp" (19 Feb 2021, 19386 Bytes) of package /linux/misc/tcpflow-1.6.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "time_histogram_view.cpp" see the Fossies "Dox" file reference documentation.

    1 /**
    2  * time_histogram_view.cpp: 
    3  * Make fancy time histograms
    4  *
    5  * This source file is public domain, as it is not based on the original tcpflow.
    6  *
    7  * Author: Michael Shick <mike@shick.in>
    8  *
    9  */
   10 
   11 #include "config.h"
   12 
   13 #ifdef HAVE_LIBCAIRO
   14 
   15 #include "time_histogram_view.h"
   16 
   17 time_histogram_view::time_histogram_view(const time_histogram &histogram_,
   18         const colormap_t &port_colors_, const rgb_t &default_color_,
   19         const rgb_t &cdf_color_) :
   20     histogram(histogram_), port_colors(port_colors_), default_color(default_color_),
   21     cdf_color(cdf_color_), bar_time_unit(), bar_time_value(), bar_time_remainder()
   22 {
   23     title = "";
   24     subtitle = "";
   25     pad_left_factor = 0.2;
   26     pad_top_factor = 0.1;
   27     y_tick_font_size = 5.0;
   28     right_tick_font_size = 6.0;
   29     x_axis_font_size = 8.0;
   30     x_axis_decoration = plot_view::AXIS_SPAN_STOP;
   31     y_label = "";
   32 }
   33 
   34 const uint8_t time_histogram_view::y_tick_count = 5;
   35 const double time_histogram_view::bar_space_factor = 1.2;
   36 const double time_histogram_view::cdf_line_width = 0.5;
   37 const std::vector<time_histogram_view::time_unit> time_histogram_view::time_units =
   38         time_histogram_view::build_time_units();
   39 const std::vector<time_histogram_view::si_prefix> time_histogram_view::si_prefixes =
   40         time_histogram_view::build_si_prefixes();
   41 const double time_histogram_view::blank_bar_line_width = 0.25;
   42 const time_histogram_view::rgb_t time_histogram_view::blank_bar_line_color(0.0, 0.0, 0.0);
   43 const double time_histogram_view::bar_label_font_size = 6.0;
   44 const double time_histogram_view::bar_label_width_factor = 0.8;
   45 const time_histogram_view::rgb_t time_histogram_view::bar_label_normal_color(0.0, 0.0, 0.0);
   46 const time_histogram_view::rgb_t time_histogram_view::bar_label_highlight_color(0.86, 0.08, 0.24);
   47 
   48 void time_histogram_view::render(cairo_t *cr, const bounds_t &bounds)
   49 {
   50     //
   51     // create x label based on duration of capture
   52     //
   53     uint64_t bar_interval = histogram.usec_per_bucket() / (1000 * 1000);
   54     // add a second to duration; considering a partial second a second makes
   55     // edge cases look nicer
   56     time_t duration = histogram.end_date() - histogram.start_date() + 1;
   57     if(histogram.packet_count() == 0) {
   58         x_label = "no packets received";
   59         x_axis_decoration = plot_view::AXIS_SPAN_STOP;
   60     }
   61     else {
   62         std::stringstream ss;
   63         // how long does is the total capture?
   64         if(duration < 1) {
   65             ss << "<1 second";
   66         }
   67         else {
   68             // the total time is represented by the two (or one) coursest appropriate units
   69             // example:
   70             //     5 hours, 10 minutes
   71             //     58 seconds
   72             // but never:
   73             //     5 hours. 10 minutes, 30 seconds
   74 
   75             // break the duration down into its constituent parts
   76             std::vector<uint64_t> duration_values;
   77             std::vector<std::string> duration_names;
   78             int remainder = duration;
   79             for(std::vector<time_unit>::const_reverse_iterator it = time_units.rbegin();
   80                     it != time_units.rend(); it++) {
   81 
   82                 duration_values.push_back(remainder / it->seconds);
   83                 duration_names.push_back(it->name);
   84                 remainder %= it->seconds;
   85             }
   86 
   87             int print_count = 0;
   88             // find how many time units are worth printing (for comma insertion)
   89             for(std::vector<uint64_t>::const_iterator it = duration_values.begin();
   90                     it != duration_values.end(); it++) {
   91                 if(*it > 0) {
   92                     print_count++;
   93                 }
   94                 // if we've seen a nonzero unit, and now a zero unit, abort because skipping
   95                 // a unit is weird (2 months, 1 second)
   96                 else if(print_count > 0) {
   97                     break;
   98                 }
   99             }
  100 
  101             // work back through the values and print the two coursest nonzero
  102             print_count = std::min(print_count, 2);
  103             int printed = 0;
  104             for(size_t ii = 0; ii < time_units.size(); ii++) {
  105                 std::string name = duration_names.at(ii);
  106                 uint64_t value = duration_values.at(ii);
  107 
  108                 // skip over insignificant units
  109                 if(value == 0 && printed == 0) {
  110                     continue;
  111                 }
  112                 printed++;
  113 
  114                 // don't actually print intermediate zero values (no 3 hours, 0 minutes, 30 seconds)
  115                 if(value > 0) {
  116                     ss << value << " " << name;
  117                 }
  118                 if(value > 1) {
  119                     ss << "s";
  120                 }
  121                 if(printed < print_count) {
  122                     ss << ", ";
  123                 }
  124 
  125                 if(printed == print_count) {
  126                     break;
  127                 }
  128             }
  129         }
  130 
  131         // how long does each bar represent?
  132         if(bar_interval < 1 && duration >= 1) {
  133             ss << " (<1 second intervals)";
  134         }
  135         else if(bar_interval >= 1) {
  136             for(std::vector<time_unit>::const_iterator it = time_units.begin();
  137                     it != time_units.end(); it++) {
  138                 
  139                 if(it + 1 == time_units.end() || bar_interval < (it+1)->seconds) {
  140                     bar_time_unit = it->name;
  141                     bar_time_value = bar_interval / it->seconds;
  142                     bar_time_remainder = bar_interval % it->seconds;
  143                     break;
  144                 }
  145 
  146             }
  147 
  148             ss << " (";
  149             if(bar_time_remainder != 0) {
  150                 ss << "~";
  151             }
  152             ss << bar_time_value << " " << bar_time_unit << " intervals)";
  153         }
  154         x_label = ss.str();
  155     }
  156 
  157     //
  158     // choose y axis tick labels
  159     //
  160 
  161     // scale raw bucket totals
  162 
  163     uint8_t unit_log_1000 = (uint8_t) (log(histogram.tallest_bar()) / log(1000));
  164     if(unit_log_1000 >= si_prefixes.size()) {
  165         unit_log_1000 = 0;
  166     }
  167     si_prefix unit = si_prefixes.at(unit_log_1000);
  168     double y_scale_range = histogram.tallest_bar() / (double) unit.magnitude;
  169     double y_scale_interval = y_scale_range / (y_tick_count - 1);
  170 
  171     uint64_t next_value = 0;
  172     for(int ii = 0; ii < y_tick_count; ii++) {
  173         uint64_t value = next_value;
  174         double next_raw_value = (ii + 1) * y_scale_interval;
  175         next_value = (uint64_t) floor(next_raw_value + 0.5);
  176 
  177         if(value == next_value && ii < y_tick_count - 1) {
  178             continue;
  179         }
  180 
  181         std::string label = ssprintf("%d %sB", value, unit.prefix.c_str());
  182 
  183     y_tick_labels.push_back(label);
  184     }
  185 
  186     right_tick_labels.push_back("0%");
  187     right_tick_labels.push_back("100%");
  188 
  189     plot_view::render(cr, bounds);
  190 }
  191 
  192 void time_histogram_view::render_data(cairo_t *cr, const bounds_t &bounds)
  193 {
  194     size_t bars = histogram.non_sparse_size();
  195     double bar_allocation = bounds.width / (double) bars; // bar width with spacing
  196     double bar_width = bar_allocation / bar_space_factor; // bar width as rendered
  197     double bar_leading_pad = (bar_allocation - bar_width) / 2.0;
  198     time_histogram::histogram_map::buckets_t::const_iterator it = histogram.begin();
  199 
  200     if(it == histogram.end()) {
  201         return;
  202     }
  203 
  204     uint32_t first_offset = it->first;
  205     double tallest_bar = (double) histogram.tallest_bar();
  206 
  207     for(; it != histogram.end(); it++) {
  208         double bar_height = (double) it->second->sum() / tallest_bar * bounds.height;
  209         double bar_x = bounds.x + (it->first - first_offset) * bar_allocation + bar_leading_pad;
  210         double bar_y = bounds.y + (bounds.height - bar_height);
  211         bounds_t bar_bounds(bar_x, bar_y, bar_width, bar_height);
  212 
  213         bucket_view bar(*it->second, port_colors, default_color);
  214 
  215         bar.render(cr, bar_bounds);
  216     }
  217 
  218     unsigned bar_label_numeric = 0;
  219     int distinct_label_count = 0;
  220     // choose initial bar value
  221     if(bar_time_unit.length() > 0) {
  222         time_t start = histogram.start_date();
  223         struct tm start_time = *localtime(&start);
  224         if(bar_time_unit == SECOND_NAME) {
  225             bar_label_numeric = start_time.tm_sec;
  226             distinct_label_count = 60;
  227         }
  228         else if(bar_time_unit == MINUTE_NAME) {
  229             bar_label_numeric = start_time.tm_min;
  230             distinct_label_count = 60;
  231         }
  232         else if(bar_time_unit == HOUR_NAME) {
  233             bar_label_numeric = start_time.tm_hour;
  234             distinct_label_count = 24;
  235         }
  236         else if(bar_time_unit == DAY_NAME) {
  237             bar_label_numeric = start_time.tm_wday;
  238             distinct_label_count = 7;
  239         }
  240         else if(bar_time_unit == MONTH_NAME) {
  241             bar_label_numeric = start_time.tm_mon;
  242             distinct_label_count = 12;
  243         }
  244         else if(bar_time_unit == YEAR_NAME) {
  245             bar_label_numeric = start_time.tm_year;
  246         }
  247         // snap label to same alignment of histogram bars
  248         bar_label_numeric -= (bar_label_numeric % bar_time_value);
  249     }
  250     // create bar lables so an appropriate font size can be selected
  251     std::vector<std::string> bar_labels;
  252     std::vector<rgb_t> bar_label_colors;
  253     // if bars are thinner than 10pt, thin out the bar labels appropriately
  254     int label_every_n_bars = ((int) (10.0 / bar_allocation)) + 1;
  255     unsigned label_bars_offset = 0;
  256     // find the offset that will cause the '00' label to appear
  257     if(distinct_label_count > 0) {
  258         label_bars_offset = ((distinct_label_count - bar_label_numeric) %
  259                 (bar_time_value * label_every_n_bars)) / bar_time_value;
  260     }
  261     bar_label_numeric += (label_bars_offset * bar_time_value);
  262     double widest_bar_label = 0;
  263     double tallest_bar_label = 0;
  264     cairo_set_font_size(cr, bar_label_font_size);
  265     for(size_t ii = 0; ii < bars; ii++) {
  266         if(ii % label_every_n_bars != label_bars_offset) {
  267             continue;
  268         }
  269         rgb_t bar_label_color;
  270         std::string bar_label = next_bar_label(bar_time_unit, bar_label_numeric,
  271                 bar_time_value * label_every_n_bars, bar_label_color);
  272         cairo_text_extents_t label_extents;
  273         cairo_text_extents(cr, bar_label.c_str(), &label_extents);
  274         if(label_extents.width > widest_bar_label) {
  275             widest_bar_label = label_extents.width;
  276         }
  277         if(label_extents.height > tallest_bar_label) {
  278             tallest_bar_label = label_extents.height;
  279         }
  280         // add to list for later rendering
  281         bar_labels.push_back(bar_label);
  282         bar_label_colors.push_back(bar_label_color);
  283     }
  284     // don't let labels be wider than bars
  285     double safe_bar_label_font_size = bar_label_font_size;
  286     double bar_label_descent = tallest_bar_label * 1.75;
  287     double target_width = bar_width * bar_label_width_factor;
  288     if(widest_bar_label > target_width) {
  289         double factor = target_width / widest_bar_label;
  290         safe_bar_label_font_size *= factor;
  291         bar_label_descent *= factor;
  292     }
  293     // if we're skipping bars for labelling, increase the label size appropriately
  294     double label_size_multiplier = pow(1.2, (double) (label_every_n_bars - 1));
  295     safe_bar_label_font_size *= label_size_multiplier;
  296     bar_label_descent *= label_size_multiplier;
  297 
  298 
  299     // CDF and bar labels
  300     double accumulator = 0.0;
  301     double histogram_sum = (double) histogram.packet_count();
  302     cairo_move_to(cr, bounds.x, bounds.y + bounds.height);
  303     for(size_t ii = 0; ii < bars; ii++) {
  304         const time_histogram::bucket bkt = histogram.at(ii + first_offset);
  305         accumulator += (double) bkt.sum() / histogram_sum;
  306 
  307         double x = bounds.x + ii * bar_allocation;
  308         double next_x = x + bar_allocation;
  309         double y = bounds.y + (1.0 - accumulator) * bounds.height;
  310 
  311         // don't draw over the left-hand y axis
  312         if(ii == 0) {
  313             cairo_move_to(cr, x, y);
  314         }
  315         else {
  316             cairo_line_to(cr, x, y);
  317         }
  318         cairo_line_to(cr, next_x, y);
  319 
  320         // draw bar label
  321         if(bar_time_unit.length() > 0 && bar_time_remainder == 0 &&
  322                 ii % label_every_n_bars == label_bars_offset) {
  323             std::string label = bar_labels.at(ii / label_every_n_bars);
  324             rgb_t color = bar_label_colors.at(ii / label_every_n_bars);
  325             cairo_set_font_size(cr, safe_bar_label_font_size);
  326             cairo_set_source_rgb(cr, color.r, color.g, color.b);
  327             cairo_text_extents_t label_extents;
  328             cairo_text_extents(cr, label.c_str(), &label_extents);
  329             double label_x = x + ((bar_allocation - label_extents.width) / 2.0);
  330             double label_y = bounds.y + bounds.height + bar_label_descent;
  331             cairo_move_to(cr, label_x, label_y);
  332             cairo_show_text(cr, label.c_str());
  333 
  334             // move back to appropriate place for next CDF step
  335             cairo_move_to(cr, next_x, y);
  336         }
  337     }
  338     cairo_set_source_rgb(cr, cdf_color.r, cdf_color.g, cdf_color.b);
  339     cairo_set_line_width(cr, cdf_line_width);
  340     cairo_stroke(cr);
  341 }
  342 
  343 // create a new bar label based on numeric_label, then increment numeric_label
  344 // by delta example: when invoked with ("day", 0, 2), "S" for sunday is
  345 // returned and numeric_label is updated to 2 which will return "T" for tuesday
  346 // next time
  347 std::string time_histogram_view::next_bar_label(const std::string &unit, unsigned &numeric_label, unsigned delta,
  348         rgb_t &label_color)
  349 {
  350     std::string output;
  351     if(numeric_label < delta) {
  352         label_color = bar_label_highlight_color;
  353     }
  354     else {
  355         label_color = bar_label_normal_color;
  356     }
  357     if(unit == SECOND_NAME || unit == MINUTE_NAME) {
  358         output = ssprintf("%02d", numeric_label);
  359         numeric_label = (numeric_label + delta) % 60;
  360     }
  361     else if(unit == HOUR_NAME) {
  362         output = ssprintf("%02d", numeric_label);
  363         numeric_label = (numeric_label + delta) % 24;
  364     }
  365     else if(unit == DAY_NAME) {
  366         label_color = bar_label_normal_color;
  367         switch(numeric_label) {
  368             case 6:
  369             case 0:
  370                 label_color = bar_label_highlight_color;
  371                 output = "S"; break;
  372             case 1:
  373                 output = "M"; break;
  374             case 2:
  375                 output = "T"; break;
  376             case 3:
  377                 output = "W"; break;
  378             case 4:
  379                 output = "R"; break;
  380             case 5:
  381                 output = "F"; break;
  382         }
  383         numeric_label = (numeric_label + delta) % 7;
  384     }
  385     else if(unit == MONTH_NAME) {
  386         switch(numeric_label) {
  387             case 0:
  388                 output = "Jan"; break;
  389             case 1:
  390                 output = "Feb"; break;
  391             case 2:
  392                 output = "Mar"; break;
  393             case 3:
  394                 output = "Apr"; break;
  395             case 4:
  396                 output = "May"; break;
  397             case 5:
  398                 output = "Jun"; break;
  399             case 6:
  400                 output = "Jul"; break;
  401             case 7:
  402                 output = "Aug"; break;
  403             case 8:
  404                 output = "Sep"; break;
  405             case 9:
  406                 output = "Oct"; break;
  407             case 10:
  408                 output = "Nov"; break;
  409             case 11:
  410                 output = "Dec"; break;
  411         }
  412         numeric_label = (numeric_label + delta) % 12;
  413     }
  414     else if(unit == YEAR_NAME) {
  415         if(delta > 20) {
  416             output = ssprintf("%04d", numeric_label);
  417         }
  418         else {
  419             output = ssprintf("%02d", numeric_label % 100);
  420         }
  421         numeric_label = (numeric_label + delta);
  422     }
  423     return output;
  424 }
  425 
  426 std::vector<time_histogram_view::time_unit> time_histogram_view::build_time_units()
  427 {
  428     std::vector<time_unit> output;
  429 
  430     output.push_back(time_unit(SECOND_NAME, 1L));
  431     output.push_back(time_unit(MINUTE_NAME, 60L));
  432     output.push_back(time_unit(HOUR_NAME, 60L * 60L));
  433     output.push_back(time_unit(DAY_NAME, 60L * 60L * 24L));
  434     output.push_back(time_unit(WEEK_NAME, 60L * 60L * 24L * 7L));
  435     output.push_back(time_unit(MONTH_NAME, 60L * 60L * 24L * 30L));
  436     output.push_back(time_unit(YEAR_NAME, 60L * 60L * 24L * 360L));
  437 
  438     return output;
  439 }
  440 
  441 std::vector<time_histogram_view::si_prefix> time_histogram_view::build_si_prefixes()
  442 {
  443     std::vector<si_prefix> output;
  444 
  445     output.push_back(si_prefix("", 1LL));
  446     output.push_back(si_prefix("K", 1000LL));
  447     output.push_back(si_prefix("M", 1000LL * 1000LL));
  448     output.push_back(si_prefix("G", 1000LL * 1000LL * 1000LL));
  449     output.push_back(si_prefix("T", 1000LL * 1000LL * 1000LL * 1000LL));
  450     output.push_back(si_prefix("P", 1000LL * 1000LL * 1000LL * 1000LL * 1000LL));
  451     output.push_back(si_prefix("E", 1000LL * 1000LL * 1000LL * 1000LL * 1000LL * 1000LL));
  452 
  453     return output;
  454 }
  455 
  456 // bucket view
  457 
  458 void time_histogram_view::bucket_view::render(cairo_t *cr, const bounds_t &bounds)
  459 {
  460     // how far up the bar have we rendered so far?
  461     double total_height = bounds.y + bounds.height;
  462 
  463     // if multiple sections of the same color follow, simply accumulate their height
  464     double height_accumulator = 0.0;
  465     rgb_t next_color = default_color;
  466 
  467     // The loop below is a bit confusing
  468     for(time_histogram::bucket::counts_t::const_iterator it = bucket.counts.begin(); it != bucket.counts.end();) {
  469 
  470         double height = bounds.height * ((double) it->second / (double) bucket.sum());
  471 
  472         // on first section, preload the first color as the 'next' color
  473         if(it == bucket.counts.begin()) {
  474             colormap_t::const_iterator color_pair = color_map.find(it->first);
  475             if(color_pair != color_map.end()) {
  476                 next_color = color_pair->second;
  477             }
  478         }
  479 
  480         // advance to the next color
  481         rgb_t color = next_color;
  482         next_color = default_color;
  483 
  484         // if there's a next bucket, get its color for the next color
  485         // next consolidate this section with the next if the colors match
  486         it++;
  487         if(it != bucket.counts.end()) {
  488             /* This gets after every bar except the last bar */
  489             colormap_t::const_iterator color_pair = color_map.find(it->first);
  490             if(color_pair != color_map.end()) {
  491                 next_color = color_pair->second;
  492             }
  493 
  494             if(color == next_color) {
  495                 height_accumulator += height;
  496                 continue;
  497             }
  498         }
  499 
  500         /* this gets run after every bar */
  501         cairo_set_source_rgb(cr, color.r, color.g, color.b);
  502 
  503         // account for consolidated sections
  504         height += height_accumulator;
  505         height_accumulator = 0.0;
  506 
  507         cairo_rectangle(cr, bounds.x, total_height - height, bounds.width, height);
  508         cairo_fill(cr);
  509 
  510         total_height -= height;
  511     }
  512 
  513     // non-TCP packets
  514     if(bucket.portless_count > 0) {
  515         double height = bounds.height * ((double) bucket.portless_count / (double) bucket.sum());
  516         cairo_set_source_rgb(cr, blank_bar_line_color.r, blank_bar_line_color.g, blank_bar_line_color.b);
  517         double offset = blank_bar_line_width / 2;
  518         cairo_set_line_width(cr, blank_bar_line_width);
  519         cairo_rectangle(cr, bounds.x + offset, total_height - height + offset,
  520                 bounds.width - blank_bar_line_width, height - blank_bar_line_width);
  521         cairo_stroke(cr);
  522     }
  523 }
  524 
  525 #endif