• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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