[Buildroot] [PATCH 2/2] graph-build-time: generate graphs based on timing data

Thomas De Schampheleire patrickdepinguin+buildroot at gmail.com
Mon Oct 10 13:12:30 UTC 2011


Hi,

On Sun, Oct 9, 2011 at 6:17 PM, Thomas Petazzoni
<thomas.petazzoni at free-electrons.com> wrote:
> This script generates graphs of packages build time, from the timing
> data generated by Buildroot in the $(O)/build-time.data file.
>
> Example usage:
>
>  cat $(O)/build-time.data | \
>      ./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 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...)
>
> Signed-off-by: Thomas Petazzoni <thomas.petazzoni at free-electrons.com>
> ---
>  support/scripts/graph-build-time |  252 ++++++++++++++++++++++++++++++++++++++
>  1 files changed, 252 insertions(+), 0 deletions(-)
>  create mode 100755 support/scripts/graph-build-time
>
> diff --git a/support/scripts/graph-build-time b/support/scripts/graph-build-time
> new file mode 100755
> index 0000000..7fde852
> --- /dev/null
> +++ b/support/scripts/graph-build-time
> @@ -0,0 +1,252 @@
> +#!/usr/bin/env python
> +
> +# Copyright (C) 2011 by Thomas Petazzoni <thomas.petazzoni at free-electrons.com>
> +#
> +# 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.data file.
> +#
> +# Example usage:
> +#
> +#   cat $(O)/build-time.data | ./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 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...)
> +#
> +# 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 matplotlib
> +import numpy
> +matplotlib.use('PDF')
> +
> +import matplotlib.pyplot as plt
> +import matplotlib.font_manager as fm
> +import csv
> +import argparse
> +import sys
> +
> +steps = [ 'extract', 'patch', 'configure', 'build',
> +          'install-target', 'install-host', 'install-images',
> +          'install-staging' ]
> +
> +histogram_colors = ['#e60004', '#009836', '#2e1d86', '#ffed00',
> +                    '#0068b5', '#f28e00', '#940084', '#97c000',
> +                    '#00469b', '#f9c000' ]
> +
> +class Package:
> +    def __init__(self, name):
> +        self.name = name
> +        self.steps_duration = {}
> +        self.duration = 0
> +
> +    def add_step(self, step, duration):
> +        self.steps_duration[step] = duration
> +        self.duration += duration
> +
> +    def get_duration(self, step):
> +        if self.steps_duration.has_key(step):
> +            return self.steps_duration[step]
> +        else:
> +            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.duration, reverse=True)
> +
> +    # 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)

This could be simplified somewhat with list comprehensions (in
progressive forms of simplification):
vals = []
for step in steps:
    val = [ p.get_duration(step) for p in data ]
    vals.append(val)


vals = []
for step in steps:
    vals.append( [ p.get_duration(step) for p in data ] )


vals = [ [ p.get_duration(step) for p in data ] for step in steps ]

At least, to me this is simpler to understand, maybe you prefer the
unwound approach.

> +
> +    bottom = [0] * n_pkgs
> +    legenditems = []
> +
> +    plt.figure()
> +
> +    # Draw the bars, step by step
> +    for i in range(0, len(vals)):
> +        b = plt.bar(ind, vals[i], 1, color=histogram_colors[i], bottom=bottom, linewidth=0)
> +        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 + .5, [ p.name for p in data ], rotation=90, fontsize=4)
> +
> +    # Adjust size of graph (double the width)
> +    sz = plt.gcf().get_size_inches()
> +    plt.gcf().set_size_inches(sz[0] * 2, sz[1])
> +
> +    # Add more space for the package names at the bottom
> +    plt.gcf().subplots_adjust(bottom=0.2)
> +
> +    # 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 == "build":
> +        plt.title('Build time of packages, by build order')
> +    elif order == "duration":
> +        plt.title('Build time of packages, by duration order')
> +
> +    # 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.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 data:
> +        if p.duration < (total * 0.01):
> +            other_value += p.duration
> +        else:
> +            labels.append(p.name)
> +            values.append(p.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)
> +
> +    # 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)
> +
> +    # 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():
> +    reader = csv.reader(sys.stdin, 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:
> +        pkg = row[0]
> +        step = row[1]
> +        duration = float(row[2]) / 1000.
> +
> +        p = getpkg(pkg)
> +        if p is None:
> +            p = Package(pkg)
> +            pkgs.append(p)
> +
> +        p.add_step(step, duration)
> +
> +    return pkgs
> +
> +parser = argparse.ArgumentParser(description='Draw build time graphs')
> +parser.add_argument("--type", metavar="GRAPH_TYPE", required=True,
> +                    help="Type of graph (histogram, pie-packages, pie-steps)")
> +parser.add_argument("--order", metavar="GRAPH_ORDER",
> +                    help="Ordering of packages: build or duration (for histogram only)")
> +parser.add_argument("--output", metavar="OUTPUT", required=True,
> +                    help="Output file (PDF extension)")
> +args = parser.parse_args()
> +
> +d = read_data()
> +
> +if args.type == "histogram":
> +    if args.order == "build" or args.order == "duration":
> +        pkg_histogram(d, args.output, args.order)
> +    else:
> +        print "Unknown graph order"
> +        sys.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)
> +

Best regards,
Thomas


More information about the buildroot mailing list