7eb3e479532698db4400f7a01e352627aa001d63
[packages/trusty/cirros-testvm.git] / cirros-testvm / src-cirros / buildroot-2015.05 / support / scripts / graph-build-time
1 #!/usr/bin/env python
2
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>
5 #
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.
10 #
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.
15 #
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
19
20 # This script generates graphs of packages build time, from the timing
21 # data generated by Buildroot in the $(O)/build-time.log file.
22 #
23 # Example usage:
24 #
25 #   cat $(O)/build-time.log | ./support/scripts/graph-build-time --type=histogram --output=foobar.pdf
26 #
27 # Three graph types are available :
28 #
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.
34 #
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.
39 #
40 #   * pie-steps, which creates a pie chart of the time spent globally
41 #     on each step (extract, patch, configure, etc...)
42 #
43 # The default is to generate an histogram ordered by package name.
44 #
45 # Requirements:
46 #
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)
51
52 import sys
53
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)
60
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')
66
67 import matplotlib.pyplot as plt
68 import matplotlib.font_manager as fm
69 import csv
70 import argparse
71
72 steps = [ 'extract', 'patch', 'configure', 'build',
73           'install-target', 'install-staging', 'install-images',
74           'install-host']
75
76 default_colors = ['#e60004', '#009836', '#2e1d86', '#ffed00',
77                   '#0068b5', '#f28e00', '#940084', '#97c000']
78
79 alternate_colors = ['#00e0e0', '#3f7f7f', '#ff0000', '#00c000',
80                     '#0080ff', '#c000ff', '#00eeee', '#e0e000']
81
82 class Package:
83     def __init__(self, name):
84         self.name = name
85         self.steps_duration = {}
86         self.steps_start = {}
87         self.steps_end = {}
88
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]
96
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
106
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)
112
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)
117
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)
125
126     bottom = [0] * n_pkgs
127     legenditems = []
128
129     plt.figure()
130
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])) ]
136
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')
139
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)
147
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)
151
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)
156
157     axes.set_ylabel('Time (seconds)')
158
159     # Reduce size of legend text
160     leg_prop = fm.FontProperties(size=6)
161
162     # Draw legend
163     plt.legend(legenditems, steps, prop=leg_prop)
164
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')
171
172     # Save graph
173     plt.savefig(output)
174
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()
181
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())
193
194     labels.append('Other')
195     values.append(other_value)
196
197     plt.figure()
198
199     # Draw pie graph
200     patches, texts, autotexts = plt.pie(values, labels=labels,
201                                         autopct='%1.1f%%', shadow=True,
202                                         colors=colors)
203
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)
209
210     plt.title('Build time per package')
211     plt.savefig(output)
212
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)
222
223     plt.figure()
224
225     # Draw pie graph
226     patches, texts, autotexts = plt.pie(steps_values, labels=steps,
227                                         autopct='%1.1f%%', shadow=True,
228                                         colors=colors)
229
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)
235
236     plt.title('Build time per step')
237     plt.savefig(output)
238
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 = []
249
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
256
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()
262
263         p = getpkg(pkg)
264         if p is None:
265             p = Package(pkg)
266             pkgs.append(p)
267
268         p.add_step(step, state, time)
269
270     return pkgs
271
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="OUTPUT",
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()
284
285 d = read_data(args.input)
286
287 if args.alternate_colors:
288     colors = alternate_colors
289 else:
290     colors = default_colors
291
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)