size-stats-compare 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. #!/usr/bin/env python
  2. # Copyright (C) 2016 Thomas De Schampheleire <thomas.de.schampheleire@gmail.com>
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. # General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. # TODO (improvements)
  17. # - support K,M,G size suffixes for threshold
  18. # - output CSV file in addition to stdout reporting
  19. import csv
  20. import argparse
  21. import sys
  22. def read_file_size_csv(inputf, detail=None):
  23. """Extract package or file sizes from CSV file into size dictionary"""
  24. sizes = {}
  25. reader = csv.reader(inputf)
  26. header = next(reader)
  27. if header[0] != 'File name' or header[1] != 'Package name' or \
  28. header[2] != 'File size' or header[3] != 'Package size':
  29. print(("Input file %s does not contain the expected header. Are you "
  30. "sure this file corresponds to the file-size-stats.csv "
  31. "file created by 'make graph-size'?") % inputf.name)
  32. sys.exit(1)
  33. for row in reader:
  34. if detail:
  35. sizes[row[0]] = int(row[2])
  36. else:
  37. sizes[row[1]] = int(row[3])
  38. return sizes
  39. def compare_sizes(old, new):
  40. """Return delta/added/removed dictionaries based on two input size
  41. dictionaries"""
  42. delta = {}
  43. oldkeys = set(old.keys())
  44. newkeys = set(new.keys())
  45. # packages/files in both
  46. for entry in newkeys.intersection(oldkeys):
  47. delta[entry] = ('', new[entry] - old[entry])
  48. # packages/files only in new
  49. for entry in newkeys.difference(oldkeys):
  50. delta[entry] = ('added', new[entry])
  51. # packages/files only in old
  52. for entry in oldkeys.difference(newkeys):
  53. delta[entry] = ('removed', -old[entry])
  54. return delta
  55. def print_results(result, threshold):
  56. """Print the given result dictionary sorted by size, ignoring any entries
  57. below or equal to threshold"""
  58. from six import iteritems
  59. list_result = list(iteritems(result))
  60. # result is a dictionary: name -> (flag, size difference)
  61. # list_result is a list of tuples: (name, (flag, size difference))
  62. for entry in sorted(list_result, key=lambda entry: entry[1][1]):
  63. if threshold is not None and abs(entry[1][1]) <= threshold:
  64. continue
  65. print('%12s %7s %s' % (entry[1][1], entry[1][0], entry[0]))
  66. # main #########################################################################
  67. description = """
  68. Compare rootfs size between Buildroot compilations, for example after changing
  69. configuration options or after switching to another Buildroot release.
  70. This script compares the file-size-stats.csv file generated by 'make graph-size'
  71. with the corresponding file from another Buildroot compilation.
  72. The size differences can be reported per package or per file.
  73. Size differences smaller or equal than a given threshold can be ignored.
  74. """
  75. parser = argparse.ArgumentParser(description=description,
  76. formatter_class=argparse.RawDescriptionHelpFormatter)
  77. parser.add_argument('-d', '--detail', action='store_true',
  78. help='''report differences for individual files rather than
  79. packages''')
  80. parser.add_argument('-t', '--threshold', type=int,
  81. help='''ignore size differences smaller or equal than this
  82. value (bytes)''')
  83. parser.add_argument('old_file_size_csv', type=argparse.FileType('r'),
  84. metavar='old-file-size-stats.csv',
  85. help="""old CSV file with file and package size statistics,
  86. generated by 'make graph-size'""")
  87. parser.add_argument('new_file_size_csv', type=argparse.FileType('r'),
  88. metavar='new-file-size-stats.csv',
  89. help='new CSV file with file and package size statistics')
  90. args = parser.parse_args()
  91. if args.detail:
  92. keyword = 'file'
  93. else:
  94. keyword = 'package'
  95. old_sizes = read_file_size_csv(args.old_file_size_csv, args.detail)
  96. new_sizes = read_file_size_csv(args.new_file_size_csv, args.detail)
  97. delta = compare_sizes(old_sizes, new_sizes)
  98. print('Size difference per %s (bytes), threshold = %s' % (keyword, args.threshold))
  99. print(80*'-')
  100. print_results(delta, args.threshold)
  101. print(80*'-')
  102. print_results({'TOTAL': ('', sum(new_sizes.values()) - sum(old_sizes.values()))},
  103. threshold=None)