1 #!/usr/bin/env python2.7
3 # Copyright 2017 gRPC authors.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
24 with open('src/core/lib/debug/stats_data.yaml') as f:
25 attrs = yaml.load(f.read())
27 REQUIRED_FIELDS = ['name', 'doc']
30 def make_type(name, fields):
31 return (collections.namedtuple(
32 name, ' '.join(list(set(REQUIRED_FIELDS + fields)))), [])
35 def c_str(s, encoding='ascii'):
36 if isinstance(s, unicode):
37 s = s.encode(encoding)
40 if not (32 <= ord(c) < 127) or c in ('\\', '"'):
41 result += '\\%03o' % ord(c)
44 return '"' + result + '"'
48 make_type('Counter', []),
49 make_type('Histogram', ['max', 'buckets']),
52 inst_map = dict((t[0].__name__, t[1]) for t in types)
59 t_name = t.__name__.lower()
63 lst.append(t(name=name, **attr))
66 assert found, "Bad decl: %s" % attr
70 return ctypes.c_ulonglong.from_buffer(ctypes.c_double(d)).value
73 def shift_works_until(mapped_bounds, shift_bits):
74 for i, ab in enumerate(zip(mapped_bounds, mapped_bounds[1:])):
76 if (a >> shift_bits) == (b >> shift_bits):
78 return len(mapped_bounds)
81 def find_ideal_shift(mapped_bounds, max_size):
83 for shift_bits in reversed(range(0, 64)):
84 n = shift_works_until(mapped_bounds, shift_bits)
87 table_size = mapped_bounds[n - 1] >> shift_bits
88 if table_size > max_size:
90 if table_size > 65535:
93 best = (shift_bits, n, table_size)
95 best = (shift_bits, n, table_size)
100 def gen_map_table(mapped_bounds, shift_data):
104 mapped_bounds = [x >> shift_data[0] for x in mapped_bounds]
106 for i in range(0, mapped_bounds[shift_data[1] - 1]):
107 while i > mapped_bounds[cur]:
116 def decl_static_table(values, type):
119 for i, vp in enumerate(static_tables):
122 print "ADD TABLE: %s %r" % (type, values)
123 r = len(static_tables)
124 static_tables.append(v)
128 def type_for_uint_table(table):
140 def gen_bucket_code(histogram):
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)
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:
158 first_nontrivial = len(bounds)
160 bounds_idx = decl_static_table(bounds, 'int')
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())
174 code += 'if (value < %d) {\n' % first_nontrivial
175 code += ('GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, value);\n' %
176 histogram.name.upper())
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],
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(
197 code += 'GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, ' % histogram.name.upper(
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)
204 # utility: print a big comment block into a set of files
205 def put_banner(files, banner):
209 print >> f, ' * %s' % line
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:
218 for line in my_source:
221 for line in my_source:
223 copyright.append(line)
225 for line in my_source:
228 copyright.append(line)
229 put_banner([H], [line[2:].rstrip() for line in copyright])
233 ["Automatically generated by tools/codegen/core/gen_stats_data.py"])
235 print >> H, "#ifndef GRPC_CORE_LIB_DEBUG_STATS_DATA_H"
236 print >> H, "#define GRPC_CORE_LIB_DEBUG_STATS_DATA_H"
238 print >> H, "#include <grpc/support/port_platform.h>"
240 print >> H, "#include <inttypes.h>"
241 print >> H, "#include \"src/core/lib/iomgr/exec_ctx.h\""
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(),
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())
258 histo_bucket_boundaries = []
260 print >> H, "typedef enum {"
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;"
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()
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) */"
291 for i, tbl in enumerate(static_tables):
292 print >> H, "extern const %s grpc_stats_table_%d[%d];" % (tbl[0], i,
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'])
305 print >> H, "#endif /* GRPC_CORE_LIB_DEBUG_STATS_DATA_H */"
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:
311 for line in my_source:
314 for line in my_source:
316 copyright.append(line)
318 for line in my_source:
321 copyright.append(line)
322 put_banner([C], [line[2:].rstrip() for line in copyright])
326 ["Automatically generated by tools/codegen/core/gen_stats_data.py"])
328 print >> C, "#include <grpc/support/port_platform.h>"
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\""
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)
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)
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)
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]))
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)
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']))
374 # patch qps_test bigquery schema
375 RECORD_EXPLICIT_PERCENTILES = [50, 95, 99]
377 with open('tools/run_tests/performance/scenario_result_schema.json', 'r') as f:
378 qps_schema = json.loads(f.read())
381 def FindNamed(js, name):
383 if el['name'] == name:
387 def RemoveCoreFields(js):
389 for field in js['fields']:
390 if not field['name'].startswith('core_'):
391 new_fields.append(field)
392 js['fields'] = new_fields
395 RemoveCoreFields(FindNamed(qps_schema, 'clientStats'))
396 RemoveCoreFields(FindNamed(qps_schema, 'serverStats'))
399 def AddCoreFields(js):
400 for counter in inst_map['Counter']:
401 js['fields'].append({
402 'name': 'core_%s' % counter.name,
406 for histogram in inst_map['Histogram']:
407 js['fields'].append({
408 'name': 'core_%s' % histogram.name,
412 js['fields'].append({
413 'name': 'core_%s_bkts' % histogram.name,
417 for pctl in RECORD_EXPLICIT_PERCENTILES:
418 js['fields'].append({
419 'name': 'core_%s_%dp' % (histogram.name, pctl),
425 AddCoreFields(FindNamed(qps_schema, 'clientStats'))
426 AddCoreFields(FindNamed(qps_schema, 'serverStats'))
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))
431 # and generate a helper script to massage scenario results into the format we'd
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:
438 for line in my_source:
440 print >> P, line.rstrip()
442 for line in my_source:
445 print >> P, line.rstrip()
448 print >> P, '# Autogenerated by tools/codegen/core/gen_stats_data.py'
451 print >> P, 'import massage_qps_stats_helpers'
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)
471 with open('src/core/lib/debug/stats_data_bq_schema.sql', 'w') as S:
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)