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


grpc
Author(s):
autogenerated on Thu Mar 13 2025 02:59:23