"Fossies" - the Fresh Open Source Software Archive

Member "tcpflow-1.6.1/src/netviz/one_page_report.cpp" (19 Feb 2021, 22983 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 "one_page_report.cpp" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 1.4.4_vs_1.4.5.

    1 /**
    2  * one_page_report.cpp: 
    3  * Generate a one-page visualization from TCP packets
    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 #include "be13_api/utils.h"
   14 #include "plot_view.h"
   15 #ifdef HAVE_LIBCAIRO
   16 #include "tcpflow.h"
   17 #include "tcpip.h"
   18 
   19 #include <ctime>
   20 #include <iomanip>
   21 #include <math.h>
   22 
   23 #include "one_page_report.h"
   24 
   25 using namespace std;
   26 
   27 const unsigned int one_page_report::max_bars = 100;
   28 const unsigned int one_page_report::port_colors_count = 4;
   29 // string constants
   30 const string one_page_report::title_version = PACKAGE_NAME " " PACKAGE_VERSION;
   31 const string one_page_report::generic_legend_format = "Port %d";
   32 const vector<one_page_report::transport_type> one_page_report::display_transports =
   33         one_page_report::build_display_transports();
   34 // ratio constants
   35 const double one_page_report::page_margin_factor = 0.05;
   36 const double one_page_report::line_space_factor = 0.25;
   37 const double one_page_report::histogram_pad_factor_y = 1.1;
   38 const double one_page_report::address_histogram_width_divisor = 2.2;
   39 // size constants
   40 const double one_page_report::packet_histogram_height = 100.0;
   41 const double one_page_report::address_histogram_height = 125.0;
   42 const double one_page_report::port_histogram_height = 100.0;
   43 const double one_page_report::legend_height = 16.0;
   44 // color constants
   45 const plot_view::rgb_t one_page_report::default_color(0.67, 0.67, 0.67);
   46 const plot_view::rgb_t one_page_report::color_orange(1.00, 0.47, 0.00);
   47 const plot_view::rgb_t one_page_report::color_red(1.00, 0.00, 0.00);
   48 const plot_view::rgb_t one_page_report::color_magenta(0.75, 0.00, 0.60);
   49 const plot_view::rgb_t one_page_report::color_purple(0.58, 0.00, 0.75);
   50 const plot_view::rgb_t one_page_report::color_deep_purple(0.40, 0.00, 0.75);
   51 const plot_view::rgb_t one_page_report::color_blue(0.02, 0.00, 1.00);
   52 const plot_view::rgb_t one_page_report::color_teal(0.00, 0.75, 0.65);
   53 const plot_view::rgb_t one_page_report::color_green(0.02, 0.75, 0.00);
   54 const plot_view::rgb_t one_page_report::color_yellow(0.99, 1.00, 0.00);
   55 const plot_view::rgb_t one_page_report::color_light_orange(1.00, 0.73, 0.00);
   56 const plot_view::rgb_t one_page_report::cdf_color(0.00, 0.00, 0.00);
   57 
   58 one_page_report::one_page_report(int max_histogram_size) : 
   59     source_identifier(), filename("report.pdf"),
   60     bounds(0.0, 0.0, 611.0, 792.0), header_font_size(8.0),
   61     top_list_font_size(8.0), histogram_show_top_n_text(3),
   62     packet_count(0), byte_count(0), earliest(), latest(), transport_counts(),
   63     ports_in_time_histogram(), color_labels(), packet_histogram(),
   64     src_port_histogram(), dst_port_histogram(), pfall(), netmap(),
   65     src_tree(max_histogram_size), dst_tree(max_histogram_size), port_aliases(),
   66     port_colormap()
   67 {
   68     earliest = (struct timeval) { 0 };
   69     latest = (struct timeval) { 0 };
   70 
   71     port_colormap[PORT_HTTP] = color_blue;
   72     port_colormap[PORT_HTTP_ALT_0] = color_blue;
   73     port_colormap[PORT_HTTP_ALT_1] = color_blue;
   74     port_colormap[PORT_HTTP_ALT_2] = color_blue;
   75     port_colormap[PORT_HTTP_ALT_3] = color_blue;
   76     port_colormap[PORT_HTTP_ALT_4] = color_blue;
   77     port_colormap[PORT_HTTP_ALT_5] = color_blue;
   78     port_colormap[PORT_HTTPS] = color_green;
   79     port_colormap[PORT_SSH] = color_purple;
   80     port_colormap[PORT_FTP_CONTROL] = color_red;
   81     port_colormap[PORT_FTP_DATA] = color_red;
   82 
   83     // build null alias map to avoid requiring special handling for unmapped ports
   84     for(int ii = 0; ii <= 65535; ii++) {
   85         port_aliases[ii] = ii;
   86     }
   87 }
   88 
   89 void one_page_report::ingest_packet(const be13::packet_info &pi)
   90 {
   91     if(earliest.tv_sec == 0 || (pi.ts.tv_sec < earliest.tv_sec ||
   92                 (pi.ts.tv_sec == earliest.tv_sec && pi.ts.tv_usec < earliest.tv_usec))) {
   93         earliest = pi.ts;
   94     }
   95     if(pi.ts.tv_sec > latest.tv_sec || (pi.ts.tv_sec == latest.tv_sec && pi.ts.tv_usec > latest.tv_usec)) {
   96         latest = pi.ts;
   97     }
   98 
   99     size_t packet_length = pi.pcap_hdr->len;
  100     packet_count++;
  101     byte_count += packet_length;
  102     transport_counts[pi.ether_type()] += packet_length; // should we handle VLANs?
  103 
  104     // break out TCP/IP info and feed child views
  105 
  106     // feed IP-only views
  107     uint8_t ip_ver = 0;
  108     if(pi.is_ip4()) {
  109         ip_ver = 4;
  110 
  111         src_tree.add((uint8_t *) pi.ip_data + pi.ip4_src_off, IP4_ADDR_LEN, packet_length);
  112         dst_tree.add((uint8_t *) pi.ip_data + pi.ip4_dst_off, IP4_ADDR_LEN, packet_length);
  113     }
  114     else if(pi.is_ip6()) {
  115         ip_ver = 6;
  116 
  117         src_tree.add((uint8_t *) pi.ip_data + pi.ip6_src_off, IP6_ADDR_LEN, packet_length);
  118         dst_tree.add((uint8_t *) pi.ip_data + pi.ip6_dst_off, IP6_ADDR_LEN, packet_length);
  119     }
  120     else {
  121         packet_histogram.insert(pi.ts, 0, packet_length, time_histogram::F_NON_TCP);
  122         return;
  123     }
  124 
  125 
  126     // feed TCP views
  127     uint16_t tcp_src = 0, tcp_dst = 0;
  128     bool has_tcp = false;
  129 
  130     switch(ip_ver) {
  131         case 4:
  132             if(!pi.is_ip4_tcp()) {
  133                 break;
  134             }
  135             tcp_src = pi.get_ip4_tcp_sport();
  136             tcp_dst = pi.get_ip4_tcp_dport();
  137             has_tcp = true;
  138             break;
  139         case 6:
  140             if(!pi.is_ip6_tcp()) {
  141                 break;
  142             }
  143             tcp_src = pi.get_ip6_tcp_sport();
  144             tcp_dst = pi.get_ip6_tcp_dport();
  145             has_tcp = true;
  146             break;
  147         default:
  148             return;
  149     }
  150 
  151     if(!has_tcp) {
  152         packet_histogram.insert(pi.ts, 0, packet_length, time_histogram::F_NON_TCP);
  153         return;
  154     }
  155 
  156     // if either the TCP source or destination is a pre-colored port, submit that
  157     // port to the time histogram
  158     port_colormap_t::const_iterator tcp_src_color = port_colormap.find(tcp_src);
  159     port_colormap_t::const_iterator tcp_dst_color = port_colormap.find(tcp_dst);
  160     in_port_t packet_histogram_port = tcp_src;
  161     // if dst is colored and src isn't; use dst instead
  162     if(tcp_dst_color != port_colormap.end() && tcp_src_color == port_colormap.end()) {
  163         packet_histogram_port = tcp_dst;
  164     }
  165     // if both are colored, alternate src and dst
  166     else if(tcp_src_color != port_colormap.end() && tcp_dst_color != port_colormap.end() &&
  167             packet_count % 2 == 0) {
  168         packet_histogram_port = tcp_dst;
  169     }
  170     // record that this port appears in the histogram for legend building purposes
  171     ports_in_time_histogram[packet_histogram_port] = true;
  172     packet_histogram.insert(pi.ts, packet_histogram_port, packet_length);
  173 
  174     src_port_histogram.increment(tcp_src, packet_length);
  175     dst_port_histogram.increment(tcp_dst, packet_length);
  176 }
  177 
  178 void one_page_report::render(const string &outdir)
  179 {
  180     string fname = outdir + "/" + filename;
  181 
  182     cairo_surface_t *surface = cairo_pdf_surface_create(fname.c_str(),
  183                  bounds.width,
  184                  bounds.height);
  185     cairo_t *cr = cairo_create(surface);
  186 
  187     //
  188     // Configure views
  189     //
  190 
  191     double pad_size = bounds.width * page_margin_factor;
  192     plot_view::bounds_t pad_bounds(bounds.x + pad_size,
  193             bounds.y + pad_size, bounds.width - pad_size * 2,
  194             bounds.height - pad_size * 2);
  195 
  196     // iff a colored common port appears in the time histogram, add its color to the legend
  197     if(ports_in_time_histogram[PORT_HTTP] ||
  198             ports_in_time_histogram[PORT_HTTP_ALT_0] ||
  199             ports_in_time_histogram[PORT_HTTP_ALT_1] ||
  200             ports_in_time_histogram[PORT_HTTP_ALT_2] ||
  201             ports_in_time_histogram[PORT_HTTP_ALT_3] ||
  202             ports_in_time_histogram[PORT_HTTP_ALT_4] ||
  203             ports_in_time_histogram[PORT_HTTP_ALT_5]) {
  204         color_labels.push_back(legend_view::entry_t(color_blue, "HTTP", PORT_HTTP));
  205     }
  206     if(ports_in_time_histogram[PORT_HTTPS]) {
  207         color_labels.push_back(legend_view::entry_t(color_green, "HTTPS", PORT_HTTPS));
  208     }
  209     if(ports_in_time_histogram[PORT_SSH]) {
  210         color_labels.push_back(legend_view::entry_t(color_purple, "SSH", PORT_SSH));
  211     }
  212     if(ports_in_time_histogram[PORT_FTP_DATA] || ports_in_time_histogram[PORT_FTP_CONTROL]) {
  213         color_labels.push_back(legend_view::entry_t(color_red, "FTP", PORT_FTP_DATA));
  214     }
  215     // assign the top 4 source ports colors if they don't already have them
  216     vector<port_histogram::port_count>::const_iterator it = src_port_histogram.begin();
  217     for(size_t count = 0; count < port_colors_count && it != src_port_histogram.end(); it++) {
  218         port_colormap_t::const_iterator color = port_colormap.find(it->port);
  219         if(color == port_colormap.end()) {
  220             string label = ssprintf(generic_legend_format.c_str(), it->port);
  221             switch(count) {
  222                 case 0:
  223                     if(ports_in_time_histogram[it->port]) {
  224                         color_labels.push_back(legend_view::entry_t(color_orange, label, it->port));
  225                     }
  226                     port_colormap[it->port] = color_orange;
  227                     break;
  228                 case 1:
  229                     if(ports_in_time_histogram[it->port]) {
  230                         color_labels.push_back(legend_view::entry_t(color_magenta, label, it->port));
  231                     }
  232                     port_colormap[it->port] = color_magenta;
  233                     break;
  234                 case 2:
  235                     if(ports_in_time_histogram[it->port]) {
  236                         color_labels.push_back(legend_view::entry_t(color_deep_purple, label, it->port));
  237                     }
  238                     port_colormap[it->port] = color_deep_purple;
  239                     break;
  240                 case 3:
  241                     if(ports_in_time_histogram[it->port]) {
  242                         color_labels.push_back(legend_view::entry_t(color_teal, label, it->port));
  243                     }
  244                     port_colormap[it->port] = color_teal;
  245                     break;
  246                 default:
  247                     break;
  248             }
  249             count++;
  250         }
  251     }
  252     sort(color_labels.begin(), color_labels.end());
  253     
  254     // time histogram
  255     double condension_factor = (double) packet_histogram.non_sparse_size() / (double) max_bars;
  256     if(condension_factor > 1.1) {
  257         // condense only by whole numbers to avoid messing up bar labels
  258         packet_histogram.condense(((int) condension_factor) + 1);
  259     }
  260     time_histogram_view th_view(packet_histogram, port_colormap, default_color,
  261             cdf_color);
  262 
  263     // color legend
  264     legend_view lg_view(color_labels);
  265 
  266     // address histograms
  267     // histograms are built from iptree here
  268     address_histogram src_addr_histogram(src_tree);
  269     address_histogram dst_addr_histogram(dst_tree);
  270     address_histogram_view src_ah_view(src_addr_histogram);
  271     if(src_addr_histogram.size() > 0) {
  272         src_ah_view.title = "Top Source Addresses";
  273     }
  274     else {
  275         src_ah_view.title = "No Source Addresses";
  276     }
  277     src_ah_view.bar_color = default_color;
  278     src_ah_view.cdf_color = cdf_color;
  279     address_histogram_view dst_ah_view(dst_addr_histogram);
  280     if(dst_addr_histogram.size() > 0) {
  281         dst_ah_view.title = "Top Destination Addresses";
  282     }
  283     else {
  284         dst_ah_view.title = "No Destination Addresses";
  285     }
  286     dst_ah_view.bar_color = default_color;
  287     dst_ah_view.cdf_color = cdf_color;
  288 
  289     // port histograms
  290     port_histogram_view sp_view(src_port_histogram, port_colormap, default_color,
  291             cdf_color);
  292     port_histogram_view dp_view(dst_port_histogram, port_colormap, default_color,
  293             cdf_color);
  294     if(src_port_histogram.size()) {
  295         sp_view.title = "Top Source Ports";
  296     }
  297     else {
  298         sp_view.title = "No Source Ports";
  299     }
  300     if(dst_port_histogram.size()) {
  301         dp_view.title = "Top Destination Ports";
  302     }
  303     else {
  304         dp_view.title = "No Destination Ports";
  305     }
  306 
  307     //
  308     // run configured views through render pass
  309     //
  310 
  311     render_pass pass(*this, cr, pad_bounds);
  312 
  313     pass.render_header();
  314     pass.render(th_view);
  315     pass.render(lg_view);
  316     if(getenv("DEBUG")) {
  317         pass.render_map();
  318         pass.render_packetfall();
  319     }
  320     pass.render(src_ah_view, dst_ah_view);
  321     pass.render(sp_view, dp_view);
  322 
  323     // cleanup
  324     cairo_destroy (cr);
  325     cairo_surface_destroy(surface);
  326 }
  327 
  328 void one_page_report::render_pass::render_header()
  329 {
  330     string formatted;
  331     // title
  332     double title_line_space = report.header_font_size * line_space_factor;
  333     //// version
  334     render_text_line(title_version, report.header_font_size,
  335             title_line_space);
  336     //// input
  337     formatted = ssprintf("Input: %s", report.source_identifier.c_str());
  338     render_text_line(formatted.c_str(), report.header_font_size,
  339             title_line_space);
  340     //// date generated
  341     time_t gen_unix = time(0);
  342     struct tm gen_time = *localtime(&gen_unix);
  343     formatted = ssprintf("Generated: %04d-%02d-%02d %02d:%02d:%02d",
  344             1900 + gen_time.tm_year, 1 + gen_time.tm_mon, gen_time.tm_mday,
  345             gen_time.tm_hour, gen_time.tm_min, gen_time.tm_sec);
  346     render_text_line(formatted.c_str(), report.header_font_size,
  347             title_line_space);
  348     //// trailing pad
  349     end_of_content += title_line_space * 4;
  350     // quick stats
  351     //// date range
  352     time_t tstart = report.earliest.tv_sec;
  353     struct tm start;
  354     memset(&start,0,sizeof(start));
  355     localtime_r(&tstart,&start);
  356 
  357     time_t tstop = report.latest.tv_sec;
  358     struct tm stop;
  359     memset(&stop,0,sizeof(stop));
  360     localtime_r(&tstop,&stop);
  361     formatted = ssprintf("Date range: %04d-%02d-%02d %02d:%02d:%02d -- %04d-%02d-%02d %02d:%02d:%02d",
  362             1900 + start.tm_year, 1 + start.tm_mon, start.tm_mday,
  363             start.tm_hour, start.tm_min, start.tm_sec,
  364             1900 + stop.tm_year, 1 + stop.tm_mon, stop.tm_mday,
  365             stop.tm_hour, stop.tm_min, stop.tm_sec);
  366     render_text_line(formatted.c_str(), report.header_font_size,
  367             title_line_space);
  368     //// packet count/size
  369     formatted = ssprintf("Packets analyzed: %s (%s)",
  370             comma_number_string(report.packet_count).c_str(),
  371             plot_view::pretty_byte_total(report.byte_count).c_str());
  372     render_text_line(formatted.c_str(), report.header_font_size,
  373             title_line_space);
  374     //// protocol breakdown
  375     uint64_t transport_total = 0;
  376     for(map<uint32_t, uint64_t>::const_iterator ii =
  377                 report.transport_counts.begin();
  378             ii != report.transport_counts.end(); ii++) {
  379         transport_total += ii->second;
  380     }
  381 
  382     stringstream ss;
  383     unsigned int percentage = 0;
  384     uint64_t classified_total = 0;
  385     ss << "Transports: ";
  386     if(transport_total > 0) {
  387         for(vector<transport_type>::const_iterator it = display_transports.begin();
  388                 it != display_transports.end(); it++) {
  389             uint64_t count = report.transport_counts[it->ethertype];
  390             classified_total += count;
  391             percentage = (unsigned int) (((double) count / (double) transport_total) * 100.0);
  392 
  393             if(percentage > 0) {
  394                 ss << it->name << " " << percentage << "% ";
  395             }
  396         }
  397         percentage = (unsigned int) (((double) (transport_total - classified_total) / transport_total) * 100.0);
  398         if(percentage > 0) {
  399             ss << "Other " << percentage << "% ";
  400         }
  401     }
  402     formatted = ss.str();
  403     render_text_line(formatted.c_str(), report.header_font_size,
  404             title_line_space);
  405     // trailing pad for entire header
  406     end_of_content += title_line_space * 4;
  407 }
  408 
  409 void one_page_report::render_pass::render_text(string text,
  410         double font_size, double x_offset,
  411         cairo_text_extents_t &rendered_extents)
  412 {
  413     cairo_set_font_size(surface, font_size);
  414     cairo_set_source_rgb(surface, 0.0, 0.0, 0.0);
  415     cairo_text_extents(surface, text.c_str(), &rendered_extents);
  416     cairo_move_to(surface, surface_bounds.x + x_offset, surface_bounds.y +
  417             end_of_content + rendered_extents.height);
  418     cairo_show_text(surface, text.c_str());
  419 }
  420 
  421 void one_page_report::render_pass::render_text_line(string text,
  422         double font_size, double line_space)
  423 {
  424     cairo_text_extents_t extents;
  425     render_text(text, font_size, 0.0, extents);
  426     end_of_content += extents.height + line_space;
  427 }
  428 
  429 void one_page_report::render_pass::render(time_histogram_view &view)
  430 {
  431     plot_view::bounds_t bnds(surface_bounds.x,
  432                              surface_bounds.y + end_of_content,
  433                              surface_bounds.width,
  434                              packet_histogram_height);
  435 
  436     view.render(surface, bnds);
  437 
  438     end_of_content += bnds.height * histogram_pad_factor_y;
  439 }
  440 
  441 void one_page_report::render_pass::render_packetfall()
  442 {
  443     plot_view::bounds_t bnds(surface_bounds.x, surface_bounds.y + end_of_content, surface_bounds.width,
  444             packet_histogram_height);
  445 
  446     report.pfall.render(surface, bnds);
  447 
  448     end_of_content += bnds.height * histogram_pad_factor_y;
  449 }
  450 
  451 void one_page_report::render_pass::render_map()
  452 {
  453     plot_view::bounds_t bnds(surface_bounds.x,
  454             surface_bounds.y + end_of_content, surface_bounds.width, packet_histogram_height);
  455 
  456     report.netmap.render(surface, bnds);
  457 
  458     end_of_content += bnds.height * histogram_pad_factor_y;
  459 }
  460 
  461 void one_page_report::render_pass::render(address_histogram_view &left, address_histogram_view &right)
  462 {
  463     double width = surface_bounds.width / address_histogram_width_divisor;
  464     const address_histogram &left_data = left.get_data();
  465     const address_histogram &right_data = right.get_data();
  466     uint64_t total_datagrams = left_data.ingest_count();
  467 
  468     plot_view::bounds_t left_bounds(surface_bounds.x, surface_bounds.y +
  469             end_of_content, width, address_histogram_height);
  470     left.render(surface, left_bounds);
  471 
  472     plot_view::bounds_t right_bounds(surface_bounds.x + (surface_bounds.width - width),
  473             surface_bounds.y + end_of_content, width, address_histogram_height);
  474     right.render(surface, right_bounds);
  475 
  476     end_of_content += max(left_bounds.height, right_bounds.height);
  477 
  478     // text stats
  479     string stat_line_format = "%d) %s - %s (%d%%)";
  480     for(size_t ii = 0; ii < report.histogram_show_top_n_text; ii++) {
  481         cairo_text_extents_t left_extents, right_extents;
  482 
  483         if(left_data.size() > ii && left_data.at(ii).count > 0) {
  484             const iptree::addr_elem &addr = left_data.at(ii);
  485             uint8_t percentage = 0;
  486 
  487             percentage = (uint8_t) (((double) addr.count / (double) total_datagrams) * 100.0);
  488 
  489             string str = ssprintf(stat_line_format.c_str(), ii + 1, addr.str().c_str(),
  490                     plot_view::pretty_byte_total(addr.count).c_str(), percentage);
  491 
  492             render_text(str.c_str(), report.top_list_font_size, left_bounds.x,
  493                     left_extents);
  494         }
  495 
  496         if(right_data.size() > ii && right_data.at(ii).count > 0) {
  497             const iptree::addr_elem &addr = right_data.at(ii);
  498             uint8_t percentage = 0;
  499 
  500             percentage = (uint8_t) (((double) addr.count / (double) total_datagrams) * 100.0);
  501 
  502             string str = ssprintf(stat_line_format.c_str(), ii + 1, addr.str().c_str(),
  503                     plot_view::pretty_byte_total(addr.count).c_str(), percentage);
  504 
  505             render_text(str.c_str(), report.top_list_font_size, right_bounds.x,
  506                     right_extents);
  507         }
  508 
  509         if((left_data.size() > ii && left_data.at(ii).count > 0) ||
  510                 (right_data.size() > ii && right_data.at(ii).count > 0)) {
  511             end_of_content += max(left_extents.height, right_extents.height) * 1.5;
  512         }
  513     }
  514 
  515     end_of_content += max(left_bounds.height, right_bounds.height) *
  516         (histogram_pad_factor_y - 1.0);
  517 }
  518 
  519 void one_page_report::render_pass::render(port_histogram_view &left, port_histogram_view &right)
  520 {
  521     port_histogram &left_data = left.get_data();
  522     port_histogram &right_data = right.get_data();
  523 
  524     uint64_t total_bytes = left_data.ingest_count();
  525 
  526     double width = surface_bounds.width / address_histogram_width_divisor;
  527 
  528     plot_view::bounds_t left_bounds(surface_bounds.x, surface_bounds.y + end_of_content,
  529             width, port_histogram_height);
  530     left.render(surface, left_bounds);
  531 
  532     plot_view::bounds_t right_bounds(surface_bounds.x + (surface_bounds.width - width),
  533             surface_bounds.y + end_of_content, width, port_histogram_height);
  534     right.render(surface, right_bounds);
  535 
  536     end_of_content += max(left_bounds.height, right_bounds.height);
  537 
  538     // text stats
  539     string stat_line_format = "%d) %d - %s (%d%%)";
  540     for(size_t ii = 0; ii < report.histogram_show_top_n_text; ii++) {
  541         cairo_text_extents_t left_extents, right_extents;
  542 
  543         if(left_data.size() > ii && left_data.at(ii).count > 0) {
  544             port_histogram::port_count port = left_data.at(ii);
  545             uint8_t percentage = 0;
  546 
  547             percentage = (uint8_t) (((double) port.count / (double) total_bytes) * 100.0);
  548 
  549             string str = ssprintf(stat_line_format.c_str(), ii + 1, port.port,
  550                     plot_view::pretty_byte_total(port.count).c_str(), percentage);
  551 
  552             render_text(str.c_str(), report.top_list_font_size, left_bounds.x,
  553                     left_extents);
  554         }
  555 
  556         if(right_data.size() > ii && right_data.at(ii).count > 0) {
  557             port_histogram::port_count port = right_data.at(ii);
  558             uint8_t percentage = 0;
  559 
  560             percentage = (uint8_t) (((double) port.count / (double) total_bytes) * 100.0);
  561 
  562             string str = ssprintf(stat_line_format.c_str(), ii + 1, port.port,
  563                     plot_view::pretty_byte_total(port.count).c_str(), percentage);
  564 
  565             render_text(str.c_str(), report.top_list_font_size, right_bounds.x,
  566                     right_extents);
  567         }
  568 
  569         if((left_data.size() > ii && left_data.at(ii).count > 0) ||
  570                 (right_data.size() > ii && right_data.at(ii).count > 0)) {
  571             end_of_content += max(left_extents.height, right_extents.height) * 1.5;
  572         }
  573     }
  574 
  575     end_of_content += max(left_bounds.height, right_bounds.height) *
  576         (histogram_pad_factor_y - 1.0);
  577 }
  578 
  579 void one_page_report::render_pass::render(const legend_view &view)
  580 {
  581     plot_view::bounds_t view_bounds(surface_bounds.x, surface_bounds.y + end_of_content,
  582             surface_bounds.width, legend_height);
  583     view.render(surface, view_bounds);
  584 
  585     end_of_content += legend_height;
  586 }
  587 
  588 vector<one_page_report::transport_type> one_page_report::build_display_transports()
  589 {
  590     vector<transport_type> v;
  591     v.push_back(transport_type(ETHERTYPE_IP, "IPv4"));
  592     v.push_back(transport_type(ETHERTYPE_IPV6, "IPv6"));
  593     v.push_back(transport_type(ETHERTYPE_ARP, "ARP"));
  594     v.push_back(transport_type(ETHERTYPE_VLAN, "VLAN"));
  595     return v;
  596 }
  597 
  598 void one_page_report::dump(int dbg)
  599 {
  600     if(dbg){
  601         std::cout << "src_tree:\n" << src_tree << "\n" << "dst_tree:\n" << dst_tree << "\n";
  602     }
  603 }
  604 
  605 #endif