| #!/usr/bin/env python |
| |
| # Copyright (C) 2011 by Thomas Petazzoni <thomas.petazzoni@free-electrons.com> |
| # Copyright (C) 2013 by Yann E. MORIN <yann.morin.1998@free.fr> |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
| # This script generates graphs of packages build time, from the timing |
| # data generated by Buildroot in the $(O)/build-time.log file. |
| # |
| # Example usage: |
| # |
| # cat $(O)/build-time.log | ./support/scripts/graph-build-time --type=histogram --output=foobar.pdf |
| # |
| # Three graph types are available : |
| # |
| # * histogram, which creates an histogram of the build time for each |
| # package, decomposed by each step (extract, patch, configure, |
| # etc.). The order in which the packages are shown is |
| # configurable: by package name, by build order, or by duration |
| # order. See the --order option. |
| # |
| # * pie-packages, which creates a pie chart of the build time of |
| # each package (without decomposition in steps). Packages that |
| # contributed to less than 1% of the overall build time are all |
| # grouped together in an "Other" entry. |
| # |
| # * pie-steps, which creates a pie chart of the time spent globally |
| # on each step (extract, patch, configure, etc...) |
| # |
| # The default is to generate an histogram ordered by package name. |
| # |
| # Requirements: |
| # |
| # * matplotlib (python-matplotlib on Debian/Ubuntu systems) |
| # * numpy (python-numpy on Debian/Ubuntu systems) |
| # * argparse (by default in Python 2.7, requires python-argparse if |
| # Python 2.6 is used) |
| |
| import sys |
| |
| try: |
| import matplotlib as mpl |
| import numpy |
| except ImportError: |
| sys.stderr.write("You need python-matplotlib and python-numpy to generate build graphs\n") |
| exit(1) |
| |
| # Use the Agg backend (which produces a PNG output, see |
| # http://matplotlib.org/faq/usage_faq.html#what-is-a-backend), |
| # otherwise an incorrect backend is used on some host machines). |
| # Note: matplotlib.use() must be called *before* matplotlib.pyplot. |
| mpl.use('Agg') |
| |
| import matplotlib.pyplot as plt # noqa: E402 |
| import matplotlib.font_manager as fm # noqa: E402 |
| import csv # noqa: E402 |
| import argparse # noqa: E402 |
| |
| steps = ['download', 'extract', 'patch', 'configure', 'build', |
| 'install-target', 'install-staging', 'install-images', |
| 'install-host'] |
| |
| default_colors = ['#8d02ff', '#e60004', '#009836', '#2e1d86', '#ffed00', |
| '#0068b5', '#f28e00', '#940084', '#97c000'] |
| |
| alternate_colors = ['#ffbe0a', '#96bdff', '#3f7f7f', '#ff0000', '#00c000', |
| '#0080ff', '#c000ff', '#00eeee', '#e0e000'] |
| |
| |
| class Package: |
| def __init__(self, name): |
| self.name = name |
| self.steps_duration = {} |
| self.steps_start = {} |
| self.steps_end = {} |
| |
| def add_step(self, step, state, time): |
| if state == "start": |
| self.steps_start[step] = time |
| else: |
| self.steps_end[step] = time |
| if step in self.steps_start and step in self.steps_end: |
| self.steps_duration[step] = self.steps_end[step] - self.steps_start[step] |
| |
| def get_duration(self, step=None): |
| if step is None: |
| duration = 0 |
| for step in list(self.steps_duration.keys()): |
| duration += self.steps_duration[step] |
| return duration |
| if step in self.steps_duration: |
| return self.steps_duration[step] |
| return 0 |
| |
| |
| # Generate an histogram of the time spent in each step of each |
| # package. |
| def pkg_histogram(data, output, order="build"): |
| n_pkgs = len(data) |
| ind = numpy.arange(n_pkgs) |
| |
| if order == "duration": |
| data = sorted(data, key=lambda p: p.get_duration(), reverse=True) |
| elif order == "name": |
| data = sorted(data, key=lambda p: p.name, reverse=False) |
| |
| # Prepare the vals array, containing one entry for each step |
| vals = [] |
| for step in steps: |
| val = [] |
| for p in data: |
| val.append(p.get_duration(step)) |
| vals.append(val) |
| |
| bottom = [0] * n_pkgs |
| legenditems = [] |
| |
| plt.figure() |
| |
| # Draw the bars, step by step |
| for i in range(0, len(vals)): |
| b = plt.bar(ind+0.1, vals[i], width=0.8, color=colors[i], bottom=bottom, linewidth=0.25) |
| legenditems.append(b[0]) |
| bottom = [bottom[j] + vals[i][j] for j in range(0, len(vals[i]))] |
| |
| # Draw the package names |
| plt.xticks(ind + .6, [p.name for p in data], rotation=-60, rotation_mode="anchor", fontsize=8, ha='left') |
| |
| # Adjust size of graph depending on the number of packages |
| # Ensure a minimal size twice as the default |
| # Magic Numbers do Magic Layout! |
| ratio = max(((n_pkgs + 10) / 48, 2)) |
| borders = 0.1 / ratio |
| sz = plt.gcf().get_figwidth() |
| plt.gcf().set_figwidth(sz * ratio) |
| |
| # Adjust space at borders, add more space for the |
| # package names at the bottom |
| plt.gcf().subplots_adjust(bottom=0.2, left=borders, right=1-borders) |
| |
| # Remove ticks in the graph for each package |
| axes = plt.gcf().gca() |
| for line in axes.get_xticklines(): |
| line.set_markersize(0) |
| |
| axes.set_ylabel('Time (seconds)') |
| |
| # Reduce size of legend text |
| leg_prop = fm.FontProperties(size=6) |
| |
| # Draw legend |
| plt.legend(legenditems, steps, prop=leg_prop) |
| |
| if order == "name": |
| plt.title('Build time of packages\n') |
| elif order == "build": |
| plt.title('Build time of packages, by build order\n') |
| elif order == "duration": |
| plt.title('Build time of packages, by duration order\n') |
| |
| # Save graph |
| plt.savefig(output) |
| |
| |
| # Generate a pie chart with the time spent building each package. |
| def pkg_pie_time_per_package(data, output): |
| # Compute total build duration |
| total = 0 |
| for p in data: |
| total += p.get_duration() |
| |
| # Build the list of labels and values, and filter the packages |
| # that account for less than 1% of the build time. |
| labels = [] |
| values = [] |
| other_value = 0 |
| for p in sorted(data, key=lambda p: p.get_duration()): |
| if p.get_duration() < (total * 0.01): |
| other_value += p.get_duration() |
| else: |
| labels.append(p.name) |
| values.append(p.get_duration()) |
| |
| labels.append('Other') |
| values.append(other_value) |
| |
| plt.figure() |
| |
| # Draw pie graph |
| patches, texts, autotexts = plt.pie(values, labels=labels, |
| autopct='%1.1f%%', shadow=True, |
| colors=colors) |
| |
| # Reduce text size |
| proptease = fm.FontProperties() |
| proptease.set_size('xx-small') |
| plt.setp(autotexts, fontproperties=proptease) |
| plt.setp(texts, fontproperties=proptease) |
| |
| plt.title('Build time per package') |
| plt.savefig(output) |
| |
| |
| # Generate a pie chart with a portion for the overall time spent in |
| # each step for all packages. |
| def pkg_pie_time_per_step(data, output): |
| steps_values = [] |
| for step in steps: |
| val = 0 |
| for p in data: |
| val += p.get_duration(step) |
| steps_values.append(val) |
| |
| plt.figure() |
| |
| # Draw pie graph |
| patches, texts, autotexts = plt.pie(steps_values, labels=steps, |
| autopct='%1.1f%%', shadow=True, |
| colors=colors) |
| |
| # Reduce text size |
| proptease = fm.FontProperties() |
| proptease.set_size('xx-small') |
| plt.setp(autotexts, fontproperties=proptease) |
| plt.setp(texts, fontproperties=proptease) |
| |
| plt.title('Build time per step') |
| plt.savefig(output) |
| |
| |
| # Parses the csv file passed on standard input and returns a list of |
| # Package objects, filed with the duration of each step and the total |
| # duration of the package. |
| def read_data(input_file): |
| if input_file is None: |
| input_file = sys.stdin |
| else: |
| input_file = open(input_file) |
| reader = csv.reader(input_file, delimiter=':') |
| pkgs = [] |
| |
| # Auxilliary function to find a package by name in the list. |
| def getpkg(name): |
| for p in pkgs: |
| if p.name == name: |
| return p |
| return None |
| |
| for row in reader: |
| time = float(row[0].strip()) |
| state = row[1].strip() |
| step = row[2].strip() |
| pkg = row[3].strip() |
| |
| p = getpkg(pkg) |
| if p is None: |
| p = Package(pkg) |
| pkgs.append(p) |
| |
| p.add_step(step, state, time) |
| |
| return pkgs |
| |
| |
| parser = argparse.ArgumentParser(description='Draw build time graphs') |
| parser.add_argument("--type", '-t', metavar="GRAPH_TYPE", |
| help="Type of graph (histogram, pie-packages, pie-steps)") |
| parser.add_argument("--order", '-O', metavar="GRAPH_ORDER", |
| help="Ordering of packages: build or duration (for histogram only)") |
| parser.add_argument("--alternate-colors", '-c', action="store_true", |
| help="Use alternate colour-scheme") |
| parser.add_argument("--input", '-i', metavar="INPUT", |
| help="Input file (usually $(O)/build/build-time.log)") |
| parser.add_argument("--output", '-o', metavar="OUTPUT", required=True, |
| help="Output file (.pdf or .png extension)") |
| args = parser.parse_args() |
| |
| d = read_data(args.input) |
| |
| if args.alternate_colors: |
| colors = alternate_colors |
| else: |
| colors = default_colors |
| |
| if args.type == "histogram" or args.type is None: |
| if args.order == "build" or args.order == "duration" or args.order == "name": |
| pkg_histogram(d, args.output, args.order) |
| elif args.order is None: |
| pkg_histogram(d, args.output, "name") |
| else: |
| sys.stderr.write("Unknown ordering: %s\n" % args.order) |
| exit(1) |
| elif args.type == "pie-packages": |
| pkg_pie_time_per_package(d, args.output) |
| elif args.type == "pie-steps": |
| pkg_pie_time_per_step(d, args.output) |
| else: |
| sys.stderr.write("Unknown type: %s\n" % args.type) |
| exit(1) |