Imported Upstream version 1.36.0
[platform/upstream/grpc.git] / tools / codegen / core / gen_stats_data.py
1 #!/usr/bin/env python2.7
2
3 # Copyright 2017 gRPC authors.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import collections
18 import ctypes
19 import math
20 import sys
21 import yaml
22 import json
23
24 with open('src/core/lib/debug/stats_data.yaml') as f:
25     attrs = yaml.load(f.read())
26
27 REQUIRED_FIELDS = ['name', 'doc']
28
29
30 def make_type(name, fields):
31     return (collections.namedtuple(
32         name, ' '.join(list(set(REQUIRED_FIELDS + fields)))), [])
33
34
35 def c_str(s, encoding='ascii'):
36     if isinstance(s, unicode):
37         s = s.encode(encoding)
38     result = ''
39     for c in s:
40         if not (32 <= ord(c) < 127) or c in ('\\', '"'):
41             result += '\\%03o' % ord(c)
42         else:
43             result += c
44     return '"' + result + '"'
45
46
47 types = (
48     make_type('Counter', []),
49     make_type('Histogram', ['max', 'buckets']),
50 )
51
52 inst_map = dict((t[0].__name__, t[1]) for t in types)
53
54 stats = []
55
56 for attr in attrs:
57     found = False
58     for t, lst in types:
59         t_name = t.__name__.lower()
60         if t_name in attr:
61             name = attr[t_name]
62             del attr[t_name]
63             lst.append(t(name=name, **attr))
64             found = True
65             break
66     assert found, "Bad decl: %s" % attr
67
68
69 def dbl2u64(d):
70     return ctypes.c_ulonglong.from_buffer(ctypes.c_double(d)).value
71
72
73 def shift_works_until(mapped_bounds, shift_bits):
74     for i, ab in enumerate(zip(mapped_bounds, mapped_bounds[1:])):
75         a, b = ab
76         if (a >> shift_bits) == (b >> shift_bits):
77             return i
78     return len(mapped_bounds)
79
80
81 def find_ideal_shift(mapped_bounds, max_size):
82     best = None
83     for shift_bits in reversed(range(0, 64)):
84         n = shift_works_until(mapped_bounds, shift_bits)
85         if n == 0:
86             continue
87         table_size = mapped_bounds[n - 1] >> shift_bits
88         if table_size > max_size:
89             continue
90         if table_size > 65535:
91             continue
92         if best is None:
93             best = (shift_bits, n, table_size)
94         elif best[1] < n:
95             best = (shift_bits, n, table_size)
96     print best
97     return best
98
99
100 def gen_map_table(mapped_bounds, shift_data):
101     tbl = []
102     cur = 0
103     print mapped_bounds
104     mapped_bounds = [x >> shift_data[0] for x in mapped_bounds]
105     print mapped_bounds
106     for i in range(0, mapped_bounds[shift_data[1] - 1]):
107         while i > mapped_bounds[cur]:
108             cur += 1
109         tbl.append(cur)
110     return tbl
111
112
113 static_tables = []
114
115
116 def decl_static_table(values, type):
117     global static_tables
118     v = (type, values)
119     for i, vp in enumerate(static_tables):
120         if v == vp:
121             return i
122     print "ADD TABLE: %s %r" % (type, values)
123     r = len(static_tables)
124     static_tables.append(v)
125     return r
126
127
128 def type_for_uint_table(table):
129     mv = max(table)
130     if mv < 2**8:
131         return 'uint8_t'
132     elif mv < 2**16:
133         return 'uint16_t'
134     elif mv < 2**32:
135         return 'uint32_t'
136     else:
137         return 'uint64_t'
138
139
140 def gen_bucket_code(histogram):
141     bounds = [0, 1]
142     done_trivial = False
143     done_unmapped = False
144     first_nontrivial = None
145     first_unmapped = None
146     while len(bounds) < histogram.buckets + 1:
147         if len(bounds) == histogram.buckets:
148             nextb = int(histogram.max)
149         else:
150             mul = math.pow(
151                 float(histogram.max) / bounds[-1],
152                 1.0 / (histogram.buckets + 1 - len(bounds)))
153             nextb = int(math.ceil(bounds[-1] * mul))
154         if nextb <= bounds[-1] + 1:
155             nextb = bounds[-1] + 1
156         elif not done_trivial:
157             done_trivial = True
158             first_nontrivial = len(bounds)
159         bounds.append(nextb)
160     bounds_idx = decl_static_table(bounds, 'int')
161     if done_trivial:
162         first_nontrivial_code = dbl2u64(first_nontrivial)
163         code_bounds = [dbl2u64(x) - first_nontrivial_code for x in bounds]
164         shift_data = find_ideal_shift(code_bounds[first_nontrivial:],
165                                       256 * histogram.buckets)
166     #print first_nontrivial, shift_data, bounds
167     #if shift_data is not None: print [hex(x >> shift_data[0]) for x in code_bounds[first_nontrivial:]]
168     code = 'value = GPR_CLAMP(value, 0, %d);\n' % histogram.max
169     map_table = gen_map_table(code_bounds[first_nontrivial:], shift_data)
170     if first_nontrivial is None:
171         code += ('GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, value);\n' %
172                  histogram.name.upper())
173     else:
174         code += 'if (value < %d) {\n' % first_nontrivial
175         code += ('GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, value);\n' %
176                  histogram.name.upper())
177         code += 'return;\n'
178         code += '}'
179         first_nontrivial_code = dbl2u64(first_nontrivial)
180         if shift_data is not None:
181             map_table_idx = decl_static_table(map_table,
182                                               type_for_uint_table(map_table))
183             code += 'union { double dbl; uint64_t uint; } _val, _bkt;\n'
184             code += '_val.dbl = value;\n'
185             code += 'if (_val.uint < %dull) {\n' % (
186                 (map_table[-1] << shift_data[0]) + first_nontrivial_code)
187             code += 'int bucket = '
188             code += 'grpc_stats_table_%d[((_val.uint - %dull) >> %d)] + %d;\n' % (
189                 map_table_idx, first_nontrivial_code, shift_data[0],
190                 first_nontrivial)
191             code += '_bkt.dbl = grpc_stats_table_%d[bucket];\n' % bounds_idx
192             code += 'bucket -= (_val.uint < _bkt.uint);\n'
193             code += 'GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, bucket);\n' % histogram.name.upper(
194             )
195             code += 'return;\n'
196             code += '}\n'
197         code += 'GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, ' % histogram.name.upper(
198         )
199         code += 'grpc_stats_histo_find_bucket_slow(value, grpc_stats_table_%d, %d));\n' % (
200             bounds_idx, histogram.buckets)
201     return (code, bounds_idx)
202
203
204 # utility: print a big comment block into a set of files
205 def put_banner(files, banner):
206     for f in files:
207         print >> f, '/*'
208         for line in banner:
209             print >> f, ' * %s' % line
210         print >> f, ' */'
211         print >> f
212
213
214 with open('src/core/lib/debug/stats_data.h', 'w') as H:
215     # copy-paste copyright notice from this file
216     with open(sys.argv[0]) as my_source:
217         copyright = []
218         for line in my_source:
219             if line[0] != '#':
220                 break
221         for line in my_source:
222             if line[0] == '#':
223                 copyright.append(line)
224                 break
225         for line in my_source:
226             if line[0] != '#':
227                 break
228             copyright.append(line)
229         put_banner([H], [line[2:].rstrip() for line in copyright])
230
231     put_banner(
232         [H],
233         ["Automatically generated by tools/codegen/core/gen_stats_data.py"])
234
235     print >> H, "#ifndef GRPC_CORE_LIB_DEBUG_STATS_DATA_H"
236     print >> H, "#define GRPC_CORE_LIB_DEBUG_STATS_DATA_H"
237     print >> H
238     print >> H, "#include <grpc/support/port_platform.h>"
239     print >> H
240     print >> H, "#include <inttypes.h>"
241     print >> H, "#include \"src/core/lib/iomgr/exec_ctx.h\""
242     print >> H
243
244     for typename, instances in sorted(inst_map.items()):
245         print >> H, "typedef enum {"
246         for inst in instances:
247             print >> H, "  GRPC_STATS_%s_%s," % (typename.upper(),
248                                                  inst.name.upper())
249         print >> H, "  GRPC_STATS_%s_COUNT" % (typename.upper())
250         print >> H, "} grpc_stats_%ss;" % (typename.lower())
251         print >> H, "extern const char *grpc_stats_%s_name[GRPC_STATS_%s_COUNT];" % (
252             typename.lower(), typename.upper())
253         print >> H, "extern const char *grpc_stats_%s_doc[GRPC_STATS_%s_COUNT];" % (
254             typename.lower(), typename.upper())
255
256     histo_start = []
257     histo_buckets = []
258     histo_bucket_boundaries = []
259
260     print >> H, "typedef enum {"
261     first_slot = 0
262     for histogram in inst_map['Histogram']:
263         histo_start.append(first_slot)
264         histo_buckets.append(histogram.buckets)
265         print >> H, "  GRPC_STATS_HISTOGRAM_%s_FIRST_SLOT = %d," % (
266             histogram.name.upper(), first_slot)
267         print >> H, "  GRPC_STATS_HISTOGRAM_%s_BUCKETS = %d," % (
268             histogram.name.upper(), histogram.buckets)
269         first_slot += histogram.buckets
270     print >> H, "  GRPC_STATS_HISTOGRAM_BUCKETS = %d" % first_slot
271     print >> H, "} grpc_stats_histogram_constants;"
272
273     print >> H, "#if defined(GRPC_COLLECT_STATS) || !defined(NDEBUG)"
274     for ctr in inst_map['Counter']:
275         print >> H, ("#define GRPC_STATS_INC_%s() " +
276                      "GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_%s)") % (
277                          ctr.name.upper(), ctr.name.upper())
278     for histogram in inst_map['Histogram']:
279         print >> H, "#define GRPC_STATS_INC_%s(value) grpc_stats_inc_%s( (int)(value))" % (
280             histogram.name.upper(), histogram.name.lower())
281         print >> H, "void grpc_stats_inc_%s(int x);" % histogram.name.lower()
282
283     print >> H, "#else"
284     for ctr in inst_map['Counter']:
285         print >> H, ("#define GRPC_STATS_INC_%s() ") % (ctr.name.upper())
286     for histogram in inst_map['Histogram']:
287         print >> H, "#define GRPC_STATS_INC_%s(value)" % (
288             histogram.name.upper())
289     print >> H, "#endif /* defined(GRPC_COLLECT_STATS) || !defined(NDEBUG) */"
290
291     for i, tbl in enumerate(static_tables):
292         print >> H, "extern const %s grpc_stats_table_%d[%d];" % (tbl[0], i,
293                                                                   len(tbl[1]))
294
295     print >> H, "extern const int grpc_stats_histo_buckets[%d];" % len(
296         inst_map['Histogram'])
297     print >> H, "extern const int grpc_stats_histo_start[%d];" % len(
298         inst_map['Histogram'])
299     print >> H, "extern const int *const grpc_stats_histo_bucket_boundaries[%d];" % len(
300         inst_map['Histogram'])
301     print >> H, "extern void (*const grpc_stats_inc_histogram[%d])(int x);" % len(
302         inst_map['Histogram'])
303
304     print >> H
305     print >> H, "#endif /* GRPC_CORE_LIB_DEBUG_STATS_DATA_H */"
306
307 with open('src/core/lib/debug/stats_data.cc', 'w') as C:
308     # copy-paste copyright notice from this file
309     with open(sys.argv[0]) as my_source:
310         copyright = []
311         for line in my_source:
312             if line[0] != '#':
313                 break
314         for line in my_source:
315             if line[0] == '#':
316                 copyright.append(line)
317                 break
318         for line in my_source:
319             if line[0] != '#':
320                 break
321             copyright.append(line)
322         put_banner([C], [line[2:].rstrip() for line in copyright])
323
324     put_banner(
325         [C],
326         ["Automatically generated by tools/codegen/core/gen_stats_data.py"])
327
328     print >> C, "#include <grpc/support/port_platform.h>"
329     print >> C
330     print >> C, "#include \"src/core/lib/debug/stats.h\""
331     print >> C, "#include \"src/core/lib/debug/stats_data.h\""
332     print >> C, "#include \"src/core/lib/gpr/useful.h\""
333     print >> C, "#include \"src/core/lib/iomgr/exec_ctx.h\""
334     print >> C
335
336     histo_code = []
337     for histogram in inst_map['Histogram']:
338         code, bounds_idx = gen_bucket_code(histogram)
339         histo_bucket_boundaries.append(bounds_idx)
340         histo_code.append(code)
341
342     for typename, instances in sorted(inst_map.items()):
343         print >> C, "const char *grpc_stats_%s_name[GRPC_STATS_%s_COUNT] = {" % (
344             typename.lower(), typename.upper())
345         for inst in instances:
346             print >> C, "  %s," % c_str(inst.name)
347         print >> C, "};"
348         print >> C, "const char *grpc_stats_%s_doc[GRPC_STATS_%s_COUNT] = {" % (
349             typename.lower(), typename.upper())
350         for inst in instances:
351             print >> C, "  %s," % c_str(inst.doc)
352         print >> C, "};"
353
354     for i, tbl in enumerate(static_tables):
355         print >> C, "const %s grpc_stats_table_%d[%d] = {%s};" % (
356             tbl[0], i, len(tbl[1]), ','.join('%s' % x for x in tbl[1]))
357
358     for histogram, code in zip(inst_map['Histogram'], histo_code):
359         print >> C, ("void grpc_stats_inc_%s(int value) {%s}") % (
360             histogram.name.lower(), code)
361
362     print >> C, "const int grpc_stats_histo_buckets[%d] = {%s};" % (len(
363         inst_map['Histogram']), ','.join('%s' % x for x in histo_buckets))
364     print >> C, "const int grpc_stats_histo_start[%d] = {%s};" % (len(
365         inst_map['Histogram']), ','.join('%s' % x for x in histo_start))
366     print >> C, "const int *const grpc_stats_histo_bucket_boundaries[%d] = {%s};" % (
367         len(inst_map['Histogram']), ','.join(
368             'grpc_stats_table_%d' % x for x in histo_bucket_boundaries))
369     print >> C, "void (*const grpc_stats_inc_histogram[%d])(int x) = {%s};" % (
370         len(inst_map['Histogram']), ','.join(
371             'grpc_stats_inc_%s' % histogram.name.lower()
372             for histogram in inst_map['Histogram']))
373
374 # patch qps_test bigquery schema
375 RECORD_EXPLICIT_PERCENTILES = [50, 95, 99]
376
377 with open('tools/run_tests/performance/scenario_result_schema.json', 'r') as f:
378     qps_schema = json.loads(f.read())
379
380
381 def FindNamed(js, name):
382     for el in js:
383         if el['name'] == name:
384             return el
385
386
387 def RemoveCoreFields(js):
388     new_fields = []
389     for field in js['fields']:
390         if not field['name'].startswith('core_'):
391             new_fields.append(field)
392     js['fields'] = new_fields
393
394
395 RemoveCoreFields(FindNamed(qps_schema, 'clientStats'))
396 RemoveCoreFields(FindNamed(qps_schema, 'serverStats'))
397
398
399 def AddCoreFields(js):
400     for counter in inst_map['Counter']:
401         js['fields'].append({
402             'name': 'core_%s' % counter.name,
403             'type': 'INTEGER',
404             'mode': 'NULLABLE'
405         })
406     for histogram in inst_map['Histogram']:
407         js['fields'].append({
408             'name': 'core_%s' % histogram.name,
409             'type': 'STRING',
410             'mode': 'NULLABLE'
411         })
412         js['fields'].append({
413             'name': 'core_%s_bkts' % histogram.name,
414             'type': 'STRING',
415             'mode': 'NULLABLE'
416         })
417         for pctl in RECORD_EXPLICIT_PERCENTILES:
418             js['fields'].append({
419                 'name': 'core_%s_%dp' % (histogram.name, pctl),
420                 'type': 'FLOAT',
421                 'mode': 'NULLABLE'
422             })
423
424
425 AddCoreFields(FindNamed(qps_schema, 'clientStats'))
426 AddCoreFields(FindNamed(qps_schema, 'serverStats'))
427
428 with open('tools/run_tests/performance/scenario_result_schema.json', 'w') as f:
429     f.write(json.dumps(qps_schema, indent=2, sort_keys=True))
430
431 # and generate a helper script to massage scenario results into the format we'd
432 # like to query
433 with open('tools/run_tests/performance/massage_qps_stats.py', 'w') as P:
434     with open(sys.argv[0]) as my_source:
435         for line in my_source:
436             if line[0] != '#':
437                 break
438         for line in my_source:
439             if line[0] == '#':
440                 print >> P, line.rstrip()
441                 break
442         for line in my_source:
443             if line[0] != '#':
444                 break
445             print >> P, line.rstrip()
446
447     print >> P
448     print >> P, '# Autogenerated by tools/codegen/core/gen_stats_data.py'
449     print >> P
450
451     print >> P, 'import massage_qps_stats_helpers'
452
453     print >> P, 'def massage_qps_stats(scenario_result):'
454     print >> P, '  for stats in scenario_result["serverStats"] + scenario_result["clientStats"]:'
455     print >> P, '    if "coreStats" in stats:'
456     print >> P, '      # Get rid of the "coreStats" element and replace it by statistics'
457     print >> P, '      # that correspond to columns in the bigquery schema.'
458     print >> P, '      core_stats = stats["coreStats"]'
459     print >> P, '      del stats["coreStats"]'
460     for counter in inst_map['Counter']:
461         print >> P, '      stats["core_%s"] = massage_qps_stats_helpers.counter(core_stats, "%s")' % (
462             counter.name, counter.name)
463     for i, histogram in enumerate(inst_map['Histogram']):
464         print >> P, '      h = massage_qps_stats_helpers.histogram(core_stats, "%s")' % histogram.name
465         print >> P, '      stats["core_%s"] = ",".join("%%f" %% x for x in h.buckets)' % histogram.name
466         print >> P, '      stats["core_%s_bkts"] = ",".join("%%f" %% x for x in h.boundaries)' % histogram.name
467         for pctl in RECORD_EXPLICIT_PERCENTILES:
468             print >> P, '      stats["core_%s_%dp"] = massage_qps_stats_helpers.percentile(h.buckets, %d, h.boundaries)' % (
469                 histogram.name, pctl, pctl)
470
471 with open('src/core/lib/debug/stats_data_bq_schema.sql', 'w') as S:
472     columns = []
473     for counter in inst_map['Counter']:
474         columns.append(('%s_per_iteration' % counter.name, 'FLOAT'))
475     print >> S, ',\n'.join('%s:%s' % x for x in columns)