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/lib/debug/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/lib/debug/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_LIB_DEBUG_STATS_DATA_H", file=H) 284 print("#define GRPC_SRC_CORE_LIB_DEBUG_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/lib/debug/histogram_view.h"', file=H) 291 print('#include "absl/strings/string_view.h"', file=H) 292 print('#include "src/core/lib/gprpp/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 " friend Histogram_%d_%d operator-(const Histogram_%d_%d& left," 307 " const Histogram_%d_%d& right);" 308 % ( 309 shape.max, 310 shape.buckets, 311 shape.max, 312 shape.buckets, 313 shape.max, 314 shape.buckets, 315 ), 316 file=H, 317 ) 318 print(" private:", file=H) 319 print( 320 " friend class HistogramCollector_%d_%d;" 321 % (shape.max, shape.buckets), 322 file=H, 323 ) 324 print(" uint64_t buckets_[%d]{};" % shape.buckets, file=H) 325 print("};", file=H) 326 print( 327 "class HistogramCollector_%d_%d {" % (shape.max, shape.buckets), 328 file=H, 329 ) 330 print(" public:", file=H) 331 print(" void Increment(int value) {", file=H) 332 print( 333 " buckets_[Histogram_%d_%d::BucketFor(value)]" 334 % (shape.max, shape.buckets), 335 file=H, 336 ) 337 print(" .fetch_add(1, std::memory_order_relaxed);", file=H) 338 print(" }", file=H) 339 print( 340 " void Collect(Histogram_%d_%d* result) const;" 341 % (shape.max, shape.buckets), 342 file=H, 343 ) 344 print(" private:", file=H) 345 print(" std::atomic<uint64_t> buckets_[%d]{};" % shape.buckets, file=H) 346 print("};", file=H) 347 348 print("struct GlobalStats {", file=H) 349 print(" enum class Counter {", file=H) 350 for ctr in inst_map["Counter"]: 351 print(" k%s," % snake_to_pascal(ctr.name), file=H) 352 print(" COUNT", file=H) 353 print(" };", file=H) 354 print(" enum class Histogram {", file=H) 355 for ctr in inst_map["Histogram"]: 356 print(" k%s," % snake_to_pascal(ctr.name), file=H) 357 print(" COUNT", file=H) 358 print(" };", file=H) 359 print(" GlobalStats();", file=H) 360 print( 361 ( 362 " static const absl::string_view" 363 " counter_name[static_cast<int>(Counter::COUNT)];" 364 ), 365 file=H, 366 ) 367 print( 368 ( 369 " static const absl::string_view" 370 " histogram_name[static_cast<int>(Histogram::COUNT)];" 371 ), 372 file=H, 373 ) 374 print( 375 ( 376 " static const absl::string_view" 377 " counter_doc[static_cast<int>(Counter::COUNT)];" 378 ), 379 file=H, 380 ) 381 print( 382 ( 383 " static const absl::string_view" 384 " histogram_doc[static_cast<int>(Histogram::COUNT)];" 385 ), 386 file=H, 387 ) 388 print(" union {", file=H) 389 print(" struct {", file=H) 390 for ctr in inst_map["Counter"]: 391 print(" uint64_t %s;" % ctr.name, file=H) 392 print(" };", file=H) 393 print(" uint64_t counters[static_cast<int>(Counter::COUNT)];", file=H) 394 print(" };", file=H) 395 for ctr in inst_map["Histogram"]: 396 print( 397 " Histogram_%d_%d %s;" % (ctr.max, ctr.buckets, ctr.name), file=H 398 ) 399 print(" HistogramView histogram(Histogram which) const;", file=H) 400 print( 401 " std::unique_ptr<GlobalStats> Diff(const GlobalStats& other) const;", 402 file=H, 403 ) 404 print("};", file=H) 405 print("class GlobalStatsCollector {", file=H) 406 print(" public:", file=H) 407 print(" std::unique_ptr<GlobalStats> Collect() const;", file=H) 408 for ctr in inst_map["Counter"]: 409 print( 410 " void Increment%s() { data_.this_cpu().%s.fetch_add(1," 411 " std::memory_order_relaxed); }" 412 % (snake_to_pascal(ctr.name), ctr.name), 413 file=H, 414 ) 415 for ctr in inst_map["Histogram"]: 416 print( 417 " void Increment%s(int value) {" 418 " data_.this_cpu().%s.Increment(value); }" 419 % (snake_to_pascal(ctr.name), ctr.name), 420 file=H, 421 ) 422 print(" private:", file=H) 423 print(" struct Data {", file=H) 424 for ctr in inst_map["Counter"]: 425 print(" std::atomic<uint64_t> %s{0};" % ctr.name, file=H) 426 for ctr in inst_map["Histogram"]: 427 print( 428 " HistogramCollector_%d_%d %s;" 429 % (ctr.max, ctr.buckets, ctr.name), 430 file=H, 431 ) 432 print(" };", file=H) 433 print( 434 ( 435 " PerCpu<Data>" 436 " data_{PerCpuOptions().SetCpusPerShard(4).SetMaxShards(32)};" 437 ), 438 file=H, 439 ) 440 print("};", file=H) 441 print("}", file=H) 442 443 print(file=H) 444 print("#endif // GRPC_SRC_CORE_LIB_DEBUG_STATS_DATA_H", file=H) 445 446with open("src/core/lib/debug/stats_data.cc", "w") as C: 447 # copy-paste copyright notice from this file 448 with open(sys.argv[0]) as my_source: 449 copyright = [] 450 for line in my_source: 451 if line[0] != "#": 452 break 453 for line in my_source: 454 if line[0] == "#": 455 copyright.append(line) 456 break 457 for line in my_source: 458 if line[0] != "#": 459 break 460 copyright.append(line) 461 put_banner([C], [line[2:].rstrip() for line in copyright]) 462 463 put_banner( 464 [C], ["Automatically generated by tools/codegen/core/gen_stats_data.py"] 465 ) 466 467 print("#include <grpc/support/port_platform.h>", file=C) 468 print(file=C) 469 print('#include "src/core/lib/debug/stats_data.h"', file=C) 470 print("#include <stdint.h>", file=C) 471 print(file=C) 472 473 histo_code = [] 474 histo_bucket_boundaries = {} 475 for shape in shapes: 476 code, bounds_idx = gen_bucket_code(shape) 477 histo_bucket_boundaries[shape] = bounds_idx 478 histo_code.append(code) 479 480 print("namespace grpc_core {", file=C) 481 print("namespace { union DblUint { double dbl; uint64_t uint; }; }", file=C) 482 483 for shape in shapes: 484 print( 485 "void HistogramCollector_%d_%d::Collect(Histogram_%d_%d* result)" 486 " const {" % (shape.max, shape.buckets, shape.max, shape.buckets), 487 file=C, 488 ) 489 print(" for (int i=0; i<%d; i++) {" % shape.buckets, file=C) 490 print( 491 ( 492 " result->buckets_[i] +=" 493 " buckets_[i].load(std::memory_order_relaxed);" 494 ), 495 file=C, 496 ) 497 print(" }", file=C) 498 print("}", file=C) 499 print( 500 "Histogram_%d_%d operator-(const Histogram_%d_%d& left, const" 501 " Histogram_%d_%d& right) {" 502 % ( 503 shape.max, 504 shape.buckets, 505 shape.max, 506 shape.buckets, 507 shape.max, 508 shape.buckets, 509 ), 510 file=C, 511 ) 512 print(" Histogram_%d_%d result;" % (shape.max, shape.buckets), file=C) 513 print(" for (int i=0; i<%d; i++) {" % shape.buckets, file=C) 514 print( 515 " result.buckets_[i] = left.buckets_[i] - right.buckets_[i];", 516 file=C, 517 ) 518 print(" }", file=C) 519 print(" return result;", file=C) 520 print("}", file=C) 521 522 for typename, instances in sorted(inst_map.items()): 523 print( 524 "const absl::string_view" 525 " GlobalStats::%s_name[static_cast<int>(%s::COUNT)] = {" 526 % (typename.lower(), typename), 527 file=C, 528 ) 529 for inst in instances: 530 print(" %s," % c_str(inst.name), file=C) 531 print("};", file=C) 532 print( 533 "const absl::string_view" 534 " GlobalStats::%s_doc[static_cast<int>(%s::COUNT)] = {" 535 % (typename.lower(), typename), 536 file=C, 537 ) 538 for inst in instances: 539 print(" %s," % c_str(inst.doc), file=C) 540 print("};", file=C) 541 542 print("namespace {", file=C) 543 for i, tbl in enumerate(static_tables): 544 print( 545 "const %s kStatsTable%d[%d] = {%s};" 546 % (tbl[0], i, len(tbl[1]), ",".join("%s" % x for x in tbl[1])), 547 file=C, 548 ) 549 print("} // namespace", file=C) 550 551 for shape, code in zip(shapes, histo_code): 552 print( 553 "int Histogram_%d_%d::BucketFor(int value) {%s}" 554 % (shape.max, shape.buckets, code), 555 file=C, 556 ) 557 558 print( 559 "GlobalStats::GlobalStats() : %s {}" 560 % ",".join("%s{0}" % ctr.name for ctr in inst_map["Counter"]), 561 file=C, 562 ) 563 564 print( 565 "HistogramView GlobalStats::histogram(Histogram which) const {", file=C 566 ) 567 print(" switch (which) {", file=C) 568 print(" default: GPR_UNREACHABLE_CODE(return HistogramView());", file=C) 569 for inst in inst_map["Histogram"]: 570 print(" case Histogram::k%s:" % snake_to_pascal(inst.name), file=C) 571 print( 572 " return HistogramView{&Histogram_%d_%d::BucketFor," 573 " kStatsTable%d, %d, %s.buckets()};" 574 % ( 575 inst.max, 576 inst.buckets, 577 histo_bucket_boundaries[Shape(inst.max, inst.buckets)], 578 inst.buckets, 579 inst.name, 580 ), 581 file=C, 582 ) 583 print(" }", file=C) 584 print("}", file=C) 585 586 print( 587 "std::unique_ptr<GlobalStats> GlobalStatsCollector::Collect() const {", 588 file=C, 589 ) 590 print(" auto result = std::make_unique<GlobalStats>();", file=C) 591 print(" for (const auto& data : data_) {", file=C) 592 for ctr in inst_map["Counter"]: 593 print( 594 " result->%s += data.%s.load(std::memory_order_relaxed);" 595 % (ctr.name, ctr.name), 596 file=C, 597 ) 598 for h in inst_map["Histogram"]: 599 print(" data.%s.Collect(&result->%s);" % (h.name, h.name), file=C) 600 print(" }", file=C) 601 print(" return result;", file=C) 602 print("}", file=C) 603 604 print( 605 ( 606 "std::unique_ptr<GlobalStats> GlobalStats::Diff(const GlobalStats&" 607 " other) const {" 608 ), 609 file=C, 610 ) 611 print(" auto result = std::make_unique<GlobalStats>();", file=C) 612 for ctr in inst_map["Counter"]: 613 print( 614 " result->%s = %s - other.%s;" % (ctr.name, ctr.name, ctr.name), 615 file=C, 616 ) 617 for h in inst_map["Histogram"]: 618 print( 619 " result->%s = %s - other.%s;" % (h.name, h.name, h.name), file=C 620 ) 621 print(" return result;", file=C) 622 print("}", file=C) 623 624 print("}", file=C) 625