341 lines
12 KiB
Python
Executable File
341 lines
12 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# Copyright (c) 2011-2014 Intel Corporation.
|
|
# All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License version 2 as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# General Public License for more details.
|
|
#
|
|
# Author: Darren Hart <dvhart@linux.intel.com>
|
|
#
|
|
|
|
"""
|
|
Display details of the kernel build size.
|
|
|
|
The generated report is comprised of many sub-reports, starting with vmlinux,
|
|
and descending into each component built-in.o.
|
|
|
|
The first line of each report block is the table header, including the report
|
|
title and the column labels. Next is the report totals for the top level file
|
|
(vmlinux or built-in.o). This is followed by itemized sizes for any component
|
|
*.o object files and all built-in.o files from one directory down (the
|
|
built-in.o components are labeled with their parent directory to avoid
|
|
displaying "built-in.o" on nearly every line). The final lines display the sum
|
|
of all the itemized components and delta between the total and the sum.
|
|
|
|
An example report from an x86_64 allnoconfig build follows in part:
|
|
|
|
Linux Kernel (vmlinux) total | text data bss
|
|
--------------------------------------------------------------------------------
|
|
vmlinux 2201904 | 864548 121612 1215744
|
|
--------------------------------------------------------------------------------
|
|
arch/x86 282709 | 171021 65448 46240
|
|
kernel 249960 | 234355 7201 8404
|
|
mm 190369 | 154171 14154 22044
|
|
fs 163867 | 160820 1351 1696
|
|
drivers 44429 | 41353 2052 1024
|
|
lib 37143 | 37053 85 5
|
|
init 21535 | 5189 16285 61
|
|
security 3674 | 3658 8 8
|
|
net 122 | 122 0 0
|
|
--------------------------------------------------------------------------------
|
|
sum 993808 | 807742 106584 79482
|
|
delta 1208096 | 56806 15028 1136262
|
|
|
|
...
|
|
|
|
drivers total | text data bss
|
|
--------------------------------------------------------------------------------
|
|
drivers/built-in.o 44429 | 41353 2052 1024
|
|
--------------------------------------------------------------------------------
|
|
drivers/base 32427 | 31267 1060 100
|
|
drivers/char 9980 | 8412 656 912
|
|
drivers/rtc 1155 | 1155 0 0
|
|
drivers/clocksource 674 | 406 256 12
|
|
drivers/video 62 | 46 16 0
|
|
--------------------------------------------------------------------------------
|
|
sum 44298 | 41286 1988 1024
|
|
delta 131 | 67 64 0
|
|
|
|
The report may optionally display an additional level of drivers/* reports:
|
|
|
|
drivers/base total | text data bss
|
|
----------------------------------------------------------------------------
|
|
drivers/base/built-in.o 32427 | 31267 1060 100
|
|
----------------------------------------------------------------------------
|
|
drivers/base/*.o 32253 | 31121 1032 100
|
|
----------------------------------------------------------------------------
|
|
sum 32253 | 31121 1032 100
|
|
delta 174 | 146 28 0
|
|
|
|
...
|
|
"""
|
|
|
|
import sys
|
|
import getopt
|
|
import os
|
|
import struct
|
|
import termios
|
|
import fcntl
|
|
import glob
|
|
from subprocess import *
|
|
|
|
|
|
def usage():
|
|
print 'Usage: ksize [OPTION]...'
|
|
print ' -d, display an additional level of drivers detail'
|
|
print ' -h, --help display this help and exit'
|
|
print ''
|
|
print 'Run ksize from the top-level Linux kernel build directory. Always'
|
|
print 'perform a "make clean" before running a build to be measured by'
|
|
print 'ksize to avoid contaminating the report with unassociated build'
|
|
print 'artifacts'
|
|
|
|
|
|
def term_width():
|
|
"""
|
|
Determine the width of the terminal for formatting the report
|
|
|
|
Prefer the COLUMNS environment variable, and fall back to termios.
|
|
The width will be limited to the range of [70, 100], and will default to 80
|
|
if none can be determined, or if redirecting to a file.
|
|
"""
|
|
minw = 70
|
|
maxw = 100
|
|
|
|
if os.environ.has_key('COLUMNS'):
|
|
return max(minw, min(int(os.environ['COLUMNS']), maxw))
|
|
|
|
try:
|
|
(rows,cols) = struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, "CCRR"))
|
|
return max(minw, min(cols, maxw))
|
|
except IOError:
|
|
# Probably redirecting to a file
|
|
pass
|
|
except struct.error, err:
|
|
print "Error:", err
|
|
sys.exit(1)
|
|
|
|
return 80
|
|
|
|
|
|
def fmt_title(title, maxw):
|
|
"""
|
|
Format the title to fit within a maximum width
|
|
|
|
The title will be shifted left and prefixed with a '<' character as
|
|
necessary to fit within the maximum width.
|
|
|
|
Args:
|
|
title (str): Title to be formatted
|
|
maxw (int): Maximum width
|
|
"""
|
|
if len(title) <= maxw:
|
|
return title
|
|
else:
|
|
return "<%s" % (title[-(maxw - 1):])
|
|
|
|
|
|
class Sizes(object):
|
|
"""
|
|
Storage class for 'size -t' output
|
|
"""
|
|
def __init__(self, title="", files=None):
|
|
"""
|
|
Create a new Sizes container and populate it with the sum of the sizes
|
|
from the files list.
|
|
|
|
Args:
|
|
title (str, optional): Title to display via the show method
|
|
files (list of str, optional): Files to pass to 'size -t'
|
|
"""
|
|
self.title = title
|
|
self.text = self.data = self.bss = self.total = 0
|
|
|
|
if files:
|
|
p = Popen("size -t " + " ".join(files),
|
|
shell=True, stdout=PIPE, stderr=PIPE)
|
|
output = p.communicate()[0].splitlines()
|
|
|
|
if len(output) > 2:
|
|
sizes = output[-1].split()[0:4]
|
|
self.text = int(sizes[0])
|
|
self.data = int(sizes[1])
|
|
self.bss = int(sizes[2])
|
|
self.total = int(sizes[3])
|
|
|
|
def __add__(self, that):
|
|
if not (isinstance(self, Sizes) and isinstance(that, Sizes)):
|
|
raise TypeError
|
|
sum = Sizes()
|
|
sum.text = self.text + that.text
|
|
sum.data = self.data + that.data
|
|
sum.bss = self.bss + that.bss
|
|
sum.total = self.total + that.total
|
|
return sum
|
|
|
|
def __sub__(self, that):
|
|
if not (isinstance(self, Sizes) and isinstance(that, Sizes)):
|
|
raise TypeError
|
|
diff = Sizes()
|
|
diff.text = self.text - that.text
|
|
diff.data = self.data - that.data
|
|
diff.bss = self.bss - that.bss
|
|
diff.total = self.total - that.total
|
|
return diff
|
|
|
|
def show(self, cols, indent="", alt_title=None):
|
|
"""
|
|
Print a row in a report for sizes represented by this object
|
|
|
|
Args:
|
|
cols (int): Width of the report in characters
|
|
indent (str, optional): Literal indentation string, all spaces
|
|
alt_title (str, optional): An alternate title to display
|
|
"""
|
|
max_title = cols - 46 - len(indent)
|
|
if alt_title is not None:
|
|
title = fmt_title(alt_title, max_title)
|
|
else:
|
|
title = fmt_title(self.title, max_title)
|
|
print "%s%-*s %10d | %10d %10d %10d" % (
|
|
indent, max_title, title, self.total,
|
|
self.text, self.data, self.bss)
|
|
|
|
|
|
class Report(object):
|
|
"""
|
|
Container of sizes and sub reports
|
|
"""
|
|
@staticmethod
|
|
def create(title, filename, subglobs):
|
|
"""
|
|
Named constructor to create hierarchies of Report objects
|
|
|
|
Args:
|
|
title (str): Title of the report
|
|
filename (str): Top level build object filename
|
|
subglobs (list of str): Shell globs matching the components of the top
|
|
level filename
|
|
"""
|
|
r = Report(title, [filename])
|
|
|
|
# Create the .o object file report for this level
|
|
path = os.path.dirname(filename)
|
|
files = [p for p in glob.iglob(path + "/*.o") if not p.endswith("/built-in.o")]
|
|
r.parts.append(Report(path + "/*.o", files))
|
|
|
|
# Create the sub-reports based on each built-in.o
|
|
for g in subglobs:
|
|
for f in glob.glob(g):
|
|
path = os.path.dirname(f)
|
|
r.parts.append(Report.create(path, f, [path + "/*/built-in.o"]))
|
|
|
|
# Display in descending total size order
|
|
r.parts.sort(reverse=True)
|
|
|
|
# Calculate the sum and deltas from each component report
|
|
for b in r.parts:
|
|
r.totals += b.sizes
|
|
r.totals.title = "sum"
|
|
|
|
r.deltas = r.sizes - r.totals
|
|
r.deltas.title = "delta"
|
|
|
|
return r
|
|
|
|
def __init__(self, title, files):
|
|
"""
|
|
Create a new singular Report object, only called by Report.create()
|
|
|
|
Args:
|
|
title (str, optional): Title to display via the show method
|
|
files (list of str, optional): Files to construct Sizes
|
|
"""
|
|
self.files = files
|
|
self.title = title
|
|
self.parts = list()
|
|
self.sizes = Sizes(title, files)
|
|
self.totals = Sizes("sum")
|
|
self.deltas = Sizes("delta")
|
|
|
|
def show(self, cols, indent=""):
|
|
"""
|
|
Print the Report as a table with Sizes as rows
|
|
|
|
Args:
|
|
cols (int): Width of the report in characters
|
|
indent (str, optional): Literal indentation string, all spaces
|
|
"""
|
|
max_title = cols - 46 - len(indent)
|
|
title = fmt_title(self.title, max_title)
|
|
rule = str.ljust(indent, cols, '-')
|
|
|
|
# Print report table header
|
|
print "%s%-*s %10s | %10s %10s %10s" % (
|
|
indent, max_title, title, "total", "text", "data", "bss")
|
|
|
|
# Print top level report filename instead of title (usually path)
|
|
print rule
|
|
self.sizes.show(cols, indent, self.files[0])
|
|
print rule
|
|
|
|
# Print component sizes (*.o and */built-in.o)
|
|
for p in self.parts:
|
|
if p.sizes.total > 0:
|
|
p.sizes.show(cols, indent)
|
|
print rule
|
|
|
|
# Print the sum of the components, and the delta with the total
|
|
self.totals.show(cols, indent)
|
|
self.deltas.show(cols, indent)
|
|
print "\n"
|
|
|
|
def __cmp__(self, that):
|
|
if not isinstance(that, Report):
|
|
raise TypeError
|
|
return cmp(self.sizes.total, that.sizes.total)
|
|
|
|
|
|
def main(argv):
|
|
try:
|
|
opts, args = getopt.getopt(argv[1:], "dh", ["help"])
|
|
except getopt.GetoptError, err:
|
|
print '%s' % str(err)
|
|
usage()
|
|
return 2
|
|
|
|
driver_detail = False
|
|
for o, a in opts:
|
|
if o == '-d':
|
|
driver_detail = True
|
|
elif o in ('-h', '--help'):
|
|
usage()
|
|
return 0
|
|
else:
|
|
assert False, "unhandled option"
|
|
|
|
cols = term_width()
|
|
|
|
globs = ["arch/*/built-in.o", "*/built-in.o"]
|
|
vmlinux = Report.create("Melis Kernel (melis)", "ekernel/melis30.elf", globs)
|
|
|
|
vmlinux.show(cols)
|
|
for b in vmlinux.parts:
|
|
if b.totals.total > 0 and len(b.parts) > 1:
|
|
b.show(cols)
|
|
if b.title == "drivers" and driver_detail:
|
|
for d in b.parts:
|
|
if d.totals.total > 0 and len(d.parts) > 1:
|
|
d.show(cols, " ")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv))
|