123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- #!/usr/bin/env python3
- # 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)
|