#!/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 # """ 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("Linux Kernel (vmlinux)", "vmlinux", 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))