a3f37c53d5
Make sure that the pie charts produced by 'graph-build' and 'graph-size' targets are sorted on the size of each piece of the pie. Otherwise, making visual analysis is difficult, as one needs to look at the legends of each piece and do the sorting manually in their head. Signed-off-by: Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
307 lines
10 KiB
Python
Executable File
307 lines
10 KiB
Python
Executable File
#!/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
|
|
import matplotlib.font_manager as fm
|
|
import csv
|
|
import argparse
|
|
|
|
steps = [ 'extract', 'patch', 'configure', 'build',
|
|
'install-target', 'install-staging', 'install-images',
|
|
'install-host']
|
|
|
|
default_colors = ['#e60004', '#009836', '#2e1d86', '#ffed00',
|
|
'#0068b5', '#f28e00', '#940084', '#97c000']
|
|
|
|
alternate_colors = ['#00e0e0', '#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 = int(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)
|