| #!/usr/bin/env python3 |
| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Shows all objects sharing the same PartitionAlloc bucket.""" |
| |
| import argparse |
| import collections |
| import logging |
| import json |
| import os |
| import subprocess |
| import sys |
| |
| |
| def _BucketSizes(alignment: int) -> list[int]: |
| """Returns the bucket sizes for a given alignment.""" |
| # Adapted from partition_alloc_constants.h. |
| _ALIGNMENT = alignment |
| _MIN_BUCKETED_ORDER = 5 if _ALIGNMENT == 16 else 4 |
| _MAX_BUCKETETD_ORDER = 20 |
| _NUM_BUCKETED_ORDERS = (_MAX_BUCKETETD_ORDER - _MIN_BUCKETED_ORDER) + 1 |
| _NUM_BUCKETS_PER_ORDER_BITS = 2 |
| _NUM_BUCKETS_PER_ORDER = 1 << _NUM_BUCKETS_PER_ORDER_BITS |
| _SMALLEST_BUCKET = 1 << (_MIN_BUCKETED_ORDER - 1) |
| |
| sizes = [] |
| current_size = _SMALLEST_BUCKET |
| current_increment = _SMALLEST_BUCKET >> _NUM_BUCKETS_PER_ORDER_BITS |
| for i in range(_NUM_BUCKETED_ORDERS): |
| for j in range(_NUM_BUCKETS_PER_ORDER): |
| if current_size % _SMALLEST_BUCKET == 0: |
| sizes.append(current_size) |
| current_size += current_increment |
| current_increment = current_increment << 1 |
| return sizes |
| |
| |
| def _ParseExecutable(build_directory: str) -> dict: |
| """Parses chrome in |build_directory| and returns types grouped by size. |
| |
| Args: |
| build_directory: build directory, with chrome inside. |
| |
| Returns: |
| {size: int -> [str]} List of all objects grouped by size. |
| """ |
| try: |
| p = subprocess.Popen([ |
| 'pahole', '--show_private_classes', '-s', |
| os.path.join(build_directory, 'chrome') |
| ], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.DEVNULL) |
| except OSError as e: |
| logging.error('Cannot execute pahole, is it installed? %s', e) |
| sys.exit(1) |
| |
| logging.info('Parsing chrome') |
| result = collections.defaultdict(list) |
| count = 0 |
| for line in p.stdout: |
| fields = line.decode('utf-8').split('\t') |
| size = int(fields[1]) |
| name = fields[0] |
| result[size].append(name) |
| count += 1 |
| if count % 10000 == 0: |
| logging.info('Found %d types', count) |
| |
| logging.info('Done. Found %d types', count) |
| return result |
| |
| |
| def _MapToBucketSizes(objects_per_size: dict, alignment: int) -> dict: |
| """From a size -> [types] mapping, groups types by bucket size. |
| |
| |
| Args: |
| objects_per_size: As returned by _ParseExecutable() |
| alignment: 8 or 16, required alignment on the target platform. |
| |
| |
| Returns: |
| {slot_size -> [str]} |
| """ |
| sizes = _BucketSizes(alignment) |
| size_objects = list(objects_per_size.items()) |
| size_objects.sort() |
| result = collections.defaultdict(list) |
| next_bucket_index = 0 |
| for (size, objects) in size_objects: |
| while next_bucket_index < len(sizes) and size > sizes[next_bucket_index]: |
| next_bucket_index += 1 |
| if next_bucket_index >= len(sizes): |
| break |
| assert size <= sizes[next_bucket_index], size |
| result[sizes[next_bucket_index]] += objects |
| return result |
| |
| |
| _CACHED_RESULTS_FILENAME = 'cached.json' |
| |
| |
| def _LoadCachedResults(): |
| with open(_CACHED_RESULTS_FILENAME, 'r') as f: |
| parsed = json.load(f) |
| objects_per_size = {} |
| for key in parsed: |
| objects_per_size[int(key)] = parsed[key] |
| return objects_per_size |
| |
| |
| def _StoreCachedResults(data): |
| with open(_CACHED_RESULTS_FILENAME, 'w') as f: |
| json.dump(data, f) |
| |
| |
| def main(): |
| logging.basicConfig(level=logging.INFO) |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--build-directory', |
| type=str, |
| required=True, |
| help='Build directory') |
| parser.add_argument('--slot-size', type=int) |
| parser.add_argument('--type', type=str) |
| parser.add_argument('--store-cached-results', action='store_true') |
| parser.add_argument('--use-cached-results', action='store_true') |
| parser.add_argument('--alignment', type=int, default=16) |
| |
| args = parser.parse_args() |
| |
| objects_per_size = None |
| if args.use_cached_results: |
| objects_per_size = _LoadCachedResults() |
| else: |
| objects_per_size = _ParseExecutable(args.build_directory) |
| |
| objects_per_bucket = _MapToBucketSizes(objects_per_size, args.alignment) |
| if args.store_cached_results: |
| _StoreCachedResults(objects_per_size) |
| |
| assert args.slot_size or args.type, 'Must provide a slot size or object type' |
| |
| size = 0 |
| if args.slot_size: |
| size = args.slot_size |
| else: |
| for object_size in objects_per_size: |
| if args.type in objects_per_size[object_size]: |
| size = object_size |
| break |
| else: |
| assert 'Type %s not found', args.type |
| logging.info('Slot Size of %s = %d', args.type, size) |
| |
| print('Bucket sizes: %s' % |
| ' '.join([str(x) for x in _BucketSizes(args.alignment)])) |
| print('Objects in bucket %d' % size) |
| for name in objects_per_size[size]: |
| print('\t' + name) |
| |
| |
| if __name__ == '__main__': |
| main() |