You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
15 KiB
347 lines
15 KiB
#!/usr/bin/python2 |
|
|
|
# Copyright 2016 The Chromium OS Authors. All rights reserved. |
|
# Use of this source code is governed by a BSD-style license that can be |
|
# found in the LICENSE file. |
|
"""Selects the optimal set of benchmarks. |
|
|
|
For each benchmark, there is a file with the common functions, as extracted by |
|
the process_hot_functions module. |
|
|
|
The script receives as input the CSV file with the CWP inclusive count values, |
|
the file with Chrome OS groups and the path containing a file with common |
|
functions for every benchmark. |
|
|
|
It extracts for every benchmark and for the CWP data all the functions that |
|
match the given Chrome OS groups. |
|
|
|
It generates all possible combinations of benchmark sets of a given size and |
|
it computes for every set a metric. |
|
It outputs the optimal sets, based on which ones have the best metric. |
|
|
|
Three different metrics have been used: function count, distance |
|
variation and score. |
|
|
|
For the function count metric, we count the unique functions covered by a |
|
set of benchmarks. Besides the number of unique functions, we compute also |
|
the fraction of unique functions out of the amount of CWP functions from the |
|
given groups. The benchmark set with the highest amount of unique functions |
|
that belong to all the given groups is considered better. |
|
|
|
For the distance variation metric, we compute the sum of the distance variations |
|
of the functions covered by a set of benchmarks. We define the distance |
|
variation as the difference between the distance value of a function and the |
|
ideal distance value (1.0). If a function appears in multiple common functions |
|
files, we consider only the minimum value. We compute also the distance |
|
variation per function. The set that has the smaller value for the |
|
distance variation per function is considered better. |
|
|
|
For the score metric, we compute the sum of the scores of the functions from a |
|
set of benchmarks. If a function appears in multiple common functions files, |
|
we consider only the maximum value. We compute also the fraction of this sum |
|
from the sum of all the scores of the functions from the CWP data covering the |
|
given groups, in the ideal case (the ideal score of a function is 1.0). |
|
|
|
We compute the metrics in the same manner for individual Chrome OS groups. |
|
""" |
|
|
|
from collections import defaultdict |
|
|
|
import argparse |
|
import csv |
|
import itertools |
|
import json |
|
import operator |
|
import os |
|
import sys |
|
|
|
import benchmark_metrics |
|
import utils |
|
|
|
|
|
class BenchmarkSet(object): |
|
"""Selects the optimal set of benchmarks of given size.""" |
|
|
|
# Constants that specify the metric type. |
|
FUNCTION_COUNT_METRIC = 'function_count' |
|
DISTANCE_METRIC = 'distance_variation' |
|
SCORE_METRIC = 'score_fraction' |
|
|
|
def __init__(self, benchmark_set_size, benchmark_set_output_file, |
|
benchmark_set_common_functions_path, cwp_inclusive_count_file, |
|
cwp_function_groups_file, metric): |
|
"""Initializes the BenchmarkSet. |
|
|
|
Args: |
|
benchmark_set_size: Constant representing the size of a benchmark set. |
|
benchmark_set_output_file: The output file that will contain the set of |
|
optimal benchmarks with the metric values. |
|
benchmark_set_common_functions_path: The directory containing the files |
|
with the common functions for the list of benchmarks. |
|
cwp_inclusive_count_file: The CSV file containing the CWP functions with |
|
their inclusive count values. |
|
cwp_function_groups_file: The file that contains the CWP function groups. |
|
metric: The type of metric used for the analysis. |
|
""" |
|
self._benchmark_set_size = int(benchmark_set_size) |
|
self._benchmark_set_output_file = benchmark_set_output_file |
|
self._benchmark_set_common_functions_path = \ |
|
benchmark_set_common_functions_path |
|
self._cwp_inclusive_count_file = cwp_inclusive_count_file |
|
self._cwp_function_groups_file = cwp_function_groups_file |
|
self._metric = metric |
|
|
|
@staticmethod |
|
def OrganizeCWPFunctionsInGroups(cwp_inclusive_count_statistics, |
|
cwp_function_groups): |
|
"""Selects the CWP functions that match the given Chrome OS groups. |
|
|
|
Args: |
|
cwp_inclusive_count_statistics: A dict with the CWP functions. |
|
cwp_function_groups: A list with the CWP function groups. |
|
|
|
Returns: |
|
A dict having as a key the name of the groups and as a value the list of |
|
CWP functions that match an individual group. |
|
""" |
|
cwp_functions_grouped = defaultdict(list) |
|
for function_key in cwp_inclusive_count_statistics: |
|
_, file_name = function_key.split(',') |
|
for group_name, file_path in cwp_function_groups: |
|
if file_path not in file_name: |
|
continue |
|
cwp_functions_grouped[group_name].append(function_key) |
|
break |
|
return cwp_functions_grouped |
|
|
|
@staticmethod |
|
def OrganizeBenchmarkSetFunctionsInGroups(benchmark_set_files, |
|
benchmark_set_common_functions_path, |
|
cwp_function_groups): |
|
"""Selects the benchmark functions that match the given Chrome OS groups. |
|
|
|
Args: |
|
benchmark_set_files: The list of common functions files corresponding to a |
|
benchmark. |
|
benchmark_set_common_functions_path: The directory containing the files |
|
with the common functions for the list of benchmarks. |
|
cwp_function_groups: A list with the CWP function groups. |
|
|
|
Returns: |
|
A dict having as a key the name of a common functions file. The value is |
|
a dict having as a key the name of a group and as value a list of |
|
functions that match the given group. |
|
""" |
|
|
|
benchmark_set_functions_grouped = {} |
|
for benchmark_file_name in benchmark_set_files: |
|
benchmark_full_file_path = \ |
|
os.path.join(benchmark_set_common_functions_path, |
|
benchmark_file_name) |
|
with open(benchmark_full_file_path) as input_file: |
|
statistics_reader = \ |
|
csv.DictReader(input_file, delimiter=',') |
|
benchmark_functions_grouped = defaultdict(dict) |
|
for statistic in statistics_reader: |
|
function_name = statistic['function'] |
|
file_name = statistic['file'] |
|
for group_name, file_path in cwp_function_groups: |
|
if file_path not in file_name: |
|
continue |
|
function_key = ','.join([function_name, file_name]) |
|
distance = float(statistic['distance']) |
|
score = float(statistic['score']) |
|
benchmark_functions_grouped[group_name][function_key] = \ |
|
(distance, score) |
|
break |
|
benchmark_set_functions_grouped[benchmark_file_name] = \ |
|
benchmark_functions_grouped |
|
return benchmark_set_functions_grouped |
|
|
|
@staticmethod |
|
def SelectOptimalBenchmarkSetBasedOnMetric(all_benchmark_combinations_sets, |
|
benchmark_set_functions_grouped, |
|
cwp_functions_grouped, |
|
metric_function_for_set, |
|
metric_comparison_operator, |
|
metric_default_value, |
|
metric_string): |
|
"""Generic method that selects the optimal benchmark set based on a metric. |
|
|
|
The reason of implementing a generic function is to avoid logic duplication |
|
for selecting a benchmark set based on the three different metrics. |
|
|
|
Args: |
|
all_benchmark_combinations_sets: The list with all the sets of benchmark |
|
combinations. |
|
benchmark_set_functions_grouped: A dict with benchmark functions as |
|
returned by OrganizeBenchmarkSetFunctionsInGroups. |
|
cwp_functions_grouped: A dict with the CWP functions as returned by |
|
OrganizeCWPFunctionsInGroups. |
|
metric_function_for_set: The method used to compute the metric for a given |
|
benchmark set. |
|
metric_comparison_operator: A comparison operator used to compare two |
|
values of the same metric (i.e: operator.lt or operator.gt). |
|
metric_default_value: The default value for the metric. |
|
metric_string: A tuple of strings used in the JSON output for the pair of |
|
the values of the metric. |
|
|
|
Returns: |
|
A list of tuples containing for each optimal benchmark set. A tuple |
|
contains the list of benchmarks from the set, the pair of metric values |
|
and a dictionary with the metrics for each group. |
|
""" |
|
optimal_sets = [([], metric_default_value, {})] |
|
|
|
for benchmark_combination_set in all_benchmark_combinations_sets: |
|
function_metrics = [benchmark_set_functions_grouped[benchmark] |
|
for benchmark in benchmark_combination_set] |
|
set_metrics, set_groups_metrics = \ |
|
metric_function_for_set(function_metrics, cwp_functions_grouped, |
|
metric_string) |
|
optimal_value = optimal_sets[0][1][0] |
|
if metric_comparison_operator(set_metrics[0], optimal_value): |
|
optimal_sets = \ |
|
[(benchmark_combination_set, set_metrics, set_groups_metrics)] |
|
elif set_metrics[0] == optimal_sets[0][1][0]: |
|
optimal_sets.append( |
|
(benchmark_combination_set, set_metrics, set_groups_metrics)) |
|
|
|
return optimal_sets |
|
|
|
def SelectOptimalBenchmarkSet(self): |
|
"""Selects the optimal benchmark sets and writes them in JSON format. |
|
|
|
Parses the CWP inclusive count statistics and benchmark common functions |
|
files. Organizes the functions into groups. For every optimal benchmark |
|
set, the method writes in the self._benchmark_set_output_file the list of |
|
benchmarks, the pair of metrics and a dictionary with the pair of |
|
metrics for each group covered by the benchmark set. |
|
""" |
|
|
|
benchmark_set_files = os.listdir(self._benchmark_set_common_functions_path) |
|
all_benchmark_combinations_sets = \ |
|
itertools.combinations(benchmark_set_files, self._benchmark_set_size) |
|
|
|
with open(self._cwp_function_groups_file) as input_file: |
|
cwp_function_groups = utils.ParseFunctionGroups(input_file.readlines()) |
|
|
|
cwp_inclusive_count_statistics = \ |
|
utils.ParseCWPInclusiveCountFile(self._cwp_inclusive_count_file) |
|
cwp_functions_grouped = self.OrganizeCWPFunctionsInGroups( |
|
cwp_inclusive_count_statistics, cwp_function_groups) |
|
benchmark_set_functions_grouped = \ |
|
self.OrganizeBenchmarkSetFunctionsInGroups( |
|
benchmark_set_files, self._benchmark_set_common_functions_path, |
|
cwp_function_groups) |
|
|
|
if self._metric == self.FUNCTION_COUNT_METRIC: |
|
metric_function_for_benchmark_set = \ |
|
benchmark_metrics.ComputeFunctionCountForBenchmarkSet |
|
metric_comparison_operator = operator.gt |
|
metric_default_value = (0, 0.0) |
|
metric_string = ('function_count', 'function_count_fraction') |
|
elif self._metric == self.DISTANCE_METRIC: |
|
metric_function_for_benchmark_set = \ |
|
benchmark_metrics.ComputeDistanceForBenchmarkSet |
|
metric_comparison_operator = operator.lt |
|
metric_default_value = (float('inf'), float('inf')) |
|
metric_string = \ |
|
('distance_variation_per_function', 'total_distance_variation') |
|
elif self._metric == self.SCORE_METRIC: |
|
metric_function_for_benchmark_set = \ |
|
benchmark_metrics.ComputeScoreForBenchmarkSet |
|
metric_comparison_operator = operator.gt |
|
metric_default_value = (0.0, 0.0) |
|
metric_string = ('score_fraction', 'total_score') |
|
else: |
|
raise ValueError("Invalid metric") |
|
|
|
optimal_benchmark_sets = \ |
|
self.SelectOptimalBenchmarkSetBasedOnMetric( |
|
all_benchmark_combinations_sets, benchmark_set_functions_grouped, |
|
cwp_functions_grouped, metric_function_for_benchmark_set, |
|
metric_comparison_operator, metric_default_value, metric_string) |
|
|
|
json_output = [] |
|
|
|
for benchmark_set in optimal_benchmark_sets: |
|
json_entry = { |
|
'benchmark_set': |
|
list(benchmark_set[0]), |
|
'metrics': { |
|
metric_string[0]: benchmark_set[1][0], |
|
metric_string[1]: benchmark_set[1][1] |
|
}, |
|
'groups': |
|
dict(benchmark_set[2]) |
|
} |
|
json_output.append(json_entry) |
|
|
|
with open(self._benchmark_set_output_file, 'w') as output_file: |
|
json.dump(json_output, output_file) |
|
|
|
|
|
def ParseArguments(arguments): |
|
parser = argparse.ArgumentParser() |
|
|
|
parser.add_argument( |
|
'--benchmark_set_common_functions_path', |
|
required=True, |
|
help='The directory containing the CSV files with the common functions ' |
|
'of the benchmark profiles and CWP data. A file will contain all the hot ' |
|
'functions from a pprof top output file that are also included in the ' |
|
'file containing the cwp inclusive count values. The CSV fields are: the ' |
|
'function name, the file and the object where the function is declared, ' |
|
'the CWP inclusive count and inclusive count fraction values, the ' |
|
'cumulative and average distance, the cumulative and average score. The ' |
|
'files with the common functions will have the same names with the ' |
|
'corresponding pprof output files.') |
|
parser.add_argument( |
|
'--cwp_inclusive_count_file', |
|
required=True, |
|
help='The CSV file containing the CWP hot functions with their ' |
|
'inclusive_count values. The CSV fields include the name of the ' |
|
'function, the file and the object with the definition, the inclusive ' |
|
'count value and the inclusive count fraction out of the total amount of ' |
|
'inclusive count values.') |
|
parser.add_argument( |
|
'--benchmark_set_size', |
|
required=True, |
|
help='The size of the benchmark sets.') |
|
parser.add_argument( |
|
'--benchmark_set_output_file', |
|
required=True, |
|
help='The JSON output file containing optimal benchmark sets with their ' |
|
'metrics. For every optimal benchmark set, the file contains the list of ' |
|
'benchmarks, the pair of metrics and a dictionary with the pair of ' |
|
'metrics for each group covered by the benchmark set.') |
|
parser.add_argument( |
|
'--metric', |
|
required=True, |
|
help='The metric used to select the optimal benchmark set. The possible ' |
|
'values are: distance_variation, function_count and score_fraction.') |
|
parser.add_argument( |
|
'--cwp_function_groups_file', |
|
required=True, |
|
help='The file that contains the CWP function groups. A line consists in ' |
|
'the group name and a file path describing the group. A group must ' |
|
'represent a Chrome OS component.') |
|
|
|
options = parser.parse_args(arguments) |
|
|
|
return options |
|
|
|
|
|
def Main(argv): |
|
options = ParseArguments(argv) |
|
benchmark_set = BenchmarkSet(options.benchmark_set_size, |
|
options.benchmark_set_output_file, |
|
options.benchmark_set_common_functions_path, |
|
options.cwp_inclusive_count_file, |
|
options.cwp_function_groups_file, options.metric) |
|
benchmark_set.SelectOptimalBenchmarkSet() |
|
|
|
|
|
if __name__ == '__main__': |
|
Main(sys.argv[1:])
|
|
|