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.
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.
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
55 import matplotlib
as mpl
58 sys
.stderr
.write("You need python-matplotlib and python-numpy to generate build graphs\n")
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.
67 import matplotlib
.pyplot
as plt
68 import matplotlib
.font_manager
as fm
72 steps
= [ 'extract', 'patch', 'configure', 'build',
73 'install-target', 'install-staging', 'install-images',
76 default_colors
= ['#e60004', '#009836', '#2e1d86', '#ffed00',
77 '#0068b5', '#f28e00', '#940084', '#97c000']
79 alternate_colors
= ['#00e0e0', '#3f7f7f', '#ff0000', '#00c000',
80 '#0080ff', '#c000ff', '#00eeee', '#e0e000']
83 def __init__(self
, name
):
85 self
.steps_duration
= {}
89 def add_step(self
, step
, state
, time
):
91 self
.steps_start
[step
] = time
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):
100 for step
in list(self
.steps_duration
.keys()):
101 duration
+= self
.steps_duration
[step
]
103 if step
in self
.steps_duration
:
104 return self
.steps_duration
[step
]
107 # Generate an histogram of the time spent in each step of each
109 def pkg_histogram(data
, output
, order
="build"):
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
123 val
.append(p
.get_duration(step
))
126 bottom
= [0] * n_pkgs
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)
163 plt
.legend(legenditems
, steps
, prop
=leg_prop
)
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')
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
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.
188 if p
.get_duration() < (total
* 0.01):
189 other_value
+= p
.get_duration()
191 labels
.append(p
.name
)
192 values
.append(p
.get_duration())
194 labels
.append('Other')
195 values
.append(other_value
)
200 patches
, texts
, autotexts
= plt
.pie(values
, labels
=labels
,
201 autopct
='%1.1f%%', shadow
=True,
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')
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
):
220 val
+= p
.get_duration(step
)
221 steps_values
.append(val
)
226 patches
, texts
, autotexts
= plt
.pie(steps_values
, labels
=steps
,
227 autopct
='%1.1f%%', shadow
=True,
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')
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
246 input_file
= open(input_file
)
247 reader
= csv
.reader(input_file
, delimiter
=':')
250 # Auxilliary function to find a package by name in the list.
258 time
= int(row
[0].strip())
259 state
= row
[1].strip()
260 step
= row
[2].strip()
268 p
.add_step(step
, state
, time
)
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
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")
298 sys
.stderr
.write("Unknown ordering: %s\n" % args
.order
)
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
)
305 sys
.stderr
.write("Unknown type: %s\n" % args
.type)