| #!/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 = ['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) |