#!/usr/bin/env python # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 from __future__ import print_function # Copyright (c) 2016, Arvid Norberg # 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 the author 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. # this script can parse and generate reports from the alert log from a # libtorrent session import os import sys import math from multiprocessing.pool import ThreadPool thread_pool = ThreadPool(8) output_dir = 'session_stats_report' stat = open(sys.argv[1]) line = stat.readline() print('looking for stats header') while 'session stats header:' not in line: line = stat.readline() print('found') keys = line.split('session stats header:')[1].strip().split(', ') try: os.mkdir(output_dir) except Exception: pass data_out = open(os.path.join(output_dir, 'counters.dat'), 'w+') idx = 0 for line in stat: if 'session stats (' not in line: continue data_out.write(("%d\t" % idx) + line.split(' values): ')[1].strip().replace(', ', '\t') + '\n') idx += 1 data_out.close() line_graph = 0 histogram = 1 stacked = 2 diff = 3 graph_colors = [] pattern = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [0, 1, 1], [1, 1, 0]] def process_color(c, op): for i in range(3): if op == 0: c[i] = min(255, c[i] + 0xb0) if op == 2: c[i] = max(0, c[i] - 0x50) return c for i in range(0, len(pattern) * 3): op = i / len(pattern) c = list(pattern[i % len(pattern)]) for j in range(3): c[j] *= 0xff c = process_color(c, op) c = '#%02x%02x%02x' % (c[0], c[1], c[2]) graph_colors.append(c) line_colors = list(graph_colors) line_colors.reverse() gradient16_colors = [] for i in range(0, 16): f = i / 16. pi = 3.1415927 r = max(int(255 * (math.sin(f * pi) + 0.2)), 0) g = max(int(255 * (math.sin((f - 0.5) * pi) + 0.2)), 0) b = max(int(255 * (math.sin((f + 0.5) * pi) + 0.2)), 0) c = '#%02x%02x%02x' % (min(r, 255), min(g, 255), min(b, 255)) gradient16_colors.append(c) gradient18_colors = [] for i in range(0, 18): f = i / 18. pi = 3.1415927 r = max(int(255 * (math.sin(f * pi) + 0.2)), 0) g = max(int(255 * (math.sin((f - 0.5) * pi) + 0.2)), 0) b = max(int(255 * (math.sin((f + 0.5) * pi) + 0.2)), 0) c = '#%02x%02x%02x' % (min(r, 255), min(g, 255), min(b, 255)) gradient18_colors.append(c) gradient6_colors = [] for i in range(0, 6): f = i / 6. c = '#%02x%02x%02x' % (min(int(255 * (-2 * f + 2)), 255), min(int(255 * (2 * f)), 255), 100) gradient6_colors.append(c) def plot_fun(script): try: ret = os.system('gnuplot "%s" 2>/dev/null' % script) except Exception as e: print('please install gnuplot: sudo apt install gnuplot') raise e if ret != 0 and ret != 256: print('gnuplot failed: %d\n' % ret) raise Exception("abort") sys.stdout.write('.') sys.stdout.flush() def to_title(key): return key.replace('_', ' ').replace('.', ' - ') def gen_report(name, unit, lines, short_unit, generation, log_file, options): filename = os.path.join(output_dir, '%s_%04d.png' % (name, generation)) thumb = os.path.join(output_dir, '%s_%04d_thumb.png' % (name, generation)) # don't re-render a graph unless the logfile has changed try: dst1 = os.stat(filename) dst2 = os.stat(thumb) src = os.stat(log_file) if dst1.st_mtime > src.st_mtime and dst2.st_mtime > src.st_mtime: sys.stdout.write('.') return None except Exception: pass script = os.path.join(output_dir, '%s_%04d.gnuplot' % (name, generation)) out = open(script, 'wb') print("set term png size 1200,700", file=out) print('set output "%s"' % filename, file=out) if 'allow-negative' not in options: print('set yrange [0:*]', file=out) print("set tics nomirror", file=out) print("set key box", file=out) print("set key left top", file=out) colors = graph_colors if options['type'] == line_graph: colors = line_colors try: if options['colors'] == 'gradient16': colors = gradient16_colors elif options['colors'] == 'gradient6': colors = gradient6_colors if options['colors'] == 'gradient18': colors = gradient18_colors except Exception: pass if options['type'] == histogram: binwidth = options['binwidth'] numbins = int(options['numbins']) print('binwidth=%f' % binwidth, file=out) print('set boxwidth binwidth', file=out) print('bin(x,width)=width*floor(x/width) + binwidth/2', file=out) print('set xrange [0:%f]' % (binwidth * numbins), file=out) print('set xlabel "%s"' % unit, file=out) print('set ylabel "number"', file=out) k = lines[0] try: column = keys.index(k) + 2 except Exception: print('"%s" not found' % k) return print('plot "%s" using (bin($%d,binwidth)):(1.0) smooth freq with boxes' % (log_file, column), file=out) print('', file=out) print('', file=out) print('', file=out) elif options['type'] == stacked: print('set xrange [0:*]', file=out) print('set ylabel "%s"' % unit, file=out) print('set xlabel "time (s)"', file=out) print('set format y "%%.1s%%c%s";' % short_unit, file=out) print('set style fill solid 1.0 noborder', file=out) print('plot', end=' ', file=out) first = True graph = '' plot_expression = '' color = 0 for k in lines: try: column = keys.index(k) + 2 except Exception: print('"%s" not found' % k) continue if not first: plot_expression = ', ' + plot_expression graph += '+' axis = 'x1y1' graph += '$%d' % column plot_expression = ' "%s" using 1:(%s) title "%s" axes %s with filledcurves x1 lc rgb "%s"' % ( log_file, graph, to_title(k), axis, colors[color % len(colors)]) + plot_expression first = False color += 1 print(plot_expression, file=out) elif options['type'] == diff: print('set xrange [0:*]', file=out) print('set ylabel "%s"' % unit, file=out) print('set xlabel "time (s)"', file=out) print('set format y "%%.1s%%c%s";' % short_unit, file=out) first = True graph = '' title = '' for k in lines: try: column = keys.index(k) + 2 except Exception: print('"%s" not found' % k) continue if not first: graph += '-' title += ' - ' graph += '$%d' % column title += to_title(k) first = False print('plot "%s" using 1:(%s) title "%s" with step' % (log_file, graph, title), file=out) else: print('set xrange [0:*]', file=out) print('set ylabel "%s"' % unit, file=out) print('set xlabel "time (s)"', file=out) print('set format y "%%.1s%%c%s";' % short_unit, file=out) print('plot', end=' ', file=out) first = True color = 0 for k in lines: try: column = keys.index(k) + 2 except Exception: print('"%s" not found' % k) continue if not first: print(', ', end=' ', file=out) axis = 'x1y1' print(' "%s" using 1:%d title "%s" axes %s with steps lc rgb "%s"' % (log_file, column, to_title(k), axis, colors[color % len(colors)]), end=' ', file=out) first = False color += 1 print('', file=out) print('set term png size 150,100', file=out) print('set output "%s"' % thumb, file=out) print('set key off', file=out) print('unset tics', file=out) print('set format x ""', file=out) print('set format y ""', file=out) print('set xlabel ""', file=out) print('set ylabel ""', file=out) print('set y2label ""', file=out) print('set rmargin 0', file=out) print('set lmargin 0', file=out) print('set tmargin 0', file=out) print('set bmargin 0', file=out) print("replot", file=out) out.close() return script def gen_html(reports, generations): file = open(os.path.join(output_dir, 'index.html'), 'w+') css = '''img { margin: 0} #head { display: block } #graphs { white-space:nowrap; } h1 { line-height: 1; display: inline } h2 { line-height: 1; display: inline; font-size: 1em; font-weight: normal};''' print('
' % css, file=file) for i in reports: print('