| #!/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.""" |
| |
| |
| # for py2/py3 compatibility |
| from __future__ import print_function |
| |
| |
| 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() |