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 17from __future__ import print_function 18 19import collections 20import ctypes 21import json 22import math 23import sys 24 25import yaml 26 27with open("src/core/telemetry/stats_data.yaml") as f: 28 attrs = yaml.safe_load(f.read()) 29 30REQUIRED_FIELDS = ["name", "doc"] 31 32 33def make_type(name, fields): 34 return ( 35 collections.namedtuple( 36 name, " ".join(list(set(REQUIRED_FIELDS + fields))) 37 ), 38 [], 39 ) 40 41 42def c_str(s, encoding="ascii"): 43 if isinstance(s, str): 44 s = s.encode(encoding) 45 result = "" 46 for c in s: 47 c = chr(c) if isinstance(c, int) else c 48 if not (32 <= ord(c) < 127) or c in ("\\", '"'): 49 result += "\\%03o" % ord(c) 50 else: 51 result += c 52 return '"' + result + '"' 53 54 55types = ( 56 make_type("Counter", []), 57 make_type("Histogram", ["max", "buckets"]), 58) 59Shape = collections.namedtuple("Shape", "max buckets") 60 61inst_map = dict((t[0].__name__, t[1]) for t in types) 62 63stats = [] 64 65for attr in attrs: 66 found = False 67 for t, lst in types: 68 t_name = t.__name__.lower() 69 if t_name in attr: 70 name = attr[t_name] 71 del attr[t_name] 72 lst.append(t(name=name, **attr)) 73 found = True 74 break 75 assert found, "Bad decl: %s" % attr 76 77 78def dbl2u64(d): 79 return ctypes.c_ulonglong.from_buffer(ctypes.c_double(d)).value 80 81 82def u642dbl(d): 83 return ctypes.c_double.from_buffer(ctypes.c_ulonglong(d)).value 84 85 86def shift_works_until(mapped_bounds, shift_bits): 87 for i, ab in enumerate(zip(mapped_bounds, mapped_bounds[1:])): 88 a, b = ab 89 if (a >> shift_bits) == (b >> shift_bits): 90 return i 91 return len(mapped_bounds) 92 93 94def find_ideal_shift(mapped_bounds, max_size): 95 best = None 96 for shift_bits in reversed(list(range(0, 64))): 97 n = shift_works_until(mapped_bounds, shift_bits) 98 if n == 0: 99 continue 100 table_size = mapped_bounds[n - 1] >> shift_bits 101 if table_size > max_size: 102 continue 103 if best is None: 104 best = (shift_bits, n, table_size) 105 elif best[1] < n: 106 best = (shift_bits, n, table_size) 107 return best 108 109 110def gen_map_table(mapped_bounds, shift_data): 111 # print("gen_map_table(%s, %s)" % (mapped_bounds, shift_data)) 112 tbl = [] 113 cur = 0 114 mapped_bounds = [x >> shift_data[0] for x in mapped_bounds] 115 for i in range(0, mapped_bounds[shift_data[1] - 1]): 116 while i > mapped_bounds[cur]: 117 cur += 1 118 tbl.append(cur) 119 return tbl 120 121 122static_tables = [] 123 124 125def decl_static_table(values, type): 126 global static_tables 127 v = (type, values) 128 for i, vp in enumerate(static_tables): 129 if v == vp: 130 return i 131 r = len(static_tables) 132 static_tables.append(v) 133 return r 134 135 136def type_for_uint_table(table): 137 mv = max(table) 138 if mv < 2**8: 139 return "uint8_t" 140 elif mv < 2**16: 141 return "uint16_t" 142 elif mv < 2**32: 143 return "uint32_t" 144 else: 145 return "uint64_t" 146 147 148def merge_cases(cases): 149 l = len(cases) 150 if l == 1: 151 return cases[0][1] 152 left_len = l // 2 153 left = cases[0:left_len] 154 right = cases[left_len:] 155 return "if (value < %d) {\n%s\n} else {\n%s\n}" % ( 156 left[-1][0], 157 merge_cases(left), 158 merge_cases(right), 159 ) 160 161 162def gen_bucket_code(shape): 163 bounds = [0, 1] 164 done_trivial = False 165 done_unmapped = False 166 first_nontrivial = None 167 first_unmapped = None 168 while len(bounds) < shape.buckets + 1: 169 if len(bounds) == shape.buckets: 170 nextb = int(shape.max) 171 else: 172 mul = math.pow( 173 float(shape.max) / bounds[-1], 174 1.0 / (shape.buckets + 1 - len(bounds)), 175 ) 176 nextb = int(math.ceil(bounds[-1] * mul)) 177 if nextb <= bounds[-1] + 1: 178 nextb = bounds[-1] + 1 179 elif not done_trivial: 180 done_trivial = True 181 first_nontrivial = len(bounds) 182 bounds.append(nextb) 183 bounds_idx = decl_static_table(bounds, "int") 184 # print first_nontrivial, shift_data, bounds 185 # if shift_data is not None: print [hex(x >> shift_data[0]) for x in code_bounds[first_nontrivial:]] 186 if first_nontrivial is None: 187 return ( 188 "return grpc_core::Clamp(value, 0, %d);\n" % shape.max, 189 bounds_idx, 190 ) 191 cases = [(0, "return 0;"), (first_nontrivial, "return value;")] 192 if done_trivial: 193 first_nontrivial_code = dbl2u64(first_nontrivial) 194 last_code = first_nontrivial_code 195 while True: 196 code = "" 197 first_nontrivial = u642dbl(first_nontrivial_code) 198 code_bounds_index = None 199 for i, b in enumerate(bounds): 200 if b > first_nontrivial: 201 code_bounds_index = i 202 break 203 code_bounds = [dbl2u64(x) - first_nontrivial_code for x in bounds] 204 shift_data = find_ideal_shift( 205 code_bounds[code_bounds_index:], 65536 206 ) 207 if not shift_data: 208 break 209 map_table = gen_map_table( 210 code_bounds[code_bounds_index:], shift_data 211 ) 212 if not map_table: 213 break 214 if map_table[-1] < 5: 215 break 216 map_table_idx = decl_static_table( 217 [x + code_bounds_index for x in map_table], 218 type_for_uint_table(map_table), 219 ) 220 last_code = ( 221 (len(map_table) - 1) << shift_data[0] 222 ) + first_nontrivial_code 223 code += "DblUint val;\n" 224 code += "val.dbl = value;\n" 225 code += "const int bucket = " 226 code += "kStatsTable%d[((val.uint - %dull) >> %d)];\n" % ( 227 map_table_idx, 228 first_nontrivial_code, 229 shift_data[0], 230 ) 231 code += ( 232 "return bucket - (value < kStatsTable%d[bucket]);" % bounds_idx 233 ) 234 cases.append((int(u642dbl(last_code)) + 1, code)) 235 first_nontrivial_code = last_code 236 last = u642dbl(last_code) + 1 237 for i, b in enumerate(bounds[:-2]): 238 if bounds[i + 1] < last: 239 continue 240 cases.append((bounds[i + 1], "return %d;" % i)) 241 cases.append((None, "return %d;" % (len(bounds) - 2))) 242 return (merge_cases(cases), bounds_idx) 243 244 245# utility: print a big comment block into a set of files 246def put_banner(files, banner): 247 for f in files: 248 for line in banner: 249 print("// %s" % line, file=f) 250 print(file=f) 251 252 253shapes = set() 254for histogram in inst_map["Histogram"]: 255 shapes.add(Shape(max=histogram.max, buckets=histogram.buckets)) 256 257 258def snake_to_pascal(name): 259 return "".join([x.capitalize() for x in name.split("_")]) 260 261 262with open("src/core/telemetry/stats_data.h", "w") as H: 263 # copy-paste copyright notice from this file 264 with open(sys.argv[0]) as my_source: 265 copyright = [] 266 for line in my_source: 267 if line[0] != "#": 268 break 269 for line in my_source: 270 if line[0] == "#": 271 copyright.append(line) 272 break 273 for line in my_source: 274 if line[0] != "#": 275 break 276 copyright.append(line) 277 put_banner([H], [line[2:].rstrip() for line in copyright]) 278 279 put_banner( 280 [H], ["Automatically generated by tools/codegen/core/gen_stats_data.py"] 281 ) 282 283 print("#ifndef GRPC_SRC_CORE_TELEMETRY_STATS_DATA_H", file=H) 284 print("#define GRPC_SRC_CORE_TELEMETRY_STATS_DATA_H", file=H) 285 print(file=H) 286 print("#include <grpc/support/port_platform.h>", file=H) 287 print("#include <atomic>", file=H) 288 print("#include <memory>", file=H) 289 print("#include <stdint.h>", file=H) 290 print('#include "src/core/telemetry/histogram_view.h"', file=H) 291 print('#include "absl/strings/string_view.h"', file=H) 292 print('#include "src/core/util/per_cpu.h"', file=H) 293 print(file=H) 294 print("namespace grpc_core {", file=H) 295 296 for shape in shapes: 297 print( 298 "class HistogramCollector_%d_%d;" % (shape.max, shape.buckets), 299 file=H, 300 ) 301 print("class Histogram_%d_%d {" % (shape.max, shape.buckets), file=H) 302 print(" public:", file=H) 303 print(" static int BucketFor(int value);", file=H) 304 print(" const uint64_t* buckets() const { return buckets_; }", file=H) 305 print( 306 " size_t bucket_count() const { return %d; }" % shape.buckets, 307 file=H, 308 ) 309 print( 310 " friend Histogram_%d_%d operator-(const Histogram_%d_%d& left," 311 " const Histogram_%d_%d& right);" 312 % ( 313 shape.max, 314 shape.buckets, 315 shape.max, 316 shape.buckets, 317 shape.max, 318 shape.buckets, 319 ), 320 file=H, 321 ) 322 print(" private:", file=H) 323 print( 324 " friend class HistogramCollector_%d_%d;" 325 % (shape.max, shape.buckets), 326 file=H, 327 ) 328 print(" uint64_t buckets_[%d]{};" % shape.buckets, file=H) 329 print("};", file=H) 330 print( 331 "class HistogramCollector_%d_%d {" % (shape.max, shape.buckets), 332 file=H, 333 ) 334 print(" public:", file=H) 335 print(" void Increment(int value) {", file=H) 336 print( 337 " buckets_[Histogram_%d_%d::BucketFor(value)]" 338 % (shape.max, shape.buckets), 339 file=H, 340 ) 341 print(" .fetch_add(1, std::memory_order_relaxed);", file=H) 342 print(" }", file=H) 343 print( 344 " void Collect(Histogram_%d_%d* result) const;" 345 % (shape.max, shape.buckets), 346 file=H, 347 ) 348 print(" private:", file=H) 349 print(" std::atomic<uint64_t> buckets_[%d]{};" % shape.buckets, file=H) 350 print("};", file=H) 351 352 print("struct GlobalStats {", file=H) 353 print(" enum class Counter {", file=H) 354 for ctr in inst_map["Counter"]: 355 print(" k%s," % snake_to_pascal(ctr.name), file=H) 356 print(" COUNT", file=H) 357 print(" };", file=H) 358 print(" enum class Histogram {", file=H) 359 for ctr in inst_map["Histogram"]: 360 print(" k%s," % snake_to_pascal(ctr.name), file=H) 361 print(" COUNT", file=H) 362 print(" };", file=H) 363 print(" GlobalStats();", file=H) 364 print( 365 ( 366 " static const absl::string_view" 367 " counter_name[static_cast<int>(Counter::COUNT)];" 368 ), 369 file=H, 370 ) 371 print( 372 ( 373 " static const absl::string_view" 374 " histogram_name[static_cast<int>(Histogram::COUNT)];" 375 ), 376 file=H, 377 ) 378 print( 379 ( 380 " static const absl::string_view" 381 " counter_doc[static_cast<int>(Counter::COUNT)];" 382 ), 383 file=H, 384 ) 385 print( 386 ( 387 " static const absl::string_view" 388 " histogram_doc[static_cast<int>(Histogram::COUNT)];" 389 ), 390 file=H, 391 ) 392 print(" union {", file=H) 393 print(" struct {", file=H) 394 for ctr in inst_map["Counter"]: 395 print(" uint64_t %s;" % ctr.name, file=H) 396 print(" };", file=H) 397 print(" uint64_t counters[static_cast<int>(Counter::COUNT)];", file=H) 398 print(" };", file=H) 399 for ctr in inst_map["Histogram"]: 400 print( 401 " Histogram_%d_%d %s;" % (ctr.max, ctr.buckets, ctr.name), file=H 402 ) 403 print(" HistogramView histogram(Histogram which) const;", file=H) 404 print( 405 " std::unique_ptr<GlobalStats> Diff(const GlobalStats& other) const;", 406 file=H, 407 ) 408 print("};", file=H) 409 print("class GlobalStatsCollector {", file=H) 410 print(" public:", file=H) 411 print(" std::unique_ptr<GlobalStats> Collect() const;", file=H) 412 for ctr in inst_map["Counter"]: 413 print( 414 " void Increment%s() { data_.this_cpu().%s.fetch_add(1," 415 " std::memory_order_relaxed); }" 416 % (snake_to_pascal(ctr.name), ctr.name), 417 file=H, 418 ) 419 for ctr in inst_map["Histogram"]: 420 print( 421 " void Increment%s(int value) {" 422 " data_.this_cpu().%s.Increment(value); }" 423 % (snake_to_pascal(ctr.name), ctr.name), 424 file=H, 425 ) 426 print(" private:", file=H) 427 print(" struct Data {", file=H) 428 for ctr in inst_map["Counter"]: 429 print(" std::atomic<uint64_t> %s{0};" % ctr.name, file=H) 430 for ctr in inst_map["Histogram"]: 431 print( 432 " HistogramCollector_%d_%d %s;" 433 % (ctr.max, ctr.buckets, ctr.name), 434 file=H, 435 ) 436 print(" };", file=H) 437 print( 438 ( 439 " PerCpu<Data>" 440 " data_{PerCpuOptions().SetCpusPerShard(4).SetMaxShards(32)};" 441 ), 442 file=H, 443 ) 444 print("};", file=H) 445 print("}", file=H) 446 447 print(file=H) 448 print("#endif // GRPC_SRC_CORE_TELEMETRY_STATS_DATA_H", file=H) 449 450with open("src/core/telemetry/stats_data.cc", "w") as C: 451 # copy-paste copyright notice from this file 452 with open(sys.argv[0]) as my_source: 453 copyright = [] 454 for line in my_source: 455 if line[0] != "#": 456 break 457 for line in my_source: 458 if line[0] == "#": 459 copyright.append(line) 460 break 461 for line in my_source: 462 if line[0] != "#": 463 break 464 copyright.append(line) 465 put_banner([C], [line[2:].rstrip() for line in copyright]) 466 467 put_banner( 468 [C], ["Automatically generated by tools/codegen/core/gen_stats_data.py"] 469 ) 470 471 print("#include <grpc/support/port_platform.h>", file=C) 472 print(file=C) 473 print('#include "src/core/telemetry/stats_data.h"', file=C) 474 print("#include <stdint.h>", file=C) 475 print(file=C) 476 477 histo_code = [] 478 histo_bucket_boundaries = {} 479 for shape in shapes: 480 code, bounds_idx = gen_bucket_code(shape) 481 histo_bucket_boundaries[shape] = bounds_idx 482 histo_code.append(code) 483 484 print("namespace grpc_core {", file=C) 485 print("namespace { union DblUint { double dbl; uint64_t uint; }; }", file=C) 486 487 for shape in shapes: 488 print( 489 "void HistogramCollector_%d_%d::Collect(Histogram_%d_%d* result)" 490 " const {" % (shape.max, shape.buckets, shape.max, shape.buckets), 491 file=C, 492 ) 493 print(" for (int i=0; i<%d; i++) {" % shape.buckets, file=C) 494 print( 495 ( 496 " result->buckets_[i] +=" 497 " buckets_[i].load(std::memory_order_relaxed);" 498 ), 499 file=C, 500 ) 501 print(" }", file=C) 502 print("}", file=C) 503 print( 504 "Histogram_%d_%d operator-(const Histogram_%d_%d& left, const" 505 " Histogram_%d_%d& right) {" 506 % ( 507 shape.max, 508 shape.buckets, 509 shape.max, 510 shape.buckets, 511 shape.max, 512 shape.buckets, 513 ), 514 file=C, 515 ) 516 print(" Histogram_%d_%d result;" % (shape.max, shape.buckets), file=C) 517 print(" for (int i=0; i<%d; i++) {" % shape.buckets, file=C) 518 print( 519 " result.buckets_[i] = left.buckets_[i] - right.buckets_[i];", 520 file=C, 521 ) 522 print(" }", file=C) 523 print(" return result;", file=C) 524 print("}", file=C) 525 526 for typename, instances in sorted(inst_map.items()): 527 print( 528 "const absl::string_view" 529 " GlobalStats::%s_name[static_cast<int>(%s::COUNT)] = {" 530 % (typename.lower(), typename), 531 file=C, 532 ) 533 for inst in instances: 534 print(" %s," % c_str(inst.name), file=C) 535 print("};", file=C) 536 print( 537 "const absl::string_view" 538 " GlobalStats::%s_doc[static_cast<int>(%s::COUNT)] = {" 539 % (typename.lower(), typename), 540 file=C, 541 ) 542 for inst in instances: 543 print(" %s," % c_str(inst.doc), file=C) 544 print("};", file=C) 545 546 print("namespace {", file=C) 547 for i, tbl in enumerate(static_tables): 548 print( 549 "const %s kStatsTable%d[%d] = {%s};" 550 % (tbl[0], i, len(tbl[1]), ",".join("%s" % x for x in tbl[1])), 551 file=C, 552 ) 553 print("} // namespace", file=C) 554 555 for shape, code in zip(shapes, histo_code): 556 print( 557 "int Histogram_%d_%d::BucketFor(int value) {%s}" 558 % (shape.max, shape.buckets, code), 559 file=C, 560 ) 561 562 print( 563 "GlobalStats::GlobalStats() : %s {}" 564 % ",".join("%s{0}" % ctr.name for ctr in inst_map["Counter"]), 565 file=C, 566 ) 567 568 print( 569 "HistogramView GlobalStats::histogram(Histogram which) const {", file=C 570 ) 571 print(" switch (which) {", file=C) 572 print(" default: GPR_UNREACHABLE_CODE(return HistogramView());", file=C) 573 for inst in inst_map["Histogram"]: 574 print(" case Histogram::k%s:" % snake_to_pascal(inst.name), file=C) 575 print( 576 " return HistogramView{&Histogram_%d_%d::BucketFor," 577 " kStatsTable%d, %d, %s.buckets()};" 578 % ( 579 inst.max, 580 inst.buckets, 581 histo_bucket_boundaries[Shape(inst.max, inst.buckets)], 582 inst.buckets, 583 inst.name, 584 ), 585 file=C, 586 ) 587 print(" }", file=C) 588 print("}", file=C) 589 590 print( 591 "std::unique_ptr<GlobalStats> GlobalStatsCollector::Collect() const {", 592 file=C, 593 ) 594 print(" auto result = std::make_unique<GlobalStats>();", file=C) 595 print(" for (const auto& data : data_) {", file=C) 596 for ctr in inst_map["Counter"]: 597 print( 598 " result->%s += data.%s.load(std::memory_order_relaxed);" 599 % (ctr.name, ctr.name), 600 file=C, 601 ) 602 for h in inst_map["Histogram"]: 603 print(" data.%s.Collect(&result->%s);" % (h.name, h.name), file=C) 604 print(" }", file=C) 605 print(" return result;", file=C) 606 print("}", file=C) 607 608 print( 609 ( 610 "std::unique_ptr<GlobalStats> GlobalStats::Diff(const GlobalStats&" 611 " other) const {" 612 ), 613 file=C, 614 ) 615 print(" auto result = std::make_unique<GlobalStats>();", file=C) 616 for ctr in inst_map["Counter"]: 617 print( 618 " result->%s = %s - other.%s;" % (ctr.name, ctr.name, ctr.name), 619 file=C, 620 ) 621 for h in inst_map["Histogram"]: 622 print( 623 " result->%s = %s - other.%s;" % (h.name, h.name, h.name), file=C 624 ) 625 print(" return result;", file=C) 626 print("}", file=C) 627 628 print("}", file=C) 629