| #!/usr/bin/env python | |
| # | |
| # Copyright 2015 the V8 project authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """This script is used to analyze GCTracer's NVP output.""" | |
| from argparse import ArgumentParser | |
| from copy import deepcopy | |
| from gc_nvp_common import split_nvp | |
| from math import ceil,log | |
| from sys import stdin | |
| class LinearBucket: | |
| def __init__(self, granularity): | |
| self.granularity = granularity | |
| def value_to_bucket(self, value): | |
| return int(value / self.granularity) | |
| def bucket_to_range(self, bucket): | |
| return (bucket * self.granularity, (bucket + 1) * self.granularity) | |
| class Log2Bucket: | |
| def __init__(self, start): | |
| self.start = int(log(start, 2)) - 1 | |
| def value_to_bucket(self, value): | |
| index = int(log(value, 2)) | |
| index -= self.start | |
| if index < 0: | |
| index = 0 | |
| return index | |
| def bucket_to_range(self, bucket): | |
| if bucket == 0: | |
| return (0, 2 ** (self.start + 1)) | |
| bucket += self.start | |
| return (2 ** bucket, 2 ** (bucket + 1)) | |
| class Histogram: | |
| def __init__(self, bucket_trait, fill_empty): | |
| self.histogram = {} | |
| self.fill_empty = fill_empty | |
| self.bucket_trait = bucket_trait | |
| def add(self, key): | |
| index = self.bucket_trait.value_to_bucket(key) | |
| if index not in self.histogram: | |
| self.histogram[index] = 0 | |
| self.histogram[index] += 1 | |
| def __str__(self): | |
| ret = [] | |
| keys = self.histogram.keys() | |
| keys.sort() | |
| last = keys[len(keys) - 1] | |
| for i in range(0, last + 1): | |
| (min_value, max_value) = self.bucket_trait.bucket_to_range(i) | |
| if i == keys[0]: | |
| keys.pop(0) | |
| ret.append(" [{0},{1}[: {2}".format( | |
| str(min_value), str(max_value), self.histogram[i])) | |
| else: | |
| if self.fill_empty: | |
| ret.append(" [{0},{1}[: {2}".format( | |
| str(min_value), str(max_value), 0)) | |
| return "\n".join(ret) | |
| class Category: | |
| def __init__(self, key, histogram, csv, percentiles): | |
| self.key = key | |
| self.values = [] | |
| self.histogram = histogram | |
| self.csv = csv | |
| self.percentiles = percentiles | |
| def process_entry(self, entry): | |
| if self.key in entry: | |
| self.values.append(float(entry[self.key])) | |
| if self.histogram: | |
| self.histogram.add(float(entry[self.key])) | |
| def min(self): | |
| return min(self.values) | |
| def max(self): | |
| return max(self.values) | |
| def avg(self): | |
| if len(self.values) == 0: | |
| return 0.0 | |
| return sum(self.values) / len(self.values) | |
| def empty(self): | |
| return len(self.values) == 0 | |
| def _compute_percentiles(self): | |
| ret = [] | |
| if len(self.values) == 0: | |
| return ret | |
| sorted_values = sorted(self.values) | |
| for percentile in self.percentiles: | |
| index = int(ceil((len(self.values) - 1) * percentile / 100)) | |
| ret.append(" {0}%: {1}".format(percentile, sorted_values[index])) | |
| return ret | |
| def __str__(self): | |
| if self.csv: | |
| ret = [self.key] | |
| ret.append(len(self.values)) | |
| ret.append(self.min()) | |
| ret.append(self.max()) | |
| ret.append(self.avg()) | |
| ret = [str(x) for x in ret] | |
| return ",".join(ret) | |
| else: | |
| ret = [self.key] | |
| ret.append(" len: {0}".format(len(self.values))) | |
| if len(self.values) > 0: | |
| ret.append(" min: {0}".format(self.min())) | |
| ret.append(" max: {0}".format(self.max())) | |
| ret.append(" avg: {0}".format(self.avg())) | |
| if self.histogram: | |
| ret.append(str(self.histogram)) | |
| if self.percentiles: | |
| ret.append("\n".join(self._compute_percentiles())) | |
| return "\n".join(ret) | |
| def __repr__(self): | |
| return "<Category: {0}>".format(self.key) | |
| def make_key_func(cmp_metric): | |
| def key_func(a): | |
| return getattr(a, cmp_metric)() | |
| return key_func | |
| def main(): | |
| parser = ArgumentParser(description="Process GCTracer's NVP output") | |
| parser.add_argument('keys', metavar='KEY', type=str, nargs='+', | |
| help='the keys of NVPs to process') | |
| parser.add_argument('--histogram-type', metavar='<linear|log2>', | |
| type=str, nargs='?', default="linear", | |
| help='histogram type to use (default: linear)') | |
| linear_group = parser.add_argument_group('linear histogram specific') | |
| linear_group.add_argument('--linear-histogram-granularity', | |
| metavar='GRANULARITY', type=int, nargs='?', | |
| default=5, | |
| help='histogram granularity (default: 5)') | |
| log2_group = parser.add_argument_group('log2 histogram specific') | |
| log2_group.add_argument('--log2-histogram-init-bucket', metavar='START', | |
| type=int, nargs='?', default=64, | |
| help='initial buck size (default: 64)') | |
| parser.add_argument('--histogram-omit-empty-buckets', | |
| dest='histogram_omit_empty', | |
| action='store_true', | |
| help='omit empty histogram buckets') | |
| parser.add_argument('--no-histogram', dest='histogram', | |
| action='store_false', help='do not print histogram') | |
| parser.set_defaults(histogram=True) | |
| parser.set_defaults(histogram_omit_empty=False) | |
| parser.add_argument('--rank', metavar='<no|min|max|avg>', | |
| type=str, nargs='?', | |
| default="no", | |
| help="rank keys by metric (default: no)") | |
| parser.add_argument('--csv', dest='csv', | |
| action='store_true', help='provide output as csv') | |
| parser.add_argument('--percentiles', dest='percentiles', | |
| type=str, default="", | |
| help='comma separated list of percentiles') | |
| args = parser.parse_args() | |
| histogram = None | |
| if args.histogram: | |
| bucket_trait = None | |
| if args.histogram_type == "log2": | |
| bucket_trait = Log2Bucket(args.log2_histogram_init_bucket) | |
| else: | |
| bucket_trait = LinearBucket(args.linear_histogram_granularity) | |
| histogram = Histogram(bucket_trait, not args.histogram_omit_empty) | |
| percentiles = [] | |
| for percentile in args.percentiles.split(','): | |
| try: | |
| percentiles.append(float(percentile)) | |
| except ValueError: | |
| pass | |
| categories = [ Category(key, deepcopy(histogram), args.csv, percentiles) | |
| for key in args.keys ] | |
| while True: | |
| line = stdin.readline() | |
| if not line: | |
| break | |
| obj = split_nvp(line) | |
| for category in categories: | |
| category.process_entry(obj) | |
| # Filter out empty categories. | |
| categories = [x for x in categories if not x.empty()] | |
| if args.rank != "no": | |
| categories = sorted(categories, key=make_key_func(args.rank), reverse=True) | |
| for category in categories: | |
| print(category) | |
| if __name__ == '__main__': | |
| main() |