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