1*4882a593Smuzhiyun#!/usr/bin/env python3 2*4882a593Smuzhiyun 3*4882a593Smuzhiyun# Copyright (C) 2011 by Thomas Petazzoni <thomas.petazzoni@free-electrons.com> 4*4882a593Smuzhiyun# Copyright (C) 2013 by Yann E. MORIN <yann.morin.1998@free.fr> 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# This program is free software; you can redistribute it and/or modify 7*4882a593Smuzhiyun# it under the terms of the GNU General Public License as published by 8*4882a593Smuzhiyun# the Free Software Foundation; either version 2 of the License, or 9*4882a593Smuzhiyun# (at your option) any later version. 10*4882a593Smuzhiyun# 11*4882a593Smuzhiyun# This program is distributed in the hope that it will be useful, 12*4882a593Smuzhiyun# but WITHOUT ANY WARRANTY; without even the implied warranty of 13*4882a593Smuzhiyun# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14*4882a593Smuzhiyun# General Public License for more details. 15*4882a593Smuzhiyun# 16*4882a593Smuzhiyun# You should have received a copy of the GNU General Public License 17*4882a593Smuzhiyun# along with this program; if not, write to the Free Software 18*4882a593Smuzhiyun# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19*4882a593Smuzhiyun 20*4882a593Smuzhiyun# This script generates graphs of packages build time, from the timing 21*4882a593Smuzhiyun# data generated by Buildroot in the $(O)/build-time.log file. 22*4882a593Smuzhiyun# 23*4882a593Smuzhiyun# Example usage: 24*4882a593Smuzhiyun# 25*4882a593Smuzhiyun# cat $(O)/build-time.log | ./support/scripts/graph-build-time --type=histogram --output=foobar.pdf 26*4882a593Smuzhiyun# 27*4882a593Smuzhiyun# Three graph types are available : 28*4882a593Smuzhiyun# 29*4882a593Smuzhiyun# * histogram, which creates an histogram of the build time for each 30*4882a593Smuzhiyun# package, decomposed by each step (extract, patch, configure, 31*4882a593Smuzhiyun# etc.). The order in which the packages are shown is 32*4882a593Smuzhiyun# configurable: by package name, by build order, or by duration 33*4882a593Smuzhiyun# order. See the --order option. 34*4882a593Smuzhiyun# 35*4882a593Smuzhiyun# * pie-packages, which creates a pie chart of the build time of 36*4882a593Smuzhiyun# each package (without decomposition in steps). Packages that 37*4882a593Smuzhiyun# contributed to less than 1% of the overall build time are all 38*4882a593Smuzhiyun# grouped together in an "Other" entry. 39*4882a593Smuzhiyun# 40*4882a593Smuzhiyun# * pie-steps, which creates a pie chart of the time spent globally 41*4882a593Smuzhiyun# on each step (extract, patch, configure, etc...) 42*4882a593Smuzhiyun# 43*4882a593Smuzhiyun# The default is to generate an histogram ordered by package name. 44*4882a593Smuzhiyun# 45*4882a593Smuzhiyun# Requirements: 46*4882a593Smuzhiyun# 47*4882a593Smuzhiyun# * matplotlib (python-matplotlib on Debian/Ubuntu systems) 48*4882a593Smuzhiyun# * numpy (python-numpy on Debian/Ubuntu systems) 49*4882a593Smuzhiyun# * argparse (by default in Python 2.7, requires python-argparse if 50*4882a593Smuzhiyun# Python 2.6 is used) 51*4882a593Smuzhiyun 52*4882a593Smuzhiyunimport sys 53*4882a593Smuzhiyun 54*4882a593Smuzhiyuntry: 55*4882a593Smuzhiyun import matplotlib as mpl 56*4882a593Smuzhiyun import numpy 57*4882a593Smuzhiyunexcept ImportError: 58*4882a593Smuzhiyun sys.stderr.write("You need python-matplotlib and python-numpy to generate build graphs\n") 59*4882a593Smuzhiyun exit(1) 60*4882a593Smuzhiyun 61*4882a593Smuzhiyun# Use the Agg backend (which produces a PNG output, see 62*4882a593Smuzhiyun# http://matplotlib.org/faq/usage_faq.html#what-is-a-backend), 63*4882a593Smuzhiyun# otherwise an incorrect backend is used on some host machines). 64*4882a593Smuzhiyun# Note: matplotlib.use() must be called *before* matplotlib.pyplot. 65*4882a593Smuzhiyunmpl.use('Agg') 66*4882a593Smuzhiyun 67*4882a593Smuzhiyunimport matplotlib.pyplot as plt # noqa: E402 68*4882a593Smuzhiyunimport matplotlib.font_manager as fm # noqa: E402 69*4882a593Smuzhiyunimport csv # noqa: E402 70*4882a593Smuzhiyunimport argparse # noqa: E402 71*4882a593Smuzhiyun 72*4882a593Smuzhiyunsteps = ['download', 'extract', 'patch', 'configure', 'build', 73*4882a593Smuzhiyun 'install-target', 'install-staging', 'install-images', 74*4882a593Smuzhiyun 'install-host'] 75*4882a593Smuzhiyun 76*4882a593Smuzhiyundefault_colors = ['#8d02ff', '#e60004', '#009836', '#2e1d86', '#ffed00', 77*4882a593Smuzhiyun '#0068b5', '#f28e00', '#940084', '#97c000'] 78*4882a593Smuzhiyun 79*4882a593Smuzhiyunalternate_colors = ['#ffbe0a', '#96bdff', '#3f7f7f', '#ff0000', '#00c000', 80*4882a593Smuzhiyun '#0080ff', '#c000ff', '#00eeee', '#e0e000'] 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun 83*4882a593Smuzhiyunclass Package: 84*4882a593Smuzhiyun def __init__(self, name): 85*4882a593Smuzhiyun self.name = name 86*4882a593Smuzhiyun self.steps_duration = {} 87*4882a593Smuzhiyun self.steps_start = {} 88*4882a593Smuzhiyun self.steps_end = {} 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun def add_step(self, step, state, time): 91*4882a593Smuzhiyun if state == "start": 92*4882a593Smuzhiyun self.steps_start[step] = time 93*4882a593Smuzhiyun else: 94*4882a593Smuzhiyun self.steps_end[step] = time 95*4882a593Smuzhiyun if step in self.steps_start and step in self.steps_end: 96*4882a593Smuzhiyun self.steps_duration[step] = self.steps_end[step] - self.steps_start[step] 97*4882a593Smuzhiyun 98*4882a593Smuzhiyun def get_duration(self, step=None): 99*4882a593Smuzhiyun if step is None: 100*4882a593Smuzhiyun duration = 0 101*4882a593Smuzhiyun for step in list(self.steps_duration.keys()): 102*4882a593Smuzhiyun duration += self.steps_duration[step] 103*4882a593Smuzhiyun return duration 104*4882a593Smuzhiyun if step in self.steps_duration: 105*4882a593Smuzhiyun return self.steps_duration[step] 106*4882a593Smuzhiyun return 0 107*4882a593Smuzhiyun 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun# Generate an histogram of the time spent in each step of each 110*4882a593Smuzhiyun# package. 111*4882a593Smuzhiyundef pkg_histogram(data, output, order="build"): 112*4882a593Smuzhiyun n_pkgs = len(data) 113*4882a593Smuzhiyun ind = numpy.arange(n_pkgs) 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun if order == "duration": 116*4882a593Smuzhiyun data = sorted(data, key=lambda p: p.get_duration(), reverse=True) 117*4882a593Smuzhiyun elif order == "name": 118*4882a593Smuzhiyun data = sorted(data, key=lambda p: p.name, reverse=False) 119*4882a593Smuzhiyun 120*4882a593Smuzhiyun # Prepare the vals array, containing one entry for each step 121*4882a593Smuzhiyun vals = [] 122*4882a593Smuzhiyun for step in steps: 123*4882a593Smuzhiyun val = [] 124*4882a593Smuzhiyun for p in data: 125*4882a593Smuzhiyun val.append(p.get_duration(step)) 126*4882a593Smuzhiyun vals.append(val) 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun bottom = [0] * n_pkgs 129*4882a593Smuzhiyun legenditems = [] 130*4882a593Smuzhiyun 131*4882a593Smuzhiyun plt.figure() 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun # Draw the bars, step by step 134*4882a593Smuzhiyun for i in range(0, len(vals)): 135*4882a593Smuzhiyun b = plt.bar(ind+0.1, vals[i], width=0.8, color=colors[i], bottom=bottom, linewidth=0.25) 136*4882a593Smuzhiyun legenditems.append(b[0]) 137*4882a593Smuzhiyun bottom = [bottom[j] + vals[i][j] for j in range(0, len(vals[i]))] 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun # Draw the package names 140*4882a593Smuzhiyun plt.xticks(ind + .6, [p.name for p in data], rotation=-60, rotation_mode="anchor", fontsize=8, ha='left') 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun # Adjust size of graph depending on the number of packages 143*4882a593Smuzhiyun # Ensure a minimal size twice as the default 144*4882a593Smuzhiyun # Magic Numbers do Magic Layout! 145*4882a593Smuzhiyun ratio = max(((n_pkgs + 10) / 48, 2)) 146*4882a593Smuzhiyun borders = 0.1 / ratio 147*4882a593Smuzhiyun sz = plt.gcf().get_figwidth() 148*4882a593Smuzhiyun plt.gcf().set_figwidth(sz * ratio) 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun # Adjust space at borders, add more space for the 151*4882a593Smuzhiyun # package names at the bottom 152*4882a593Smuzhiyun plt.gcf().subplots_adjust(bottom=0.2, left=borders, right=1-borders) 153*4882a593Smuzhiyun 154*4882a593Smuzhiyun # Remove ticks in the graph for each package 155*4882a593Smuzhiyun axes = plt.gcf().gca() 156*4882a593Smuzhiyun for line in axes.get_xticklines(): 157*4882a593Smuzhiyun line.set_markersize(0) 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun axes.set_ylabel('Time (seconds)') 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun # Reduce size of legend text 162*4882a593Smuzhiyun leg_prop = fm.FontProperties(size=6) 163*4882a593Smuzhiyun 164*4882a593Smuzhiyun # Draw legend 165*4882a593Smuzhiyun plt.legend(legenditems, steps, prop=leg_prop) 166*4882a593Smuzhiyun 167*4882a593Smuzhiyun if order == "name": 168*4882a593Smuzhiyun plt.title('Build time of packages\n') 169*4882a593Smuzhiyun elif order == "build": 170*4882a593Smuzhiyun plt.title('Build time of packages, by build order\n') 171*4882a593Smuzhiyun elif order == "duration": 172*4882a593Smuzhiyun plt.title('Build time of packages, by duration order\n') 173*4882a593Smuzhiyun 174*4882a593Smuzhiyun # Save graph 175*4882a593Smuzhiyun plt.savefig(output) 176*4882a593Smuzhiyun 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun# Generate a pie chart with the time spent building each package. 179*4882a593Smuzhiyundef pkg_pie_time_per_package(data, output): 180*4882a593Smuzhiyun # Compute total build duration 181*4882a593Smuzhiyun total = 0 182*4882a593Smuzhiyun for p in data: 183*4882a593Smuzhiyun total += p.get_duration() 184*4882a593Smuzhiyun 185*4882a593Smuzhiyun # Build the list of labels and values, and filter the packages 186*4882a593Smuzhiyun # that account for less than 1% of the build time. 187*4882a593Smuzhiyun labels = [] 188*4882a593Smuzhiyun values = [] 189*4882a593Smuzhiyun other_value = 0 190*4882a593Smuzhiyun for p in sorted(data, key=lambda p: p.get_duration()): 191*4882a593Smuzhiyun if p.get_duration() < (total * 0.01): 192*4882a593Smuzhiyun other_value += p.get_duration() 193*4882a593Smuzhiyun else: 194*4882a593Smuzhiyun labels.append(p.name) 195*4882a593Smuzhiyun values.append(p.get_duration()) 196*4882a593Smuzhiyun 197*4882a593Smuzhiyun labels.append('Other') 198*4882a593Smuzhiyun values.append(other_value) 199*4882a593Smuzhiyun 200*4882a593Smuzhiyun plt.figure() 201*4882a593Smuzhiyun 202*4882a593Smuzhiyun # Draw pie graph 203*4882a593Smuzhiyun patches, texts, autotexts = plt.pie(values, labels=labels, 204*4882a593Smuzhiyun autopct='%1.1f%%', shadow=True, 205*4882a593Smuzhiyun colors=colors) 206*4882a593Smuzhiyun 207*4882a593Smuzhiyun # Reduce text size 208*4882a593Smuzhiyun proptease = fm.FontProperties() 209*4882a593Smuzhiyun proptease.set_size('xx-small') 210*4882a593Smuzhiyun plt.setp(autotexts, fontproperties=proptease) 211*4882a593Smuzhiyun plt.setp(texts, fontproperties=proptease) 212*4882a593Smuzhiyun 213*4882a593Smuzhiyun plt.title('Build time per package') 214*4882a593Smuzhiyun plt.savefig(output) 215*4882a593Smuzhiyun 216*4882a593Smuzhiyun 217*4882a593Smuzhiyun# Generate a pie chart with a portion for the overall time spent in 218*4882a593Smuzhiyun# each step for all packages. 219*4882a593Smuzhiyundef pkg_pie_time_per_step(data, output): 220*4882a593Smuzhiyun steps_values = [] 221*4882a593Smuzhiyun for step in steps: 222*4882a593Smuzhiyun val = 0 223*4882a593Smuzhiyun for p in data: 224*4882a593Smuzhiyun val += p.get_duration(step) 225*4882a593Smuzhiyun steps_values.append(val) 226*4882a593Smuzhiyun 227*4882a593Smuzhiyun plt.figure() 228*4882a593Smuzhiyun 229*4882a593Smuzhiyun # Draw pie graph 230*4882a593Smuzhiyun patches, texts, autotexts = plt.pie(steps_values, labels=steps, 231*4882a593Smuzhiyun autopct='%1.1f%%', shadow=True, 232*4882a593Smuzhiyun colors=colors) 233*4882a593Smuzhiyun 234*4882a593Smuzhiyun # Reduce text size 235*4882a593Smuzhiyun proptease = fm.FontProperties() 236*4882a593Smuzhiyun proptease.set_size('xx-small') 237*4882a593Smuzhiyun plt.setp(autotexts, fontproperties=proptease) 238*4882a593Smuzhiyun plt.setp(texts, fontproperties=proptease) 239*4882a593Smuzhiyun 240*4882a593Smuzhiyun plt.title('Build time per step') 241*4882a593Smuzhiyun plt.savefig(output) 242*4882a593Smuzhiyun 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun# Parses the csv file passed on standard input and returns a list of 245*4882a593Smuzhiyun# Package objects, filed with the duration of each step and the total 246*4882a593Smuzhiyun# duration of the package. 247*4882a593Smuzhiyundef read_data(input_file): 248*4882a593Smuzhiyun if input_file is None: 249*4882a593Smuzhiyun input_file = sys.stdin 250*4882a593Smuzhiyun else: 251*4882a593Smuzhiyun input_file = open(input_file) 252*4882a593Smuzhiyun reader = csv.reader(input_file, delimiter=':') 253*4882a593Smuzhiyun pkgs = [] 254*4882a593Smuzhiyun 255*4882a593Smuzhiyun # Auxilliary function to find a package by name in the list. 256*4882a593Smuzhiyun def getpkg(name): 257*4882a593Smuzhiyun for p in pkgs: 258*4882a593Smuzhiyun if p.name == name: 259*4882a593Smuzhiyun return p 260*4882a593Smuzhiyun return None 261*4882a593Smuzhiyun 262*4882a593Smuzhiyun for row in reader: 263*4882a593Smuzhiyun time = float(row[0].strip()) 264*4882a593Smuzhiyun state = row[1].strip() 265*4882a593Smuzhiyun step = row[2].strip() 266*4882a593Smuzhiyun pkg = row[3].strip() 267*4882a593Smuzhiyun 268*4882a593Smuzhiyun p = getpkg(pkg) 269*4882a593Smuzhiyun if p is None: 270*4882a593Smuzhiyun p = Package(pkg) 271*4882a593Smuzhiyun pkgs.append(p) 272*4882a593Smuzhiyun 273*4882a593Smuzhiyun p.add_step(step, state, time) 274*4882a593Smuzhiyun 275*4882a593Smuzhiyun return pkgs 276*4882a593Smuzhiyun 277*4882a593Smuzhiyun 278*4882a593Smuzhiyunparser = argparse.ArgumentParser(description='Draw build time graphs') 279*4882a593Smuzhiyunparser.add_argument("--type", '-t', metavar="GRAPH_TYPE", 280*4882a593Smuzhiyun help="Type of graph (histogram, pie-packages, pie-steps)") 281*4882a593Smuzhiyunparser.add_argument("--order", '-O', metavar="GRAPH_ORDER", 282*4882a593Smuzhiyun help="Ordering of packages: build or duration (for histogram only)") 283*4882a593Smuzhiyunparser.add_argument("--alternate-colors", '-c', action="store_true", 284*4882a593Smuzhiyun help="Use alternate colour-scheme") 285*4882a593Smuzhiyunparser.add_argument("--input", '-i', metavar="INPUT", 286*4882a593Smuzhiyun help="Input file (usually $(O)/build/build-time.log)") 287*4882a593Smuzhiyunparser.add_argument("--output", '-o', metavar="OUTPUT", required=True, 288*4882a593Smuzhiyun help="Output file (.pdf or .png extension)") 289*4882a593Smuzhiyunargs = parser.parse_args() 290*4882a593Smuzhiyun 291*4882a593Smuzhiyund = read_data(args.input) 292*4882a593Smuzhiyun 293*4882a593Smuzhiyunif args.alternate_colors: 294*4882a593Smuzhiyun colors = alternate_colors 295*4882a593Smuzhiyunelse: 296*4882a593Smuzhiyun colors = default_colors 297*4882a593Smuzhiyun 298*4882a593Smuzhiyunif args.type == "histogram" or args.type is None: 299*4882a593Smuzhiyun if args.order == "build" or args.order == "duration" or args.order == "name": 300*4882a593Smuzhiyun pkg_histogram(d, args.output, args.order) 301*4882a593Smuzhiyun elif args.order is None: 302*4882a593Smuzhiyun pkg_histogram(d, args.output, "name") 303*4882a593Smuzhiyun else: 304*4882a593Smuzhiyun sys.stderr.write("Unknown ordering: %s\n" % args.order) 305*4882a593Smuzhiyun exit(1) 306*4882a593Smuzhiyunelif args.type == "pie-packages": 307*4882a593Smuzhiyun pkg_pie_time_per_package(d, args.output) 308*4882a593Smuzhiyunelif args.type == "pie-steps": 309*4882a593Smuzhiyun pkg_pie_time_per_step(d, args.output) 310*4882a593Smuzhiyunelse: 311*4882a593Smuzhiyun sys.stderr.write("Unknown type: %s\n" % args.type) 312*4882a593Smuzhiyun exit(1) 313