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