• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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