| #!/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. |
| """Parses allocation profiles from a trace and computes the external |
| fragmentation from PartitionAlloc |
| |
| We compute this as the difference between the memory allocated and the total |
| amount of memory used by the allocator. (For example, a bucket may have empty |
| slot spans, in which case PartitionAlloc is using more memory it has |
| allocated.) |
| |
| The output of this script is two sets of graphs. The first shows external |
| fragmentation (as a percentage) for each bucket. The second shows the actual |
| amount of memory wasted due to external fragmentation for each bucket. |
| |
| See: trace_utils.py for details on collecting a trace. |
| """ |
| |
| import argparse |
| import logging |
| import os |
| import re |
| |
| from matplotlib import pylab as plt |
| import numpy as np |
| |
| import trace_utils |
| |
| |
| def _SummarizeStatsForAllocators(result_for_pid: dict, allocators: dict): |
| """We compute the wasted memory here by taking the difference of |
| 'allocated_objects_size' and 'size'. See |
| MemoryDumpPartitionStatsDumper::PartitionDumpTotals in chrome for details on |
| where these are collected. |
| """ |
| size_counts = [] |
| # We are only interested in the main malloc partition, reported in |
| # |ReportPartitionAllocStats|. |
| pattern = re.compile("malloc/partitions/allocator/buckets/bucket_\d+") |
| for entry in allocators: |
| # |entry| can represent a subset of the allocations for a given allocator. |
| if (not pattern.match(entry)): |
| continue |
| attrs = allocators[entry]['attrs'] |
| |
| allocated_objects_size = trace_utils.GetAllocatorAttr( |
| attrs, 'allocated_objects_size') |
| slot_size = trace_utils.GetAllocatorAttr(attrs, 'slot_size') |
| try: |
| # 'size' is only available in the dump if 'allocated_objects_size' is |
| # non-zero. |
| size = trace_utils.GetAllocatorAttr(attrs, 'size') |
| fragmentation = 1 - allocated_objects_size / size |
| except KeyError: |
| assert allocated_objects_size == 0 |
| size = 0 |
| fragmentation = 0 |
| assert allocated_objects_size <= size |
| |
| size_counts.append( |
| (slot_size, 100 * fragmentation, size - allocated_objects_size)) |
| size_counts.sort() |
| result_for_pid['data'] = np.array(size_counts, |
| dtype=[('size', np.int), |
| ('fragmentation', np.int), |
| ('unused', np.int)]) |
| |
| |
| def _PlotProcess(all_data: dict, pid: int, output_prefix: str): |
| """Represents the allocation size distribution. |
| |
| Args: |
| all_data: As returned by _ParseTrace(). |
| pid: PID to plot the data for. |
| output_prefix: Prefix of the output file. |
| """ |
| data = all_data[pid] |
| logging.info('Plotting data for PID %d' % pid) |
| |
| fragmentation_title = ('External Fragmentation (%%) vs Size - %s - %s' % |
| (data['name'], data['labels'])) |
| fragmentation_output = ('%s_%s_fragmentation.png' % (output_prefix, pid)) |
| trace_utils.PlotProcessFragmentation(fragmentation_title, data, |
| fragmentation_output) |
| |
| unused_title = ('External Unused Memory vs Size - %s - %s' % |
| (data['name'], data['labels'])) |
| unused_output = ('%s_%d_unused.png' % (output_prefix, pid)) |
| trace_utils.PlotProcessWaste(unused_title, data, unused_output) |
| |
| |
| def _CreateArgumentParser(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--trace', |
| type=str, |
| required=True, |
| help='Path to a trace.json[.gz] with memory-infra enabled.') |
| parser.add_argument('--output-dir', |
| type=str, |
| required=True, |
| help='Output directory for graphs.') |
| return parser |
| |
| |
| def main(): |
| logging.basicConfig(level=logging.INFO) |
| parser = _CreateArgumentParser() |
| args = parser.parse_args() |
| |
| logging.info('Loading the trace') |
| trace = trace_utils.LoadTrace(args.trace) |
| |
| logging.info('Parsing the trace') |
| stats_per_process = trace_utils.ParseTrace(trace, |
| _SummarizeStatsForAllocators) |
| |
| logging.info('Plotting the results') |
| for pid in stats_per_process: |
| if 'data' in stats_per_process[pid]: |
| _PlotProcess(stats_per_process, pid, |
| os.path.join(args.output_dir, 'external')) |
| |
| |
| if __name__ == '__main__': |
| main() |