|  | #!/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() |