#!/usr/bin/env python3 # Copyright 2017 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function import collections import ctypes import json import math import sys import yaml with open('src/core/lib/debug/stats_data.yaml') as f: attrs = yaml.safe_load(f.read(), Loader=yaml.Loader) REQUIRED_FIELDS = ['name', 'doc'] def make_type(name, fields): return (collections.namedtuple( name, ' '.join(list(set(REQUIRED_FIELDS + fields)))), []) def c_str(s, encoding='ascii'): if isinstance(s, str): s = s.encode(encoding) result = '' for c in s: c = chr(c) if isinstance(c, int) else c if not (32 <= ord(c) < 127) or c in ('\\', '"'): result += '\\%03o' % ord(c) else: result += c return '"' + result + '"' types = ( make_type('Counter', []), make_type('Histogram', ['max', 'buckets']), ) Shape = collections.namedtuple('Shape', 'max buckets') inst_map = dict((t[0].__name__, t[1]) for t in types) stats = [] for attr in attrs: found = False for t, lst in types: t_name = t.__name__.lower() if t_name in attr: name = attr[t_name] del attr[t_name] lst.append(t(name=name, **attr)) found = True break assert found, "Bad decl: %s" % attr def dbl2u64(d): return ctypes.c_ulonglong.from_buffer(ctypes.c_double(d)).value def u642dbl(d): return ctypes.c_double.from_buffer(ctypes.c_ulonglong(d)).value def shift_works_until(mapped_bounds, shift_bits): for i, ab in enumerate(zip(mapped_bounds, mapped_bounds[1:])): a, b = ab if (a >> shift_bits) == (b >> shift_bits): return i return len(mapped_bounds) def find_ideal_shift(mapped_bounds, max_size): best = None for shift_bits in reversed(list(range(0, 64))): n = shift_works_until(mapped_bounds, shift_bits) if n == 0: continue table_size = mapped_bounds[n - 1] >> shift_bits if table_size > max_size: continue if best is None: best = (shift_bits, n, table_size) elif best[1] < n: best = (shift_bits, n, table_size) return best def gen_map_table(mapped_bounds, shift_data): #print("gen_map_table(%s, %s)" % (mapped_bounds, shift_data)) tbl = [] cur = 0 mapped_bounds = [x >> shift_data[0] for x in mapped_bounds] for i in range(0, mapped_bounds[shift_data[1] - 1]): while i > mapped_bounds[cur]: cur += 1 tbl.append(cur) return tbl static_tables = [] def decl_static_table(values, type): global static_tables v = (type, values) for i, vp in enumerate(static_tables): if v == vp: return i r = len(static_tables) static_tables.append(v) return r def type_for_uint_table(table): mv = max(table) if mv < 2**8: return 'uint8_t' elif mv < 2**16: return 'uint16_t' elif mv < 2**32: return 'uint32_t' else: return 'uint64_t' def merge_cases(cases): l = len(cases) if l == 1: return cases[0][1] left_len = l // 2 left = cases[0:left_len] right = cases[left_len:] return 'if (value < %d) {\n%s\n} else {\n%s\n}' % ( left[-1][0], merge_cases(left), merge_cases(right)) def gen_bucket_code(shape): bounds = [0, 1] done_trivial = False done_unmapped = False first_nontrivial = None first_unmapped = None while len(bounds) < shape.buckets + 1: if len(bounds) == shape.buckets: nextb = int(shape.max) else: mul = math.pow( float(shape.max) / bounds[-1], 1.0 / (shape.buckets + 1 - len(bounds))) nextb = int(math.ceil(bounds[-1] * mul)) if nextb <= bounds[-1] + 1: nextb = bounds[-1] + 1 elif not done_trivial: done_trivial = True first_nontrivial = len(bounds) bounds.append(nextb) bounds_idx = decl_static_table(bounds, 'int') #print first_nontrivial, shift_data, bounds #if shift_data is not None: print [hex(x >> shift_data[0]) for x in code_bounds[first_nontrivial:]] if first_nontrivial is None: return ('return grpc_core::Clamp(value, 0, %d);\n' % shape.max, bounds_idx) cases = [(0, 'return 0;'), (first_nontrivial, 'return value;')] if done_trivial: first_nontrivial_code = dbl2u64(first_nontrivial) last_code = first_nontrivial_code while True: code = '' first_nontrivial = u642dbl(first_nontrivial_code) code_bounds_index = None for i, b in enumerate(bounds): if b > first_nontrivial: code_bounds_index = i break code_bounds = [dbl2u64(x) - first_nontrivial_code for x in bounds] shift_data = find_ideal_shift(code_bounds[code_bounds_index:], 65536) if not shift_data: break map_table = gen_map_table(code_bounds[code_bounds_index:], shift_data) if not map_table: break if map_table[-1] < 5: break map_table_idx = decl_static_table( [x + code_bounds_index for x in map_table], type_for_uint_table(map_table)) last_code = ( (len(map_table) - 1) << shift_data[0]) + first_nontrivial_code code += 'DblUint val;\n' code += 'val.dbl = value;\n' code += 'const int bucket = ' code += 'kStatsTable%d[((val.uint - %dull) >> %d)];\n' % ( map_table_idx, first_nontrivial_code, shift_data[0]) code += 'return bucket - (value < kStatsTable%d[bucket]);' % bounds_idx cases.append((int(u642dbl(last_code)) + 1, code)) first_nontrivial_code = last_code last = u642dbl(last_code) + 1 for i, b in enumerate(bounds[:-2]): if bounds[i + 1] < last: continue cases.append((bounds[i + 1], 'return %d;' % i)) cases.append((None, 'return %d;' % (len(bounds) - 2))) return (merge_cases(cases), bounds_idx) # utility: print a big comment block into a set of files def put_banner(files, banner): for f in files: for line in banner: print('// %s' % line, file=f) print(file=f) shapes = set() for histogram in inst_map['Histogram']: shapes.add(Shape(max=histogram.max, buckets=histogram.buckets)) def snake_to_pascal(name): return ''.join([x.capitalize() for x in name.split('_')]) with open('src/core/lib/debug/stats_data.h', 'w') as H: # copy-paste copyright notice from this file with open(sys.argv[0]) as my_source: copyright = [] for line in my_source: if line[0] != '#': break for line in my_source: if line[0] == '#': copyright.append(line) break for line in my_source: if line[0] != '#': break copyright.append(line) put_banner([H], [line[2:].rstrip() for line in copyright]) put_banner( [H], ["Automatically generated by tools/codegen/core/gen_stats_data.py"]) print("#ifndef GRPC_SRC_CORE_LIB_DEBUG_STATS_DATA_H", file=H) print("#define GRPC_SRC_CORE_LIB_DEBUG_STATS_DATA_H", file=H) print(file=H) print("#include ", file=H) print("#include ", file=H) print("#include ", file=H) print("#include ", file=H) print("#include \"src/core/lib/debug/histogram_view.h\"", file=H) print("#include \"absl/strings/string_view.h\"", file=H) print("#include \"src/core/lib/gprpp/per_cpu.h\"", file=H) print(file=H) print("namespace grpc_core {", file=H) for shape in shapes: print("class HistogramCollector_%d_%d;" % (shape.max, shape.buckets), file=H) print("class Histogram_%d_%d {" % (shape.max, shape.buckets), file=H) print(" public:", file=H) print(" static int BucketFor(int value);", file=H) print(" const uint64_t* buckets() const { return buckets_; }", file=H) print( " friend Histogram_%d_%d operator-(const Histogram_%d_%d& left, const Histogram_%d_%d& right);" % (shape.max, shape.buckets, shape.max, shape.buckets, shape.max, shape.buckets), file=H) print(" private:", file=H) print(" friend class HistogramCollector_%d_%d;" % (shape.max, shape.buckets), file=H) print(" uint64_t buckets_[%d]{};" % shape.buckets, file=H) print("};", file=H) print("class HistogramCollector_%d_%d {" % (shape.max, shape.buckets), file=H) print(" public:", file=H) print(" void Increment(int value) {", file=H) print(" buckets_[Histogram_%d_%d::BucketFor(value)]" % (shape.max, shape.buckets), file=H) print(" .fetch_add(1, std::memory_order_relaxed);", file=H) print(" }", file=H) print(" void Collect(Histogram_%d_%d* result) const;" % (shape.max, shape.buckets), file=H) print(" private:", file=H) print(" std::atomic buckets_[%d]{};" % shape.buckets, file=H) print("};", file=H) print("struct GlobalStats {", file=H) print(" enum class Counter {", file=H) for ctr in inst_map['Counter']: print(" k%s," % snake_to_pascal(ctr.name), file=H) print(" COUNT", file=H) print(" };", file=H) print(" enum class Histogram {", file=H) for ctr in inst_map['Histogram']: print(" k%s," % snake_to_pascal(ctr.name), file=H) print(" COUNT", file=H) print(" };", file=H) print(" GlobalStats();", file=H) print( " static const absl::string_view counter_name[static_cast(Counter::COUNT)];", file=H) print( " static const absl::string_view histogram_name[static_cast(Histogram::COUNT)];", file=H) print( " static const absl::string_view counter_doc[static_cast(Counter::COUNT)];", file=H) print( " static const absl::string_view histogram_doc[static_cast(Histogram::COUNT)];", file=H) print(" union {", file=H) print(" struct {", file=H) for ctr in inst_map['Counter']: print(" uint64_t %s;" % ctr.name, file=H) print(" };", file=H) print(" uint64_t counters[static_cast(Counter::COUNT)];", file=H) print(" };", file=H) for ctr in inst_map['Histogram']: print(" Histogram_%d_%d %s;" % (ctr.max, ctr.buckets, ctr.name), file=H) print(" HistogramView histogram(Histogram which) const;", file=H) print( " std::unique_ptr Diff(const GlobalStats& other) const;", file=H) print("};", file=H) print("class GlobalStatsCollector {", file=H) print(" public:", file=H) print(" std::unique_ptr Collect() const;", file=H) for ctr in inst_map['Counter']: print( " void Increment%s() { data_.this_cpu().%s.fetch_add(1, std::memory_order_relaxed); }" % (snake_to_pascal(ctr.name), ctr.name), file=H) for ctr in inst_map['Histogram']: print( " void Increment%s(int value) { data_.this_cpu().%s.Increment(value); }" % (snake_to_pascal(ctr.name), ctr.name), file=H) print(" private:", file=H) print(" struct Data {", file=H) for ctr in inst_map['Counter']: print(" std::atomic %s{0};" % ctr.name, file=H) for ctr in inst_map['Histogram']: print(" HistogramCollector_%d_%d %s;" % (ctr.max, ctr.buckets, ctr.name), file=H) print(" };", file=H) print( " PerCpu data_{PerCpuOptions().SetCpusPerShard(4).SetMaxShards(32)};", file=H) print("};", file=H) print("}", file=H) print(file=H) print("#endif // GRPC_SRC_CORE_LIB_DEBUG_STATS_DATA_H", file=H) with open('src/core/lib/debug/stats_data.cc', 'w') as C: # copy-paste copyright notice from this file with open(sys.argv[0]) as my_source: copyright = [] for line in my_source: if line[0] != '#': break for line in my_source: if line[0] == '#': copyright.append(line) break for line in my_source: if line[0] != '#': break copyright.append(line) put_banner([C], [line[2:].rstrip() for line in copyright]) put_banner( [C], ["Automatically generated by tools/codegen/core/gen_stats_data.py"]) print("#include ", file=C) print(file=C) print("#include \"src/core/lib/debug/stats_data.h\"", file=C) print("#include ", file=C) print(file=C) histo_code = [] histo_bucket_boundaries = {} for shape in shapes: code, bounds_idx = gen_bucket_code(shape) histo_bucket_boundaries[shape] = bounds_idx histo_code.append(code) print("namespace grpc_core {", file=C) print("namespace { union DblUint { double dbl; uint64_t uint; }; }", file=C) for shape in shapes: print( "void HistogramCollector_%d_%d::Collect(Histogram_%d_%d* result) const {" % (shape.max, shape.buckets, shape.max, shape.buckets), file=C) print(" for (int i=0; i<%d; i++) {" % shape.buckets, file=C) print( " result->buckets_[i] += buckets_[i].load(std::memory_order_relaxed);", file=C) print(" }", file=C) print("}", file=C) print( "Histogram_%d_%d operator-(const Histogram_%d_%d& left, const Histogram_%d_%d& right) {" % (shape.max, shape.buckets, shape.max, shape.buckets, shape.max, shape.buckets), file=C) print(" Histogram_%d_%d result;" % (shape.max, shape.buckets), file=C) print(" for (int i=0; i<%d; i++) {" % shape.buckets, file=C) print(" result.buckets_[i] = left.buckets_[i] - right.buckets_[i];", file=C) print(" }", file=C) print(" return result;", file=C) print("}", file=C) for typename, instances in sorted(inst_map.items()): print( "const absl::string_view GlobalStats::%s_name[static_cast(%s::COUNT)] = {" % (typename.lower(), typename), file=C) for inst in instances: print(" %s," % c_str(inst.name), file=C) print("};", file=C) print( "const absl::string_view GlobalStats::%s_doc[static_cast(%s::COUNT)] = {" % (typename.lower(), typename), file=C) for inst in instances: print(" %s," % c_str(inst.doc), file=C) print("};", file=C) print("namespace {", file=C) for i, tbl in enumerate(static_tables): print("const %s kStatsTable%d[%d] = {%s};" % (tbl[0], i, len(tbl[1]), ','.join('%s' % x for x in tbl[1])), file=C) print("} // namespace", file=C) for shape, code in zip(shapes, histo_code): print(("int Histogram_%d_%d::BucketFor(int value) {%s}") % (shape.max, shape.buckets, code), file=C) print("GlobalStats::GlobalStats() : %s {}" % ",".join("%s{0}" % ctr.name for ctr in inst_map['Counter']), file=C) print("HistogramView GlobalStats::histogram(Histogram which) const {", file=C) print(" switch (which) {", file=C) print(" default: GPR_UNREACHABLE_CODE(return HistogramView());", file=C) for inst in inst_map['Histogram']: print(" case Histogram::k%s:" % snake_to_pascal(inst.name), file=C) print( " return HistogramView{&Histogram_%d_%d::BucketFor, kStatsTable%d, %d, %s.buckets()};" % (inst.max, inst.buckets, histo_bucket_boundaries[Shape( inst.max, inst.buckets)], inst.buckets, inst.name), file=C) print(" }", file=C) print("}", file=C) print( "std::unique_ptr GlobalStatsCollector::Collect() const {", file=C) print(" auto result = std::make_unique();", file=C) print(" for (const auto& data : data_) {", file=C) for ctr in inst_map['Counter']: print(" result->%s += data.%s.load(std::memory_order_relaxed);" % (ctr.name, ctr.name), file=C) for h in inst_map['Histogram']: print(" data.%s.Collect(&result->%s);" % (h.name, h.name), file=C) print(" }", file=C) print(" return result;", file=C) print("}", file=C) print( "std::unique_ptr GlobalStats::Diff(const GlobalStats& other) const {", file=C) print(" auto result = std::make_unique();", file=C) for ctr in inst_map['Counter']: print(" result->%s = %s - other.%s;" % (ctr.name, ctr.name, ctr.name), file=C) for h in inst_map['Histogram']: print(" result->%s = %s - other.%s;" % (h.name, h.name, h.name), file=C) print(" return result;", file=C) print("}", file=C) print("}", file=C)