1#!/usr/bin/env python 2# 3# Copyright 2015 the V8 project authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""This script is used to analyze GCTracer's NVP output.""" 8 9 10# for py2/py3 compatibility 11from __future__ import print_function 12 13 14from argparse import ArgumentParser 15from copy import deepcopy 16from gc_nvp_common import split_nvp 17from math import ceil, log 18from sys import stdin 19 20 21class LinearBucket: 22 def __init__(self, granularity): 23 self.granularity = granularity 24 25 def value_to_bucket(self, value): 26 return int(value / self.granularity) 27 28 def bucket_to_range(self, bucket): 29 return (bucket * self.granularity, (bucket + 1) * self.granularity) 30 31 32class Log2Bucket: 33 def __init__(self, start): 34 self.start = int(log(start, 2)) - 1 35 36 def value_to_bucket(self, value): 37 index = int(log(value, 2)) 38 index -= self.start 39 if index < 0: 40 index = 0 41 return index 42 43 def bucket_to_range(self, bucket): 44 if bucket == 0: 45 return (0, 2 ** (self.start + 1)) 46 bucket += self.start 47 return (2 ** bucket, 2 ** (bucket + 1)) 48 49 50class Histogram: 51 def __init__(self, bucket_trait, fill_empty): 52 self.histogram = {} 53 self.fill_empty = fill_empty 54 self.bucket_trait = bucket_trait 55 56 def add(self, key): 57 index = self.bucket_trait.value_to_bucket(key) 58 if index not in self.histogram: 59 self.histogram[index] = 0 60 self.histogram[index] += 1 61 62 def __str__(self): 63 ret = [] 64 keys = self.histogram.keys() 65 keys.sort() 66 last = keys[len(keys) - 1] 67 for i in range(0, last + 1): 68 (min_value, max_value) = self.bucket_trait.bucket_to_range(i) 69 if i == keys[0]: 70 keys.pop(0) 71 ret.append(" [{0},{1}[: {2}".format( 72 str(min_value), str(max_value), self.histogram[i])) 73 else: 74 if self.fill_empty: 75 ret.append(" [{0},{1}[: {2}".format( 76 str(min_value), str(max_value), 0)) 77 return "\n".join(ret) 78 79 80class Category: 81 def __init__(self, key, histogram, csv, percentiles): 82 self.key = key 83 self.values = [] 84 self.histogram = histogram 85 self.csv = csv 86 self.percentiles = percentiles 87 88 def process_entry(self, entry): 89 if self.key in entry: 90 self.values.append(float(entry[self.key])) 91 if self.histogram: 92 self.histogram.add(float(entry[self.key])) 93 94 def min(self): 95 return min(self.values) 96 97 def max(self): 98 return max(self.values) 99 100 def avg(self): 101 if len(self.values) == 0: 102 return 0.0 103 return sum(self.values) / len(self.values) 104 105 def empty(self): 106 return len(self.values) == 0 107 108 def _compute_percentiles(self): 109 ret = [] 110 if len(self.values) == 0: 111 return ret 112 sorted_values = sorted(self.values) 113 for percentile in self.percentiles: 114 index = int(ceil((len(self.values) - 1) * percentile / 100)) 115 ret.append(" {0}%: {1}".format(percentile, sorted_values[index])) 116 return ret 117 118 def __str__(self): 119 if self.csv: 120 ret = [self.key] 121 ret.append(len(self.values)) 122 ret.append(self.min()) 123 ret.append(self.max()) 124 ret.append(self.avg()) 125 ret = [str(x) for x in ret] 126 return ",".join(ret) 127 else: 128 ret = [self.key] 129 ret.append(" len: {0}".format(len(self.values))) 130 if len(self.values) > 0: 131 ret.append(" min: {0}".format(self.min())) 132 ret.append(" max: {0}".format(self.max())) 133 ret.append(" avg: {0}".format(self.avg())) 134 if self.histogram: 135 ret.append(str(self.histogram)) 136 if self.percentiles: 137 ret.append("\n".join(self._compute_percentiles())) 138 return "\n".join(ret) 139 140 def __repr__(self): 141 return "<Category: {0}>".format(self.key) 142 143 144def make_key_func(cmp_metric): 145 def key_func(a): 146 return getattr(a, cmp_metric)() 147 return key_func 148 149 150def main(): 151 parser = ArgumentParser(description="Process GCTracer's NVP output") 152 parser.add_argument('keys', metavar='KEY', type=str, nargs='+', 153 help='the keys of NVPs to process') 154 parser.add_argument('--histogram-type', metavar='<linear|log2>', 155 type=str, nargs='?', default="linear", 156 help='histogram type to use (default: linear)') 157 linear_group = parser.add_argument_group('linear histogram specific') 158 linear_group.add_argument('--linear-histogram-granularity', 159 metavar='GRANULARITY', type=int, nargs='?', 160 default=5, 161 help='histogram granularity (default: 5)') 162 log2_group = parser.add_argument_group('log2 histogram specific') 163 log2_group.add_argument('--log2-histogram-init-bucket', metavar='START', 164 type=int, nargs='?', default=64, 165 help='initial buck size (default: 64)') 166 parser.add_argument('--histogram-omit-empty-buckets', 167 dest='histogram_omit_empty', 168 action='store_true', 169 help='omit empty histogram buckets') 170 parser.add_argument('--no-histogram', dest='histogram', 171 action='store_false', help='do not print histogram') 172 parser.set_defaults(histogram=True) 173 parser.set_defaults(histogram_omit_empty=False) 174 parser.add_argument('--rank', metavar='<no|min|max|avg>', 175 type=str, nargs='?', 176 default="no", 177 help="rank keys by metric (default: no)") 178 parser.add_argument('--csv', dest='csv', 179 action='store_true', help='provide output as csv') 180 parser.add_argument('--percentiles', dest='percentiles', 181 type=str, default="", 182 help='comma separated list of percentiles') 183 args = parser.parse_args() 184 185 histogram = None 186 if args.histogram: 187 bucket_trait = None 188 if args.histogram_type == "log2": 189 bucket_trait = Log2Bucket(args.log2_histogram_init_bucket) 190 else: 191 bucket_trait = LinearBucket(args.linear_histogram_granularity) 192 histogram = Histogram(bucket_trait, not args.histogram_omit_empty) 193 194 percentiles = [] 195 for percentile in args.percentiles.split(','): 196 try: 197 percentiles.append(float(percentile)) 198 except ValueError: 199 pass 200 201 categories = [ Category(key, deepcopy(histogram), args.csv, percentiles) 202 for key in args.keys ] 203 204 while True: 205 line = stdin.readline() 206 if not line: 207 break 208 obj = split_nvp(line) 209 for category in categories: 210 category.process_entry(obj) 211 212 # Filter out empty categories. 213 categories = [x for x in categories if not x.empty()] 214 215 if args.rank != "no": 216 categories = sorted(categories, key=make_key_func(args.rank), reverse=True) 217 218 for category in categories: 219 print(category) 220 221 222if __name__ == '__main__': 223 main() 224