forked from Pissandshittium/pissandshittium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[fuchsia] Create a utility for understanding manifest contents
Some manifests, eg chrome/test:browser_tests have many files. This utility helps us understand what is there and make them smaller. Output can either be relative to file count or size. Bug: 1297758 Change-Id: I5daa42ef34119deed02b3ad6e7ee5a47c5d9eef8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3485416 Reviewed-by: Kevin Marshall <kmarshall@chromium.org> Commit-Queue: Bryant Chandler <bryantchandler@chromium.org> Cr-Commit-Position: refs/heads/main@{#976686}
- Loading branch information
Bryant Chandler
authored and
Chromium LUCI CQ
committed
Mar 2, 2022
1 parent
c01f358
commit 110fae9
Showing
1 changed file
with
161 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright 2022 The Chromium Authors. All rights reserved. | ||
# Use of this source code is governed by a BSD-style license that can be | ||
# found in the LICENSE file. | ||
""" | ||
Prints out du-style information about the files that will be deployed | ||
for a given target | ||
""" | ||
|
||
import argparse | ||
import os | ||
import re | ||
import sys | ||
from typing import Iterable | ||
from enum import Enum | ||
|
||
|
||
def format_size(bytesize: float) -> str: | ||
"""Convert bytes to human readable format. | ||
Args: | ||
bytesize: Number to humanize | ||
Returns: | ||
Size as string in human-readable format (e.g. 1.8MiB) | ||
""" | ||
if bytesize < 1024: | ||
return f'{bytesize}B' | ||
|
||
for suffix in 'BKMGTPEZY': | ||
if bytesize < 1024: | ||
break | ||
bytesize /= 1024 | ||
|
||
return f'{bytesize:.1f}{suffix}iB' # pylint: disable=undefined-loop-variable | ||
|
||
|
||
class FilesystemNode: | ||
def __init__(self, path: str) -> None: | ||
self.path = path | ||
self.descendant_count = 0 | ||
try: | ||
self.size = 0 if os.path.isdir(self.path) else os.path.getsize(self.path) | ||
except FileNotFoundError: | ||
print(f'{path} not found, please check that you have compiled ' | ||
'the target that generates this manifest.') | ||
exit(1) | ||
|
||
|
||
class Analysis(Enum): | ||
FILE_COUNT = 'file_count' | ||
SIZE = 'size' | ||
|
||
def __str__(self): | ||
return self.value | ||
|
||
|
||
class SortOrder(Enum): | ||
ASCENDING = 'ascending' | ||
DESCENDING = 'descending' | ||
|
||
def __str__(self): | ||
return self.value | ||
|
||
|
||
def compute_prefix_paths(path: str) -> Iterable[str]: | ||
prefix = path.rpartition('/')[0] | ||
while prefix: | ||
yield prefix | ||
prefix = prefix.rpartition('/')[0] | ||
|
||
|
||
class ManifestAnalyzer: | ||
def __init__(self) -> None: | ||
self.path_map: dict[str, FilesystemNode] = dict() | ||
|
||
def parse_manifest(self, manifest_path: str) -> None: | ||
out_dir = re.match('out\/[^\/]+', manifest_path).group() | ||
|
||
with open(manifest_path, 'r') as manifest: | ||
for line in manifest: | ||
relative_path = line.strip().partition('=')[2] | ||
self.register_file(f'{out_dir}/{relative_path}') | ||
|
||
def register_file(self, path: str) -> None: | ||
if path in self.path_map: | ||
return | ||
|
||
leaf_node = FilesystemNode(path) | ||
self.path_map[path] = leaf_node | ||
|
||
for prefix in compute_prefix_paths(path): | ||
if prefix in self.path_map: | ||
parent_node = self.path_map[prefix] | ||
else: | ||
parent_node = FilesystemNode(prefix) | ||
self.path_map[prefix] = parent_node | ||
|
||
parent_node.descendant_count += 1 | ||
parent_node.size += leaf_node.size | ||
|
||
def print_file_count(self, max_depth: int, sort_order: SortOrder) -> None: | ||
sorted_nodes = sorted(self.path_map.values(), | ||
key=lambda node: node.descendant_count, | ||
reverse=sort_order == SortOrder.DESCENDING) | ||
for node in sorted_nodes: | ||
if node.descendant_count <= 0: | ||
continue | ||
depth = node.path.count('/') | ||
if depth > max_depth: | ||
continue | ||
print(f'{node.descendant_count: >10}\t{node.path}') | ||
|
||
def print_byte_size(self, max_depth: int, sort_order: SortOrder) -> None: | ||
sorted_nodes = sorted(self.path_map.values(), | ||
key=lambda node: node.size, | ||
reverse=sort_order == SortOrder.DESCENDING) | ||
for node in sorted_nodes: | ||
depth = node.path.count('/') | ||
if depth > max_depth: | ||
continue | ||
print(f'{format_size(node.size): >10}\t{node.path}') | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser( | ||
description='Launches a long-running emulator that can ' | ||
'be re-used for multiple test runs.') | ||
parser.add_argument( | ||
'manifest_path', | ||
type=str, | ||
help='path to the .manifest ' | ||
'file. For example, the manifest for chrome/test:browser_tests can be ' | ||
'found at <out_dir>/gen/chrome/test/browser_tests/browser_tests.manifest') | ||
parser.add_argument('--analysis', | ||
type=Analysis, | ||
choices=list(Analysis), | ||
default=Analysis.SIZE, | ||
help='which type of analysis to print') | ||
parser.add_argument('--max-depth', | ||
type=int, | ||
default=sys.maxsize, | ||
help='only print directories to the provided depth') | ||
parser.add_argument( | ||
'--sort-order', | ||
type=SortOrder, | ||
choices=list(SortOrder), | ||
default=SortOrder.ASCENDING, | ||
help='which order to use for sorting, defualts to ascending') | ||
args = parser.parse_args() | ||
|
||
analyzer = ManifestAnalyzer() | ||
analyzer.parse_manifest(args.manifest_path) | ||
if args.analysis == Analysis.FILE_COUNT: | ||
analyzer.print_file_count(args.max_depth, args.sort_order) | ||
else: | ||
analyzer.print_byte_size(args.max_depth, args.sort_order) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |