Commit 040b53f2 authored by Henrique Nakashima's avatar Henrique Nakashima Committed by Commit Bot

[Lorenz] Generate modularization metrics

This will be used in the builder to generate stats to track the progress
of modularization (which is done by running some scripts manually at
the moment.

Sample output:
{"class_edges": 51396, "class_nodes": 8368, "inbound_ChromeActivity": 196, "inbound_ChromeTabbedActivity": 24, "package_cycles_size_equals_2": 390, "package_cycles_size_up_to_4": 19949, "package_edges": 7932, "package_nodes": 744}

Bug: 1133838
Change-Id: Ia732386706740df290e58b0187c25589320217b7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2441323
Commit-Queue: Henrique Nakashima <hnakashima@chromium.org>
Reviewed-by: default avatarMohamed Heikal <mheikal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812930}
parent 46a6c2f2
#!/usr/bin/env python3
# Copyright 2020 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.
"""Command-line tool for generating modularization stats."""
import argparse
import json
from typing import Dict, List
import class_dependency
import count_cycles
import graph
import package_dependency
import print_dependencies_helper
import serialization
CLASSES_TO_COUNT_INBOUND = ['ChromeActivity', 'ChromeTabbedActivity']
def _generate_graph_sizes(
class_graph: class_dependency.JavaClassDependencyGraph,
package_graph: package_dependency.JavaPackageDependencyGraph
) -> Dict[str, int]:
return {
'class_nodes': class_graph.num_nodes,
'class_edges': class_graph.num_edges,
'package_nodes': package_graph.num_nodes,
'package_edges': package_graph.num_edges
}
def _generate_inbound_stats(
class_graph: class_dependency.JavaClassDependencyGraph,
class_names: List[str]) -> Dict[str, int]:
valid_class_names = \
print_dependencies_helper.get_valid_classes_from_class_list(
class_graph, class_names)
result = {}
for class_name, valid_class_name in zip(class_names, valid_class_names):
node: class_dependency.JavaClass = class_graph.get_node_by_key(
valid_class_name)
result[f'inbound_{class_name}'] = len(node.inbound)
return result
def _generate_package_cycle_stats(
package_graph: package_dependency.JavaPackageDependencyGraph
) -> Dict[str, int]:
all_cycles = count_cycles.find_cycles(package_graph, 4)
cycles_size_2 = len(all_cycles[2])
cycles_size_up_to_4 = sum(map(len, all_cycles[2:]))
return {
'package_cycles_size_equals_2': cycles_size_2,
'package_cycles_size_up_to_4': cycles_size_up_to_4
}
def main():
arg_parser = argparse.ArgumentParser(
description='Given a JSON dependency graph, output a JSON with a '
'number of metrics to track progress of modularization.')
required_arg_group = arg_parser.add_argument_group('required arguments')
required_arg_group.add_argument(
'-f',
'--file',
required=True,
help='Path to the JSON file containing the dependency graph. '
'See the README on how to generate this file.')
arg_parser.add_argument(
'-o',
'--output',
help='File to write the result json to. In not specified, outputs to '
'stdout.')
arguments = arg_parser.parse_args()
class_graph, package_graph = \
serialization.load_class_and_package_graphs_from_file(arguments.file)
stats = {}
stats.update(_generate_graph_sizes(class_graph, package_graph))
stats.update(_generate_inbound_stats(class_graph,
CLASSES_TO_COUNT_INBOUND))
stats.update(_generate_package_cycle_stats(package_graph))
if arguments.output:
with open(arguments.output, 'w') as f:
json.dump(stats, f, sort_keys=True)
else:
print(json.dumps(stats, sort_keys=True))
if __name__ == '__main__':
main()
......@@ -186,67 +186,6 @@ def print_class_dependencies_for_key(
return target_dependencies
def get_valid_classes_from_class_input(
class_graph: class_dependency.JavaClassDependencyGraph,
class_names_input: str) -> List[str]:
"""Parses classes given as input into fully qualified, valid classes."""
result = []
class_graph_keys = [node.name for node in class_graph.nodes]
class_names = class_names_input.split(',')
for class_name in class_names:
valid_keys = print_dependencies_helper.get_valid_class_keys_matching(
class_graph_keys, class_name)
check_only_one_valid_key(valid_keys, class_name, 'class')
result.append(valid_keys[0])
return result
def get_valid_classes_from_package_input(
package_graph: package_dependency.JavaPackageDependencyGraph,
package_names_input: str) -> List[str]:
"""Parses packages given as input into fully qualified, valid classes."""
result = []
package_graph_keys = [node.name for node in package_graph.nodes]
package_names = package_names_input.split(',')
for package_name in package_names:
valid_keys = print_dependencies_helper.get_valid_package_keys_matching(
package_graph_keys, package_name)
check_only_one_valid_key(valid_keys, package_name, 'package')
package_key: str = valid_keys[0]
package_node: package_dependency.JavaPackage = \
package_graph.get_node_by_key(package_key)
classes_in_package: List[str] = sorted(package_node.classes.keys())
result.extend(classes_in_package)
return result
def check_only_one_valid_key(valid_keys: List[str], key_input: str,
entity: str) -> None:
if len(valid_keys) == 0:
raise ValueError(f'No {entity} found by the name {key_input}.')
elif len(valid_keys) > 1:
print(f'Multiple valid keys found for the name {key_input}, '
'please disambiguate between one of the following options:')
for valid_key in valid_keys:
print(f'\t{valid_key}')
raise ValueError(
f'Multiple valid keys found for the name {key_input}.')
else: # len(valid_keys) == 1
return
def main():
"""Prints class-level dependencies for one or more input classes."""
arg_parser = argparse.ArgumentParser(
......@@ -313,12 +252,12 @@ def main():
valid_class_names = []
if arguments.class_names:
valid_class_names.extend(
get_valid_classes_from_class_input(class_graph,
arguments.class_names))
print_dependencies_helper.get_valid_classes_from_class_input(
class_graph, arguments.class_names))
if arguments.package_names:
valid_class_names.extend(
get_valid_classes_from_package_input(package_graph,
arguments.package_names))
print_dependencies_helper.get_valid_classes_from_package_input(
package_graph, arguments.package_names))
target_dependencies = TargetDependencies()
for i, fully_qualified_class_name in enumerate(valid_class_names):
......
......@@ -6,6 +6,80 @@
from typing import List
import class_dependency
import package_dependency
def get_valid_classes_from_class_input(
class_graph: class_dependency.JavaClassDependencyGraph,
class_names_input: str) -> List[str]:
"""Parses classes given as input into fully qualified, valid classes.
Input is a comma-separated list of classes."""
class_names = class_names_input.split(',')
return get_valid_classes_from_class_list(class_graph, class_names)
def get_valid_classes_from_class_list(
class_graph: class_dependency.JavaClassDependencyGraph,
class_names: List[str]) -> List[str]:
"""Parses classes given as input into fully qualified, valid classes.
Input is a list of class names."""
result = []
class_graph_keys = [node.name for node in class_graph.nodes]
for class_name in class_names:
valid_keys = get_valid_class_keys_matching(class_graph_keys,
class_name)
_check_only_one_valid_key(valid_keys, class_name, 'class')
result.append(valid_keys[0])
return result
def get_valid_classes_from_package_input(
package_graph: package_dependency.JavaPackageDependencyGraph,
package_names_input: str) -> List[str]:
"""Parses packages given as input into fully qualified, valid classes."""
result = []
package_graph_keys = [node.name for node in package_graph.nodes]
package_names = package_names_input.split(',')
for package_name in package_names:
valid_keys = get_valid_package_keys_matching(package_graph_keys,
package_name)
_check_only_one_valid_key(valid_keys, package_name, 'package')
package_key: str = valid_keys[0]
package_node: package_dependency.JavaPackage = \
package_graph.get_node_by_key(package_key)
classes_in_package: List[str] = sorted(package_node.classes.keys())
result.extend(classes_in_package)
return result
def _check_only_one_valid_key(valid_keys: List[str], key_input: str,
entity: str) -> None:
if len(valid_keys) == 0:
raise ValueError(f'No {entity} found by the name {key_input}.')
elif len(valid_keys) > 1:
print(f'Multiple valid keys found for the name {key_input}, '
'please disambiguate between one of the following options:')
for valid_key in valid_keys:
print(f'\t{valid_key}')
raise ValueError(
f'Multiple valid keys found for the name {key_input}.')
else: # len(valid_keys) == 1
return
def get_valid_package_keys_matching(all_keys: List,
input_key: str) -> List[str]:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment