OLD | NEW |
1 # Copyright 2017 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 """Classes that comprise the data model for binary size analysis. | 4 """Classes that comprise the data model for binary size analysis. |
5 | 5 |
6 The primary classes are Symbol, and SymbolGroup. | 6 The primary classes are Symbol, and SymbolGroup. |
7 | 7 |
8 Description of common properties: | 8 Description of common properties: |
9 * address: The start address of the symbol. | 9 * address: The start address of the symbol. |
10 May be 0 (e.g. for .bss or for SymbolGroups). | 10 May be 0 (e.g. for .bss or for SymbolGroups). |
11 * size: The number of bytes this symbol takes up, including padding that comes | 11 * size: The number of bytes this symbol takes up, including padding that comes |
12 before |address|. | 12 before |address|. |
| 13 * num_aliases: The number of symbols with the same address (including self). |
| 14 * pss: size / num_aliases. |
13 * padding: The number of bytes of padding before |address| due to this symbol. | 15 * padding: The number of bytes of padding before |address| due to this symbol. |
14 * name: Symbol names with parameter list removed. | 16 * name: Symbol names with parameter list removed. |
15 Never None, but will be '' for anonymous symbols. | 17 Never None, but will be '' for anonymous symbols. |
16 * full_name: Symbols names with parameter list left in. | 18 * full_name: Symbols names with parameter list left in. |
17 Never None, but will be '' for anonymous symbols, and for symbols that do | 19 Never None, but will be '' for anonymous symbols, and for symbols that do |
18 not contain a parameter list. | 20 not contain a parameter list. |
19 * is_anonymous: True when the symbol exists in an anonymous namespace (which | 21 * is_anonymous: True when the symbol exists in an anonymous namespace (which |
20 are removed from both full_name and name during normalization). | 22 are removed from both full_name and name during normalization). |
21 * section_name: E.g. ".text", ".rodata", ".data.rel.local" | 23 * section_name: E.g. ".text", ".rodata", ".data.rel.local" |
22 * section: The second character of |section_name|. E.g. "t", "r", "d". | 24 * section: The second character of |section_name|. E.g. "t", "r", "d". |
23 """ | 25 """ |
24 | 26 |
25 import collections | 27 import collections |
26 import copy | 28 import copy |
| 29 import logging |
27 import os | 30 import os |
28 import re | 31 import re |
29 | 32 |
30 import match_util | 33 import match_util |
31 | 34 |
32 | 35 |
33 METADATA_GIT_REVISION = 'git_revision' | 36 METADATA_GIT_REVISION = 'git_revision' |
34 METADATA_APK_FILENAME = 'apk_file_name' # Path relative to output_directory. | 37 METADATA_APK_FILENAME = 'apk_file_name' # Path relative to output_directory. |
35 METADATA_MAP_FILENAME = 'map_file_name' # Path relative to output_directory. | 38 METADATA_MAP_FILENAME = 'map_file_name' # Path relative to output_directory. |
36 METADATA_ELF_ARCHITECTURE = 'elf_arch' # "Machine" field from readelf -h | 39 METADATA_ELF_ARCHITECTURE = 'elf_arch' # "Machine" field from readelf -h |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
152 | 155 |
153 Refer to module docs for field descriptions. | 156 Refer to module docs for field descriptions. |
154 """ | 157 """ |
155 | 158 |
156 __slots__ = ( | 159 __slots__ = ( |
157 'address', | 160 'address', |
158 'full_name', | 161 'full_name', |
159 'is_anonymous', | 162 'is_anonymous', |
160 'object_path', | 163 'object_path', |
161 'name', | 164 'name', |
| 165 'num_aliases', |
162 'padding', | 166 'padding', |
163 'section_name', | 167 'section_name', |
164 'source_path', | 168 'source_path', |
165 'size', | 169 'size', |
166 ) | 170 ) |
167 | 171 |
168 def __init__(self, section_name, size_without_padding, address=None, | 172 def __init__(self, section_name, size_without_padding, address=None, |
169 name=None, source_path=None, object_path=None, | 173 name=None, source_path=None, object_path=None, |
170 full_name=None, is_anonymous=False): | 174 full_name=None, is_anonymous=False, num_aliases=1): |
171 self.section_name = section_name | 175 self.section_name = section_name |
172 self.address = address or 0 | 176 self.address = address or 0 |
173 self.name = name or '' | 177 self.name = name or '' |
174 self.full_name = full_name or '' | 178 self.full_name = full_name or '' |
175 self.source_path = source_path or '' | 179 self.source_path = source_path or '' |
176 self.object_path = object_path or '' | 180 self.object_path = object_path or '' |
177 self.size = size_without_padding | 181 self.size = size_without_padding |
178 # Change this to be a bitfield of flags if ever there is a need to add | |
179 # another similar thing. | |
180 self.is_anonymous = is_anonymous | 182 self.is_anonymous = is_anonymous |
| 183 self.num_aliases = num_aliases |
181 self.padding = 0 | 184 self.padding = 0 |
182 | 185 |
183 def __repr__(self): | 186 def __repr__(self): |
184 return ('%s@%x(size_without_padding=%d,padding=%d,name=%s,path=%s,anon=%d)' | 187 return ('%s@%x(size_without_padding=%d,padding=%d,name=%s,path=%s,anon=%d)' |
185 % (self.section_name, self.address, self.size_without_padding, | 188 % (self.section_name, self.address, self.size_without_padding, |
186 self.padding, self.name, self.source_path or self.object_path, | 189 self.padding, self.name, self.source_path or self.object_path, |
187 int(self.is_anonymous))) | 190 int(self.is_anonymous))) |
188 | 191 |
| 192 @property |
| 193 def pss(self): |
| 194 return float(self.size) / self.num_aliases |
| 195 |
| 196 @property |
| 197 def pss_without_padding(self): |
| 198 return float(self.size_without_padding) / self.num_aliases |
| 199 |
189 | 200 |
190 class SymbolGroup(BaseSymbol): | 201 class SymbolGroup(BaseSymbol): |
191 """Represents a group of symbols using the same interface as Symbol. | 202 """Represents a group of symbols using the same interface as Symbol. |
192 | 203 |
193 SymbolGroups are immutable. All filtering / sorting will return new | 204 SymbolGroups are immutable. All filtering / sorting will return new |
194 SymbolGroups objects. | 205 SymbolGroups objects. |
195 | 206 |
196 Overrides many __functions__. E.g. the following are all valid: | 207 Overrides many __functions__. E.g. the following are all valid: |
197 * len(group) | 208 * len(group) |
198 * iter(group) | 209 * iter(group) |
199 * group[0] | 210 * group[0] |
200 * group['0x1234'] # By symbol address | 211 * group['0x1234'] # By symbol address |
201 * without_group2 = group1 - group2 | 212 * without_group2 = group1 - group2 |
202 * unioned = group1 + group2 | 213 * unioned = group1 + group2 |
203 """ | 214 """ |
204 | 215 |
205 __slots__ = ( | 216 __slots__ = ( |
206 '_padding', | 217 '_padding', |
207 '_size', | 218 '_size', |
| 219 '_pss', |
208 '_symbols', | 220 '_symbols', |
209 '_filtered_symbols', | 221 '_filtered_symbols', |
210 'full_name', | 222 'full_name', |
211 'name', | 223 'name', |
212 'section_name', | 224 'section_name', |
213 'is_sorted', | 225 'is_sorted', |
214 ) | 226 ) |
215 | 227 |
216 def __init__(self, symbols, filtered_symbols=None, name=None, | 228 def __init__(self, symbols, filtered_symbols=None, name=None, |
217 full_name=None, section_name=None, is_sorted=False): | 229 full_name=None, section_name=None, is_sorted=False): |
218 self._padding = None | 230 self._padding = None |
219 self._size = None | 231 self._size = None |
| 232 self._pss = None |
220 self._symbols = symbols | 233 self._symbols = symbols |
221 self._filtered_symbols = filtered_symbols or [] | 234 self._filtered_symbols = filtered_symbols or [] |
222 self.name = name or '' | 235 self.name = name or '' |
223 self.full_name = full_name | 236 self.full_name = full_name |
224 self.section_name = section_name or '.*' | 237 self.section_name = section_name or '.*' |
225 self.is_sorted = is_sorted | 238 self.is_sorted = is_sorted |
226 | 239 |
227 def __repr__(self): | 240 def __repr__(self): |
228 return 'Group(name=%s,count=%d,size=%d)' % ( | 241 return 'Group(name=%s,count=%d,size=%d)' % ( |
229 self.name, len(self), self.size) | 242 self.name, len(self), self.size) |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
270 | 283 |
271 @property | 284 @property |
272 def is_anonymous(self): | 285 def is_anonymous(self): |
273 first = self._symbols[0].is_anonymous | 286 first = self._symbols[0].is_anonymous |
274 return first if all( | 287 return first if all( |
275 s.is_anonymous == first for s in self._symbols) else False | 288 s.is_anonymous == first for s in self._symbols) else False |
276 | 289 |
277 @property | 290 @property |
278 def object_path(self): | 291 def object_path(self): |
279 first = self._symbols[0].object_path | 292 first = self._symbols[0].object_path |
280 return first if all(s.object_path == first for s in self._symbols) else None | 293 return first if all(s.object_path == first for s in self._symbols) else '' |
281 | 294 |
282 @property | 295 @property |
283 def source_path(self): | 296 def source_path(self): |
284 first = self._symbols[0].source_path | 297 first = self._symbols[0].source_path |
285 return first if all(s.source_path == first for s in self._symbols) else None | 298 return first if all(s.source_path == first for s in self._symbols) else '' |
| 299 |
| 300 def IterUniqueSymbols(self): |
| 301 seen_addresses = set() |
| 302 for s in self: |
| 303 if s.num_aliases == 1: |
| 304 yield s |
| 305 elif s.address not in seen_addresses: |
| 306 seen_addresses.add(s.address) |
| 307 yield s |
286 | 308 |
287 @property | 309 @property |
288 def size(self): | 310 def size(self): |
289 if self._size is None: | 311 if self._size is None: |
290 if self.IsBss(): | 312 if self.IsBss(): |
291 self._size = sum(s.size for s in self) | 313 self._size = sum(s.size for s in self) |
292 self._size = sum(s.size for s in self if not s.IsBss()) | 314 else: |
| 315 self._size = sum(s.size for s in self.IterUniqueSymbols()) |
293 return self._size | 316 return self._size |
294 | 317 |
295 @property | 318 @property |
| 319 def pss(self): |
| 320 if self._pss is None: |
| 321 if self.IsBss(): |
| 322 self._pss = self.size |
| 323 else: |
| 324 self._pss = sum(s.pss for s in self) |
| 325 return self._pss |
| 326 |
| 327 @property |
296 def padding(self): | 328 def padding(self): |
297 if self._padding is None: | 329 if self._padding is None: |
298 self._padding = sum(s.padding for s in self) | 330 self._padding = sum(s.padding for s in self.IterUniqueSymbols()) |
299 return self._padding | 331 return self._padding |
300 | 332 |
| 333 @property |
| 334 def num_aliases(self): |
| 335 return 1 |
| 336 |
301 def IsGroup(self): | 337 def IsGroup(self): |
302 return True | 338 return True |
303 | 339 |
304 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, | 340 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, |
305 section_name=None, is_sorted=None): | 341 section_name=None, is_sorted=None): |
306 if is_sorted is None: | 342 if is_sorted is None: |
307 is_sorted = self.is_sorted | 343 is_sorted = self.is_sorted |
308 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, | 344 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, |
309 section_name=section_name, is_sorted=is_sorted) | 345 section_name=section_name, is_sorted=is_sorted) |
310 | 346 |
(...skipping 13 matching lines...) Expand all Loading... |
324 | 360 |
325 def SortedByAddress(self, reverse=False): | 361 def SortedByAddress(self, reverse=False): |
326 return self.Sorted(key=(lambda s:s.address), reverse=reverse) | 362 return self.Sorted(key=(lambda s:s.address), reverse=reverse) |
327 | 363 |
328 def SortedByCount(self, reverse=False): | 364 def SortedByCount(self, reverse=False): |
329 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), | 365 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), |
330 reverse=not reverse) | 366 reverse=not reverse) |
331 | 367 |
332 def Filter(self, func): | 368 def Filter(self, func): |
333 filtered_and_kept = ([], []) | 369 filtered_and_kept = ([], []) |
334 for symbol in self: | 370 symbol = None |
335 filtered_and_kept[int(bool(func(symbol)))].append(symbol) | 371 try: |
| 372 for symbol in self: |
| 373 filtered_and_kept[int(bool(func(symbol)))].append(symbol) |
| 374 except: |
| 375 logging.warning('Filter failed on symbol %r', symbol) |
| 376 raise |
| 377 |
336 return self._CreateTransformed(filtered_and_kept[1], | 378 return self._CreateTransformed(filtered_and_kept[1], |
337 filtered_symbols=filtered_and_kept[0], | 379 filtered_symbols=filtered_and_kept[0], |
338 section_name=self.section_name) | 380 section_name=self.section_name) |
339 | 381 |
340 def WhereBiggerThan(self, min_size): | 382 def WhereBiggerThan(self, min_size): |
341 return self.Filter(lambda s: s.size >= min_size) | 383 return self.Filter(lambda s: s.size >= min_size) |
342 | 384 |
343 def WhereInSection(self, section): | 385 def WhereInSection(self, section): |
344 if len(section) == 1: | 386 if len(section) == 1: |
345 ret = self.Filter(lambda s: s.section == section) | 387 ret = self.Filter(lambda s: s.section == section) |
(...skipping 335 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
681 | 723 |
682 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 724 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
683 idx = -len(separator) | 725 idx = -len(separator) |
684 prev_idx = None | 726 prev_idx = None |
685 for _ in xrange(count): | 727 for _ in xrange(count): |
686 idx = string.find(separator, idx + len(separator)) | 728 idx = string.find(separator, idx + len(separator)) |
687 if idx < 0: | 729 if idx < 0: |
688 break | 730 break |
689 prev_idx = idx | 731 prev_idx = idx |
690 return string[:prev_idx] | 732 return string[:prev_idx] |
OLD | NEW |