blob: de41f434a190b059245fbea0014901baef538596 [file]
#!/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())
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 <grpc/support/port_platform.h>", file=H)
print("#include <atomic>", file=H)
print("#include <memory>", file=H)
print("#include <stdint.h>", 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<uint64_t> 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<int>(Counter::COUNT)];"
),
file=H,
)
print(
(
" static const absl::string_view"
" histogram_name[static_cast<int>(Histogram::COUNT)];"
),
file=H,
)
print(
(
" static const absl::string_view"
" counter_doc[static_cast<int>(Counter::COUNT)];"
),
file=H,
)
print(
(
" static const absl::string_view"
" histogram_doc[static_cast<int>(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<int>(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<GlobalStats> Diff(const GlobalStats& other) const;",
file=H,
)
print("};", file=H)
print("class GlobalStatsCollector {", file=H)
print(" public:", file=H)
print(" std::unique_ptr<GlobalStats> 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<uint64_t> %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>"
" 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 <grpc/support/port_platform.h>", file=C)
print(file=C)
print('#include "src/core/lib/debug/stats_data.h"', file=C)
print("#include <stdint.h>", 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<int>(%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<int>(%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<GlobalStats> GlobalStatsCollector::Collect() const {",
file=C,
)
print(" auto result = std::make_unique<GlobalStats>();", 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> GlobalStats::Diff(const GlobalStats&"
" other) const {"
),
file=C,
)
print(" auto result = std::make_unique<GlobalStats>();", 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)