| # Copyright 2022 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import collections |
| import json |
| import re |
| |
| |
| def re_from_string_or_list(input): |
| if input is None: |
| return None |
| re_str = input if isinstance(input, str) else '|'.join(input) |
| return re.compile(re_str) |
| |
| |
| class BucketMatch(object): |
| |
| def __init__(self, name, process, vmo): |
| self.name = name |
| self.process = re_from_string_or_list(process) |
| self.vmo = re_from_string_or_list(vmo) |
| |
| |
| class Bucket(object): |
| |
| def __init__(self, name): |
| self.name = name |
| self.processes = collections.defaultdict(list) |
| self.size = 0 |
| |
| |
| class Digest(object): |
| |
| @classmethod |
| def FromMatchSpecs(cls, snapshot, match_specs): |
| return Digest( |
| snapshot, [BucketMatch(m[0], m[1], m[2]) for m in match_specs]) |
| |
| @classmethod |
| def FromJSON(cls, snapshot, match_json): |
| return Digest( |
| snapshot, [ |
| BucketMatch( |
| m["name"], m["process"] if "process" in m else None, |
| m["vmo"] if "vmo" in m else None) for m in match_json |
| ]) |
| |
| @classmethod |
| def FromJSONFile(cls, snapshot, match_file): |
| return cls.FromJSON(snapshot, json.load(match_file)) |
| |
| @classmethod |
| def FromJSONString(cls, snapshot, match_string): |
| return cls.FromJSON(snapshot, json.loads(match_string)) |
| |
| @classmethod |
| def FromJSONFilename(cls, snapshot, match_filename): |
| with open(match_filename) as match_file: |
| return cls.FromJSONFile(snapshot, match_file) |
| |
| def __init__(self, snapshot, bucket_matches): |
| self.buckets = {bm.name: Bucket(bm.name) for bm in bucket_matches} |
| undigested_vmos = snapshot.vmos.copy() |
| # Each VMO will be assigned to at most one bucket. Precedence follows |
| # the order of bucket_matches. |
| for process in snapshot.processes.values(): |
| for bm in bucket_matches: |
| if bm.process and not bm.process.fullmatch(process.name): |
| continue |
| for vmo in process.vmos: |
| if vmo.koid not in undigested_vmos: |
| continue |
| if bm.vmo and not bm.vmo.fullmatch(vmo.name): |
| continue |
| if vmo.committed_bytes == 0: |
| continue |
| self.buckets[bm.name].processes[(process,)].append(vmo) |
| self.buckets[bm.name].size += vmo.committed_bytes |
| del undigested_vmos[vmo.koid] |
| |
| undigested = Bucket("Undigested") |
| # For the Undigested bucket, we want to store VMOs by "sharing pools" |
| # That is, the keys to undigested.processes will be a set of process |
| # names that map to the list of VMOs they all share. |
| vmos_to_processes = collections.defaultdict(list) |
| for process in snapshot.processes.values(): |
| for v in filter(lambda v: v.koid in undigested_vmos, process.vmos): |
| vmos_to_processes[v.koid].append(process) |
| |
| for vmo_koid, processes in vmos_to_processes.items(): |
| processes.sort(key=lambda p: p.full_name) |
| vmo = snapshot.vmos[vmo_koid] |
| if vmo.committed_bytes == 0: |
| continue |
| undigested.processes[tuple(processes)].append(vmo) |
| undigested.size += vmo.committed_bytes |
| self.buckets["Undigested"] = undigested |
| |
| kernel = Bucket("Kernel") |
| kernel.size = ( |
| snapshot.kernel.wired + snapshot.kernel.total_heap + |
| snapshot.kernel.mmu + snapshot.kernel.ipc + snapshot.kernel.other) |
| self.buckets["Kernel"] = kernel |
| orphaned = Bucket("Orphaned") |
| orphaned.size = snapshot.orphaned |
| self.buckets["Orphaned"] = orphaned |