xref: /aosp_15_r20/build/bazel/scripts/difftool/diffs/bloaty.py (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*7594170eSAndroid Build Coastguard Worker#
3*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2022 The Android Open Source Project
4*7594170eSAndroid Build Coastguard Worker#
5*7594170eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*7594170eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*7594170eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*7594170eSAndroid Build Coastguard Worker#
9*7594170eSAndroid Build Coastguard Worker#   http://www.apache.org/licenses/LICENSE-2.0
10*7594170eSAndroid Build Coastguard Worker#
11*7594170eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*7594170eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*7594170eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*7594170eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*7594170eSAndroid Build Coastguard Worker# limitations under the License."""
16*7594170eSAndroid Build Coastguard Worker
17*7594170eSAndroid Build Coastguard Workerimport csv
18*7594170eSAndroid Build Coastguard Workerimport pathlib
19*7594170eSAndroid Build Coastguard Workerimport subprocess
20*7594170eSAndroid Build Coastguard Workerfrom diffs.diff import Diff, ExtractInfo
21*7594170eSAndroid Build Coastguard Worker
22*7594170eSAndroid Build Coastguard Worker
23*7594170eSAndroid Build Coastguard Workerclass BloatyDiff(Diff):
24*7594170eSAndroid Build Coastguard Worker  """BloatyDiff compares the sizes of symbols present in cc objects
25*7594170eSAndroid Build Coastguard Worker
26*7594170eSAndroid Build Coastguard Worker  Bloaty McBloatface (bloaty) is used to discover size differences in object
27*7594170eSAndroid Build Coastguard Worker  files or cc binaries. This diff returns a list of symbols which are new or
28*7594170eSAndroid Build Coastguard Worker  larger in one file than the other.
29*7594170eSAndroid Build Coastguard Worker
30*7594170eSAndroid Build Coastguard Worker  The output does not distinguish between new symbols and ones that are simply
31*7594170eSAndroid Build Coastguard Worker  larger, so this output is best combined with the NmSymbolDiff to see which
32*7594170eSAndroid Build Coastguard Worker  symbols are new.
33*7594170eSAndroid Build Coastguard Worker
34*7594170eSAndroid Build Coastguard Worker  Example bloaty output (note: compileunits may not always be available):
35*7594170eSAndroid Build Coastguard Worker  $ bloaty --csv -d compileunits,symbols $BAZEL_OBJ -- $LEGACY_OBJ
36*7594170eSAndroid Build Coastguard Worker  compileunits,symbols,vmsize,filesize
37*7594170eSAndroid Build Coastguard Worker  external/zstd/lib/compress/zstd_fast.c,ZSTD_compressBlock_doubleFast_extDict_generic,6240,6344
38*7594170eSAndroid Build Coastguard Worker  external/zstd/lib/compress/zstd_fast.c,ZSTD_compressBlock_lazy_dictMatchState,-3428,-3551
39*7594170eSAndroid Build Coastguard Worker
40*7594170eSAndroid Build Coastguard Worker  The first entry is a symbol that is larger in the Bazel version of the binary,
41*7594170eSAndroid Build Coastguard Worker  and the second entry is a symbol that is larger in the Soong version of the
42*7594170eSAndroid Build Coastguard Worker  binary.
43*7594170eSAndroid Build Coastguard Worker  """
44*7594170eSAndroid Build Coastguard Worker  def __init__(self, tool_name, data_source, has_debug_symbols=False):
45*7594170eSAndroid Build Coastguard Worker    self.tool_name = tool_name
46*7594170eSAndroid Build Coastguard Worker    self.data_source = data_source
47*7594170eSAndroid Build Coastguard Worker    self.has_debug_symbols = has_debug_symbols
48*7594170eSAndroid Build Coastguard Worker
49*7594170eSAndroid Build Coastguard Worker  def _print_diff_row(self, row, ignore_keys):
50*7594170eSAndroid Build Coastguard Worker    attrs = sorted({
51*7594170eSAndroid Build Coastguard Worker      k: v
52*7594170eSAndroid Build Coastguard Worker      for k, v in row.items()
53*7594170eSAndroid Build Coastguard Worker      if k not in ignore_keys
54*7594170eSAndroid Build Coastguard Worker    }.items())
55*7594170eSAndroid Build Coastguard Worker    return row[self.data_source] + ": { " + ", ".join(f"{a[0]}: {a[1]}" for a in attrs) + " }"
56*7594170eSAndroid Build Coastguard Worker
57*7594170eSAndroid Build Coastguard Worker  def _collect_diff_compileunits(self, diffreader: csv.DictReader):
58*7594170eSAndroid Build Coastguard Worker    # maps from compileunit to list of diff rows
59*7594170eSAndroid Build Coastguard Worker    left_bigger = collections.defaultdict(list)
60*7594170eSAndroid Build Coastguard Worker    right_bigger = collections.defaultdict(list)
61*7594170eSAndroid Build Coastguard Worker
62*7594170eSAndroid Build Coastguard Worker    for row in diffreader:
63*7594170eSAndroid Build Coastguard Worker      compileunit = row["compileunits"]
64*7594170eSAndroid Build Coastguard Worker      if len(compileunit) > 0 and compileunit[0] == "[":
65*7594170eSAndroid Build Coastguard Worker        continue
66*7594170eSAndroid Build Coastguard Worker      filesize = row["filesize"]
67*7594170eSAndroid Build Coastguard Worker      if int(filesize) < 0:
68*7594170eSAndroid Build Coastguard Worker        left_bigger[compileunit].append(row)
69*7594170eSAndroid Build Coastguard Worker      elif int(filesize) > 0:
70*7594170eSAndroid Build Coastguard Worker        right_bigger[compileunit].append(row)
71*7594170eSAndroid Build Coastguard Worker
72*7594170eSAndroid Build Coastguard Worker    def print_diff_dict(dict):
73*7594170eSAndroid Build Coastguard Worker      lines = []
74*7594170eSAndroid Build Coastguard Worker      for compileunit, data in sorted(dict.items()):
75*7594170eSAndroid Build Coastguard Worker        lines.append("\t" + compileunit + ":")
76*7594170eSAndroid Build Coastguard Worker        rows = []
77*7594170eSAndroid Build Coastguard Worker        for row in data:
78*7594170eSAndroid Build Coastguard Worker          if row[self.data_source] and row[self.data_source][0] == "[":
79*7594170eSAndroid Build Coastguard Worker            continue
80*7594170eSAndroid Build Coastguard Worker          rows.append("\t\t" + self.print_diff_row(row, ignore_keys=[self.data_source, "compileunits"]))
81*7594170eSAndroid Build Coastguard Worker        lines.extend(sorted(rows))
82*7594170eSAndroid Build Coastguard Worker      return "\n".join(lines)
83*7594170eSAndroid Build Coastguard Worker
84*7594170eSAndroid Build Coastguard Worker    return print_diff_dict(left_bigger), print_diff_dict(right_bigger)
85*7594170eSAndroid Build Coastguard Worker
86*7594170eSAndroid Build Coastguard Worker  def _collect_diff(self, diffreader):
87*7594170eSAndroid Build Coastguard Worker    left_bigger = []
88*7594170eSAndroid Build Coastguard Worker    right_bigger = []
89*7594170eSAndroid Build Coastguard Worker
90*7594170eSAndroid Build Coastguard Worker    for row in diffreader:
91*7594170eSAndroid Build Coastguard Worker      filesize = row["filesize"]
92*7594170eSAndroid Build Coastguard Worker      if int(filesize) > 0:
93*7594170eSAndroid Build Coastguard Worker        left_bigger.append(row)
94*7594170eSAndroid Build Coastguard Worker      elif int(filesize) < 0:
95*7594170eSAndroid Build Coastguard Worker        right_bigger.append(row)
96*7594170eSAndroid Build Coastguard Worker
97*7594170eSAndroid Build Coastguard Worker    left_errors = "\n".join(["\t" + self._print_diff_row(row, ignore_keys=[self.data_source]) for row in left_bigger])
98*7594170eSAndroid Build Coastguard Worker    right_errors = "\n".join(["\t" + self._print_diff_row(row, ignore_keys=[self.data_source]) for row in right_bigger])
99*7594170eSAndroid Build Coastguard Worker    return left_errors, right_errors
100*7594170eSAndroid Build Coastguard Worker
101*7594170eSAndroid Build Coastguard Worker  def diff(self, left_path: pathlib.Path, right_path: pathlib.Path) -> list[str]:
102*7594170eSAndroid Build Coastguard Worker    try:
103*7594170eSAndroid Build Coastguard Worker      diff_csv = subprocess.run(["bloaty",
104*7594170eSAndroid Build Coastguard Worker                                  "--csv",
105*7594170eSAndroid Build Coastguard Worker                                  "-n", "0",
106*7594170eSAndroid Build Coastguard Worker                                  "-w",
107*7594170eSAndroid Build Coastguard Worker                                  "-d",
108*7594170eSAndroid Build Coastguard Worker                                  self.data_source + (",compileunits" if self.has_debug_symbols else ""),
109*7594170eSAndroid Build Coastguard Worker                                  str(left_path),
110*7594170eSAndroid Build Coastguard Worker                                  "--",
111*7594170eSAndroid Build Coastguard Worker                                  str(right_path)],
112*7594170eSAndroid Build Coastguard Worker                                 check=True, capture_output=True,
113*7594170eSAndroid Build Coastguard Worker                                 encoding="utf-8").stdout.splitlines()
114*7594170eSAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
115*7594170eSAndroid Build Coastguard Worker      print("ERROR: bloaty tool returned non-zero exit status")
116*7594170eSAndroid Build Coastguard Worker      if self.has_debug_symbols:
117*7594170eSAndroid Build Coastguard Worker        print("ERROR: do objects contain debug symbols?")
118*7594170eSAndroid Build Coastguard Worker      raise e
119*7594170eSAndroid Build Coastguard Worker
120*7594170eSAndroid Build Coastguard Worker    diffreader = csv.DictReader(diff_csv)
121*7594170eSAndroid Build Coastguard Worker
122*7594170eSAndroid Build Coastguard Worker    if self.has_debug_symbols:
123*7594170eSAndroid Build Coastguard Worker      left_bigger, right_bigger = self._collect_diff_compileunits(diffreader)
124*7594170eSAndroid Build Coastguard Worker    else:
125*7594170eSAndroid Build Coastguard Worker      left_bigger, right_bigger = self._collect_diff(diffreader)
126*7594170eSAndroid Build Coastguard Worker
127*7594170eSAndroid Build Coastguard Worker    errors = []
128*7594170eSAndroid Build Coastguard Worker    if left_bigger:
129*7594170eSAndroid Build Coastguard Worker      errors.append(f"the following {self.data_source} are either unique or larger in\n{left_path}\n than those in\n{right_path}:\n{left_bigger}")
130*7594170eSAndroid Build Coastguard Worker    if right_bigger:
131*7594170eSAndroid Build Coastguard Worker      errors.append(f"the following {self.data_source} are either unique or larger in\n{right_path}\n than those in\n{left_path}:\n{right_bigger}")
132*7594170eSAndroid Build Coastguard Worker
133*7594170eSAndroid Build Coastguard Worker    return errors
134