3 # Copyright (C) 2014 by Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 import matplotlib
.font_manager
as fm
30 import matplotlib
.pyplot
as plt
32 sys
.stderr
.write("You need python-matplotlib to generate the size graph\n")
35 colors
= ['#e60004', '#009836', '#2e1d86', '#ffed00',
36 '#0068b5', '#f28e00', '#940084', '#97c000']
39 # This function adds a new file to 'filesdict', after checking its
40 # size. The 'filesdict' contain the relative path of the file as the
41 # key, and as the value a tuple containing the name of the package to
42 # which the file belongs and the size of the file.
44 # filesdict: the dict to which the file is added
45 # relpath: relative path of the file
46 # fullpath: absolute path to the file
47 # pkg: package to which the file belongs
49 def add_file(filesdict
, relpath
, abspath
, pkg
):
50 if not os
.path
.exists(abspath
):
52 if os
.path
.islink(abspath
):
54 sz
= os
.stat(abspath
).st_size
55 filesdict
[relpath
] = (pkg
, sz
)
58 # This function returns a dict where each key is the path of a file in
59 # the root filesystem, and the value is a tuple containing two
60 # elements: the name of the package to which this file belongs and the
63 # builddir: path to the Buildroot output directory
65 def build_package_dict(builddir
):
67 with
open(os
.path
.join(builddir
, "build", "packages-file-list.txt")) as filelistf
:
68 for l
in filelistf
.readlines():
69 pkg
, fpath
= l
.split(",", 1)
70 # remove the initial './' in each file path
71 fpath
= fpath
.strip()[2:]
72 fullpath
= os
.path
.join(builddir
, "target", fpath
)
73 add_file(filesdict
, fpath
, fullpath
, pkg
)
77 # This function builds a dictionary that contains the name of a
78 # package as key, and the size of the files installed by this package
81 # filesdict: dictionary with the name of the files as key, and as
82 # value a tuple containing the name of the package to which the files
83 # belongs, and the size of the file. As returned by
86 # builddir: path to the Buildroot output directory
88 def build_package_size(filesdict
, builddir
):
89 pkgsize
= collections
.defaultdict(int)
92 for root
, _
, files
in os
.walk(os
.path
.join(builddir
, "target")):
94 fpath
= os
.path
.join(root
, f
)
95 if os
.path
.islink(fpath
):
99 if st
.st_ino
in seeninodes
:
103 seeninodes
.add(st
.st_ino
)
105 frelpath
= os
.path
.relpath(fpath
, os
.path
.join(builddir
, "target"))
106 if not frelpath
in filesdict
:
107 print("WARNING: %s is not part of any package" % frelpath
)
110 pkg
= filesdict
[frelpath
][0]
112 pkgsize
[pkg
] += st
.st_size
117 # Given a dict returned by build_package_size(), this function
118 # generates a pie chart of the size installed by each package.
120 # pkgsize: dictionary with the name of the package as a key, and the
121 # size as the value, as returned by build_package_size.
123 # outputf: output file for the graph
125 def draw_graph(pkgsize
, outputf
):
126 total
= sum(pkgsize
.values())
130 for (p
, sz
) in pkgsize
.items():
131 if sz
< (total
* 0.01):
134 labels
.append("%s (%d kB)" % (p
, sz
/ 1000.))
136 labels
.append("Other (%d kB)" % (other_value
/ 1000.))
137 values
.append(other_value
)
140 patches
, texts
, autotexts
= plt
.pie(values
, labels
=labels
,
141 autopct
='%1.1f%%', shadow
=True,
144 proptease
= fm
.FontProperties()
145 proptease
.set_size('xx-small')
146 plt
.setp(autotexts
, fontproperties
=proptease
)
147 plt
.setp(texts
, fontproperties
=proptease
)
149 plt
.suptitle("Filesystem size per package", fontsize
=18, y
=.97)
150 plt
.title("Total filesystem size: %d kB" % (total
/ 1000.), fontsize
=10, y
=.96)
154 # Generate a CSV file with statistics about the size of each file, its
155 # size contribution to the package and to the overall system.
157 # filesdict: dictionary with the name of the files as key, and as
158 # value a tuple containing the name of the package to which the files
159 # belongs, and the size of the file. As returned by
160 # build_package_dict.
162 # pkgsize: dictionary with the name of the package as a key, and the
163 # size as the value, as returned by build_package_size.
165 # outputf: output CSV file
167 def gen_files_csv(filesdict
, pkgsizes
, outputf
):
169 for (p
, sz
) in pkgsizes
.items():
171 with
open(outputf
, 'w') as csvfile
:
172 wr
= csv
.writer(csvfile
, delimiter
=',', quoting
=csv
.QUOTE_MINIMAL
)
173 wr
.writerow(["File name",
177 "File size in package (%)",
178 "File size in system (%)"])
179 for f
, (pkgname
, filesize
) in filesdict
.items():
180 pkgsize
= pkgsizes
[pkgname
]
181 wr
.writerow([f
, pkgname
, filesize
, pkgsize
,
182 "%.1f" % (float(filesize
) / pkgsize
* 100),
183 "%.1f" % (float(filesize
) / total
* 100)])
187 # Generate a CSV file with statistics about the size of each package,
188 # and their size contribution to the overall system.
190 # pkgsize: dictionary with the name of the package as a key, and the
191 # size as the value, as returned by build_package_size.
193 # outputf: output CSV file
195 def gen_packages_csv(pkgsizes
, outputf
):
196 total
= sum(pkgsizes
.values())
197 with
open(outputf
, 'w') as csvfile
:
198 wr
= csv
.writer(csvfile
, delimiter
=',', quoting
=csv
.QUOTE_MINIMAL
)
199 wr
.writerow(["Package name", "Package size", "Package size in system (%)"])
200 for (pkg
, size
) in pkgsizes
.items():
201 wr
.writerow([pkg
, size
, "%.1f" % (float(size
) / total
* 100)])
203 parser
= argparse
.ArgumentParser(description
='Draw size statistics graphs')
205 parser
.add_argument("--builddir", '-i', metavar
="BUILDDIR", required
=True,
206 help="Buildroot output directory")
207 parser
.add_argument("--graph", '-g', metavar
="GRAPH",
208 help="Graph output file (.pdf or .png extension)")
209 parser
.add_argument("--file-size-csv", '-f', metavar
="FILE_SIZE_CSV",
210 help="CSV output file with file size statistics")
211 parser
.add_argument("--package-size-csv", '-p', metavar
="PKG_SIZE_CSV",
212 help="CSV output file with package size statistics")
213 args
= parser
.parse_args()
215 # Find out which package installed what files
216 pkgdict
= build_package_dict(args
.builddir
)
218 # Collect the size installed by each package
219 pkgsize
= build_package_size(pkgdict
, args
.builddir
)
222 draw_graph(pkgsize
, args
.graph
)
223 if args
.file_size_csv
:
224 gen_files_csv(pkgdict
, pkgsize
, args
.file_size_csv
)
225 if args
.package_size_csv
:
226 gen_packages_csv(pkgsize
, args
.package_size_csv
)