Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(510)

Side by Side Diff: tools/binary_size/models.py

Issue 2778963003: Revert of V2 of //tools/binary_size rewrite (diffs). (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/binary_size/mapfileparser.py ('k') | tools/binary_size/query.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright 2017 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4 """Classes that comprise the data model for binary size analysis."""
5
6 import collections
7 import copy
8 import re
9
10
11 SECTION_TO_SECTION_NAME = {
12 'b': '.bss',
13 'd': '.data',
14 'r': '.rodata',
15 't': '.text',
16 }
17
18
19 class SizeInfo(object):
20 """Represents all size information for a single binary.
21
22 Fields:
23 section_sizes: A dict of section_name -> size.
24 symbols: A SymbolGroup (or SymbolDiff) with all symbols in it.
25 """
26 __slots__ = (
27 'symbols',
28 'section_sizes',
29 )
30
31 """Root size information."""
32 def __init__(self, symbols, section_sizes):
33 self.symbols = symbols
34 self.section_sizes = section_sizes # E.g. {'.text': 0}
35
36
37 class BaseSymbol(object):
38 """Base class for Symbol and SymbolGroup."""
39 __slots__ = ()
40
41 @property
42 def section(self):
43 """Returns the one-letter section.
44
45 E.g. If section_name == '.rodata', then section == 'r'.
46 """
47 return self.section_name[1]
48
49 @property
50 def size_without_padding(self):
51 return self.size - self.padding
52
53 @property
54 def end_address(self):
55 return self.address + self.size_without_padding
56
57 def IsBss(self):
58 return self.section_name == '.bss'
59
60 def IsGroup(self):
61 return False
62
63 def IsGenerated(self):
64 # TODO(agrieve): Also match generated functions such as:
65 # startup._GLOBAL__sub_I_page_allocator.cc
66 return self.name.endswith(']') and not self.name.endswith('[]')
67
68 def _Key(self):
69 """Returns a tuple that can be used to see if two Symbol are the same.
70
71 Keys are not guaranteed to be unique within a SymbolGroup. For example, it
72 is common to have multiple "** merge strings" symbols, which will have a
73 common key."""
74 return (self.section_name, self.function_signature or self.name)
75
76
77 class Symbol(BaseSymbol):
78 """Represents a single symbol within a binary."""
79
80 __slots__ = (
81 'section_name',
82 'address',
83 'size',
84 'padding',
85 'name',
86 'function_signature',
87 'path',
88 )
89
90 def __init__(self, section_name, size_without_padding, address=None,
91 name=None, path=None, function_signature=None):
92 self.section_name = section_name
93 self.address = address or 0
94 self.name = name or ''
95 self.function_signature = function_signature or ''
96 self.path = path or ''
97 self.size = size_without_padding
98 self.padding = 0
99
100 def __repr__(self):
101 return '%s@%x(size=%d,padding=%d,name=%s,path=%s)' % (
102 self.section_name, self.address, self.size_without_padding,
103 self.padding, self.name, self.path)
104
105
106 class SymbolGroup(BaseSymbol):
107 """Represents a group of symbols using the same interface as Symbol.
108
109 SymbolGroups are immutable. All filtering / sorting will return new
110 SymbolGroups objects.
111 """
112
113 __slots__ = (
114 'symbols',
115 'filtered_symbols',
116 'name',
117 'section_name',
118 )
119
120 def __init__(self, symbols, filtered_symbols=None, name=None,
121 section_name=None):
122 self.symbols = symbols
123 self.filtered_symbols = filtered_symbols or []
124 self.name = name or ''
125 self.section_name = section_name or '.*'
126
127 def __repr__(self):
128 return 'Group(name=%s,count=%d,size=%d)' % (
129 self.name, len(self), self.size)
130
131 def __iter__(self):
132 return iter(self.symbols)
133
134 def __len__(self):
135 return len(self.symbols)
136
137 def __getitem__(self, index):
138 return self.symbols[index]
139
140 def __sub__(self, other):
141 other_ids = set(id(s) for s in other)
142 new_symbols = [s for s in self if id(s) not in other_ids]
143 return self._CreateTransformed(new_symbols, section_name=self.section_name)
144
145 def __add__(self, other):
146 self_ids = set(id(s) for s in self)
147 new_symbols = self.symbols + [s for s in other if id(s) not in self_ids]
148 return self._CreateTransformed(new_symbols, section_name=self.section_name)
149
150 @property
151 def address(self):
152 return 0
153
154 @property
155 def function_signature(self):
156 return None
157
158 @property
159 def path(self):
160 return None
161
162 @property
163 def size(self):
164 if self.IsBss():
165 return sum(s.size for s in self)
166 return sum(s.size for s in self if not s.IsBss())
167
168 @property
169 def padding(self):
170 return sum(s.padding for s in self)
171
172 def IsGroup(self):
173 return True
174
175 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None,
176 section_name=None):
177 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name,
178 section_name=section_name)
179
180 def Sorted(self, cmp_func=None, key=None, reverse=False):
181 # Default to sorting by abs(size) then name.
182 if cmp_func is None and key is None:
183 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.size), a.name),
184 (b.IsBss(), abs(a.size), b.name))
185
186 new_symbols = sorted(self.symbols, cmp_func, key, reverse)
187 return self._CreateTransformed(new_symbols,
188 filtered_symbols=self.filtered_symbols,
189 section_name=self.section_name)
190
191 def Filter(self, func):
192 filtered_and_kept = ([], [])
193 for symbol in self:
194 filtered_and_kept[int(bool(func(symbol)))].append(symbol)
195 return self._CreateTransformed(filtered_and_kept[1],
196 filtered_symbols=filtered_and_kept[0],
197 section_name=self.section_name)
198
199 def WhereBiggerThan(self, min_size):
200 return self.Filter(lambda s: s.size >= min_size)
201
202 def WhereInSection(self, section):
203 if len(section) == 1:
204 ret = self.Filter(lambda s: s.section == section)
205 ret.section_name = SECTION_TO_SECTION_NAME[section]
206 else:
207 ret = self.Filter(lambda s: s.section_name == section)
208 ret.section_name = section
209 return ret
210
211 def WhereIsGenerated(self):
212 return self.Filter(lambda s: s.IsGenerated())
213
214 def WhereNameMatches(self, pattern):
215 regex = re.compile(pattern)
216 return self.Filter(lambda s: regex.search(s.name))
217
218 def WherePathMatches(self, pattern):
219 regex = re.compile(pattern)
220 return self.Filter(lambda s: s.path and regex.search(s.path))
221
222 def WhereAddressInRange(self, start, end):
223 return self.Filter(lambda s: s.address >= start and s.address <= end)
224
225 def WhereHasAnyAttribution(self):
226 return self.Filter(lambda s: s.name or s.path)
227
228 def Inverted(self):
229 return self._CreateTransformed(self.filtered_symbols,
230 filtered_symbols=self.symbols)
231
232 def GroupBy(self, func):
233 new_syms = []
234 filtered_symbols = []
235 symbols_by_token = collections.defaultdict(list)
236 for symbol in self:
237 token = func(symbol)
238 if not token:
239 filtered_symbols.append(symbol)
240 continue
241 symbols_by_token[token].append(symbol)
242 for token, symbols in symbols_by_token.iteritems():
243 new_syms.append(self._CreateTransformed(symbols, name=token,
244 section_name=self.section_name))
245 return self._CreateTransformed(new_syms, filtered_symbols=filtered_symbols,
246 section_name=self.section_name)
247
248 def GroupByNamespace(self, depth=1):
249 def extract_namespace(symbol):
250 # Does not distinguish between classes and namespaces.
251 idx = -2
252 for _ in xrange(depth):
253 idx = symbol.name.find('::', idx + 2)
254 if idx != -1:
255 ret = symbol.name[:idx]
256 if '<' not in ret:
257 return ret
258 return '{global}'
259 return self.GroupBy(extract_namespace)
260
261 def GroupByPath(self, depth=1):
262 def extract_path(symbol):
263 idx = -1
264 for _ in xrange(depth):
265 idx = symbol.path.find('/', idx + 1)
266 if idx != -1:
267 return symbol.path[:idx]
268 return '{path unknown}'
269 return self.GroupBy(extract_path)
270
271
272 class SymbolDiff(SymbolGroup):
273 """A SymbolGroup subclass representing a diff of two other SymbolGroups.
274
275 All Symbols contained within have a |size| which is actually the size delta.
276 Additionally, metadata is kept about which symbols were added / removed /
277 changed.
278 """
279 __slots__ = (
280 '_added_ids',
281 '_removed_ids',
282 )
283
284 def __init__(self, added, removed, similar):
285 self._added_ids = set(id(s) for s in added)
286 self._removed_ids = set(id(s) for s in removed)
287 symbols = []
288 symbols.extend(added)
289 symbols.extend(removed)
290 symbols.extend(similar)
291 super(SymbolDiff, self).__init__(symbols)
292
293 def __repr__(self):
294 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % (
295 'SymbolGroup', self.added_count, self.removed_count, self.changed_count,
296 self.unchanged_count, self.size)
297
298 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None,
299 section_name=None):
300 ret = SymbolDiff.__new__(SymbolDiff)
301 # Printing sorts, so fast-path the same symbols case.
302 if len(symbols) == len(self.symbols):
303 ret._added_ids = self._added_ids
304 ret._removed_ids = self._removed_ids
305 else:
306 ret._added_ids = set(id(s) for s in symbols if self.IsAdded(s))
307 ret._removed_ids = set(id(s) for s in symbols if self.IsRemoved(s))
308 super(SymbolDiff, ret).__init__(symbols, filtered_symbols=filtered_symbols,
309 name=name, section_name=section_name)
310
311 return ret
312
313 @property
314 def added_count(self):
315 return len(self._added_ids)
316
317 @property
318 def removed_count(self):
319 return len(self._removed_ids)
320
321 @property
322 def changed_count(self):
323 not_changed = self.unchanged_count + self.added_count + self.removed_count
324 return len(self) - not_changed
325
326 @property
327 def unchanged_count(self):
328 return sum(1 for s in self if self.IsSimilar(s) and s.size == 0)
329
330 def IsAdded(self, sym):
331 return id(sym) in self._added_ids
332
333 def IsSimilar(self, sym):
334 key = id(sym)
335 return key not in self._added_ids and key not in self._removed_ids
336
337 def IsRemoved(self, sym):
338 return id(sym) in self._removed_ids
339
340 def WhereNotUnchanged(self):
341 return self.Filter(lambda s: not self.IsSimilar(s) or s.size)
342
343
344 def Diff(new, old):
345 """Diffs two SizeInfo or SymbolGroup objects.
346
347 When diffing SizeInfos, ret.section_sizes are the result of |new| - |old|, and
348 ret.symbols will be a SymbolDiff.
349
350 When diffing SymbolGroups, a SymbolDiff is returned.
351
352 Returns:
353 Returns a SizeInfo when args are of type SizeInfo.
354 Returns a SymbolDiff when args are of type SymbolGroup.
355 """
356 if isinstance(new, SizeInfo):
357 assert isinstance(old, SizeInfo)
358 section_sizes = {
359 k:new.section_sizes[k] - v for k, v in old.section_sizes.iteritems()}
360 symbol_diff = Diff(new.symbols, old.symbols)
361 return SizeInfo(symbol_diff, section_sizes)
362
363 assert isinstance(new, SymbolGroup) and isinstance(old, SymbolGroup)
364 symbols_by_key = collections.defaultdict(list)
365 for s in old:
366 symbols_by_key[s._Key()].append(s)
367
368 added = []
369 removed = []
370 similar = []
371 # For similar symbols, padding is zeroed out. In order to not lose the
372 # information entirely, store it in aggregate.
373 padding_by_section_name = collections.defaultdict(int)
374 for new_sym in new:
375 matching_syms = symbols_by_key.get(new_sym._Key())
376 if matching_syms:
377 old_sym = matching_syms.pop(0)
378 # More stable/useful to compare size without padding.
379 size_diff = (new_sym.size_without_padding -
380 old_sym.size_without_padding)
381 merged_sym = Symbol(old_sym.section_name, size_diff,
382 address=old_sym.address, name=old_sym.name,
383 path=old_sym.path,
384 function_signature=old_sym.function_signature)
385 similar.append(merged_sym)
386 padding_by_section_name[new_sym.section_name] += (
387 new_sym.padding - old_sym.padding)
388 else:
389 added.append(new_sym)
390
391 for remaining_syms in symbols_by_key.itervalues():
392 for old_sym in remaining_syms:
393 duped = copy.copy(old_sym)
394 duped.size = -duped.size
395 duped.padding = -duped.padding
396 removed.append(duped)
397
398 for section_name, padding in padding_by_section_name.iteritems():
399 similar.append(Symbol(section_name, padding,
400 '** aggregate padding of delta symbols'))
401 return SymbolDiff(added, removed, similar)
OLDNEW
« no previous file with comments | « tools/binary_size/mapfileparser.py ('k') | tools/binary_size/query.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698