xref: /OK3568_Linux_fs/buildroot/support/scripts/graph-build-time (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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