#!/usr/bin/env python3
#
# Copyright (c) 2011, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Display details of the kernel build size, broken up by built-in.[o,a]. Sort
# the objects by size. Run from the top level kernel build directory.
#
# Author: Darren Hart <dvhart@linux.intel.com>
#

import sys
import getopt
import os
from subprocess import *

def usage():
    prog = os.path.basename(sys.argv[0])
    print('Usage: %s [OPTION]...' % prog)
    print('  -d,                 display an additional level of drivers detail')
    print('  -h, --help          display this help and exit')
    print('')
    print('Run %s from the top-level Linux kernel build directory.' % prog)


class Sizes:
    def __init__(self, glob):
        self.title = glob
        p = Popen("size -t " + str(glob), shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True)
        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])
        else:
            self.text = self.data = self.bss = self.total = 0

    def show(self, indent=""):
        print("%-32s %10d | %10d %10d %10d" % \
              (indent+self.title, self.total, self.text, self.data, self.bss))


class Report:
    def create(filename, title, subglob=None):
        r = Report(filename, title)
        path = os.path.dirname(filename)

        p = Popen("ls " + str(path) + "/*.o | grep -v built-in.o",
                  shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True)
        glob = ' '.join(p.communicate()[0].splitlines())
        oreport = Report(glob, str(path) + "/*.o")
        oreport.sizes.title = str(path) + "/*.o"
        r.parts.append(oreport)

        if subglob:
            p = Popen("ls " + subglob, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True)
            for f in p.communicate()[0].splitlines():
                path = os.path.dirname(f)
                r.parts.append(Report.create(f, path, str(path) + "/*/built-in.[o,a]"))
            r.parts.sort(reverse=True)

        for b in r.parts:
            r.totals["total"] += b.sizes.total
            r.totals["text"] += b.sizes.text
            r.totals["data"] += b.sizes.data
            r.totals["bss"] += b.sizes.bss

        r.deltas["total"] = r.sizes.total - r.totals["total"]
        r.deltas["text"] = r.sizes.text - r.totals["text"]
        r.deltas["data"] = r.sizes.data - r.totals["data"]
        r.deltas["bss"] = r.sizes.bss - r.totals["bss"]
        return r
    create = staticmethod(create)

    def __init__(self, glob, title):
        self.glob = glob
        self.title = title
        self.sizes = Sizes(glob)
        self.parts = []
        self.totals = {"total":0, "text":0, "data":0, "bss":0}
        self.deltas = {"total":0, "text":0, "data":0, "bss":0}

    def show(self, indent=""):
        rule = str.ljust(indent, 80, '-')
        print("%-32s %10s | %10s %10s %10s" % \
              (indent+self.title, "total", "text", "data", "bss"))
        print(rule)
        self.sizes.show(indent)
        print(rule)
        for p in self.parts:
            if p.sizes.total > 0:
                p.sizes.show(indent)
        print(rule)
        print("%-32s %10d | %10d %10d %10d" % \
              (indent+"sum", self.totals["total"], self.totals["text"],
               self.totals["data"], self.totals["bss"]))
        print("%-32s %10d | %10d %10d %10d" % \
              (indent+"delta", self.deltas["total"], self.deltas["text"],
               self.deltas["data"], self.deltas["bss"]))
        print("\n")

    def __lt__(this, that):
        if that is None:
            return 1
        if not isinstance(that, Report):
            raise TypeError
        return this.sizes.total < that.sizes.total

    def __cmp__(this, that):
        if that is None:
            return 1
        if not isinstance(that, Report):
            raise TypeError
        if this.sizes.total < that.sizes.total:
            return -1
        if this.sizes.total > that.sizes.total:
            return 1
        return 0


def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "dh", ["help"])
    except getopt.GetoptError as err:
        print('%s' % str(err))
        usage()
        sys.exit(2)

    driver_detail = False
    for o, a in opts:
        if o == '-d':
            driver_detail = True
        elif o in ('-h', '--help'):
            usage()
            sys.exit(0)
        else:
            assert False, "unhandled option"

    glob = "arch/*/built-in.[o,a] */built-in.[o,a]"
    vmlinux = Report.create("vmlinux",  "Linux Kernel", glob)

    vmlinux.show()
    for b in vmlinux.parts:
        if b.totals["total"] > 0 and len(b.parts) > 1:
            b.show()
        if b.title == "drivers" and driver_detail:
            for d in b.parts:
                if d.totals["total"] > 0 and len(d.parts) > 1:
                    d.show("    ")


if __name__ == "__main__":
    main()
