| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 | #!/usr/bin/env python2.7# 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.import collectionsimport ctypesimport mathimport sysimport yamlimport jsonwith open('src/core/lib/debug/stats_data.yaml') as f:    attrs = yaml.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, unicode):        s = s.encode(encoding)    result = ''    for c in s:        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']),)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" % attrdef dbl2u64(d):    return ctypes.c_ulonglong.from_buffer(ctypes.c_double(d)).valuedef 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(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 table_size > 65535: continue        if best is None:            best = (shift_bits, n, table_size)        elif best[1] < n:            best = (shift_bits, n, table_size)    print best    return bestdef gen_map_table(mapped_bounds, shift_data):    tbl = []    cur = 0    print mapped_bounds    mapped_bounds = [x >> shift_data[0] for x in mapped_bounds]    print mapped_bounds    for i in range(0, mapped_bounds[shift_data[1] - 1]):        while i > mapped_bounds[cur]:            cur += 1        tbl.append(cur)    return tblstatic_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    print "ADD TABLE: %s %r" % (type, values)    r = len(static_tables)    static_tables.append(v)    return rdef 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 gen_bucket_code(histogram):    bounds = [0, 1]    done_trivial = False    done_unmapped = False    first_nontrivial = None    first_unmapped = None    while len(bounds) < histogram.buckets + 1:        if len(bounds) == histogram.buckets:            nextb = int(histogram.max)        else:            mul = math.pow(                float(histogram.max) / bounds[-1],                1.0 / (histogram.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')    if done_trivial:        first_nontrivial_code = dbl2u64(first_nontrivial)        code_bounds = [dbl2u64(x) - first_nontrivial_code for x in bounds]        shift_data = find_ideal_shift(code_bounds[first_nontrivial:],                                      256 * histogram.buckets)    #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:]]    code = 'value = GPR_CLAMP(value, 0, %d);\n' % histogram.max    map_table = gen_map_table(code_bounds[first_nontrivial:], shift_data)    if first_nontrivial is None:        code += ('GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, value);\n' %                 histogram.name.upper())    else:        code += 'if (value < %d) {\n' % first_nontrivial        code += ('GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, value);\n' %                 histogram.name.upper())        code += 'return;\n'        code += '}'        first_nontrivial_code = dbl2u64(first_nontrivial)        if shift_data is not None:            map_table_idx = decl_static_table(map_table,                                              type_for_uint_table(map_table))            code += 'union { double dbl; uint64_t uint; } _val, _bkt;\n'            code += '_val.dbl = value;\n'            code += 'if (_val.uint < %dull) {\n' % (                (map_table[-1] << shift_data[0]) + first_nontrivial_code)            code += 'int bucket = '            code += 'grpc_stats_table_%d[((_val.uint - %dull) >> %d)] + %d;\n' % (                map_table_idx, first_nontrivial_code, shift_data[0],                first_nontrivial)            code += '_bkt.dbl = grpc_stats_table_%d[bucket];\n' % bounds_idx            code += 'bucket -= (_val.uint < _bkt.uint);\n'            code += 'GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, bucket);\n' % histogram.name.upper(            )            code += 'return;\n'            code += '}\n'        code += 'GRPC_STATS_INC_HISTOGRAM(GRPC_STATS_HISTOGRAM_%s, ' % histogram.name.upper(        )        code += 'grpc_stats_histo_find_bucket_slow(value, grpc_stats_table_%d, %d));\n' % (            bounds_idx, histogram.buckets)    return (code, bounds_idx)# utility: print a big comment block into a set of filesdef put_banner(files, banner):    for f in files:        print >> f, '/*'        for line in banner:            print >> f, ' * %s' % line        print >> f, ' */'        print >> fwith 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 >> H, "#ifndef GRPC_CORE_LIB_DEBUG_STATS_DATA_H"    print >> H, "#define GRPC_CORE_LIB_DEBUG_STATS_DATA_H"    print >> H    print >> H, "#include <grpc/support/port_platform.h>"    print >> H    print >> H, "#include <inttypes.h>"    print >> H, "#include \"src/core/lib/iomgr/exec_ctx.h\""    print >> H    for typename, instances in sorted(inst_map.items()):        print >> H, "typedef enum {"        for inst in instances:            print >> H, "  GRPC_STATS_%s_%s," % (typename.upper(),                                                 inst.name.upper())        print >> H, "  GRPC_STATS_%s_COUNT" % (typename.upper())        print >> H, "} grpc_stats_%ss;" % (typename.lower())        print >> H, "extern const char *grpc_stats_%s_name[GRPC_STATS_%s_COUNT];" % (            typename.lower(), typename.upper())        print >> H, "extern const char *grpc_stats_%s_doc[GRPC_STATS_%s_COUNT];" % (            typename.lower(), typename.upper())    histo_start = []    histo_buckets = []    histo_bucket_boundaries = []    print >> H, "typedef enum {"    first_slot = 0    for histogram in inst_map['Histogram']:        histo_start.append(first_slot)        histo_buckets.append(histogram.buckets)        print >> H, "  GRPC_STATS_HISTOGRAM_%s_FIRST_SLOT = %d," % (            histogram.name.upper(), first_slot)        print >> H, "  GRPC_STATS_HISTOGRAM_%s_BUCKETS = %d," % (            histogram.name.upper(), histogram.buckets)        first_slot += histogram.buckets    print >> H, "  GRPC_STATS_HISTOGRAM_BUCKETS = %d" % first_slot    print >> H, "} grpc_stats_histogram_constants;"    print >> H, "#if defined(GRPC_COLLECT_STATS) || !defined(NDEBUG)"    for ctr in inst_map['Counter']:        print >> H, ("#define GRPC_STATS_INC_%s() " +                     "GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_%s)") % (                         ctr.name.upper(), ctr.name.upper())    for histogram in inst_map['Histogram']:        print >> H, "#define GRPC_STATS_INC_%s(value) grpc_stats_inc_%s( (int)(value))" % (            histogram.name.upper(), histogram.name.lower())        print >> H, "void grpc_stats_inc_%s(int x);" % histogram.name.lower()    print >> H, "#else"    for ctr in inst_map['Counter']:        print >> H, ("#define GRPC_STATS_INC_%s() ") % (ctr.name.upper())    for histogram in inst_map['Histogram']:        print >> H, "#define GRPC_STATS_INC_%s(value)" % (            histogram.name.upper())    print >> H, "#endif /* defined(GRPC_COLLECT_STATS) || !defined(NDEBUG) */"    for i, tbl in enumerate(static_tables):        print >> H, "extern const %s grpc_stats_table_%d[%d];" % (tbl[0], i,                                                                  len(tbl[1]))    print >> H, "extern const int grpc_stats_histo_buckets[%d];" % len(        inst_map['Histogram'])    print >> H, "extern const int grpc_stats_histo_start[%d];" % len(        inst_map['Histogram'])    print >> H, "extern const int *const grpc_stats_histo_bucket_boundaries[%d];" % len(        inst_map['Histogram'])    print >> H, "extern void (*const grpc_stats_inc_histogram[%d])(int x);" % len(        inst_map['Histogram'])    print >> H    print >> H, "#endif /* GRPC_CORE_LIB_DEBUG_STATS_DATA_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 >> C, "#include <grpc/support/port_platform.h>"    print >> C    print >> C, "#include \"src/core/lib/debug/stats.h\""    print >> C, "#include \"src/core/lib/debug/stats_data.h\""    print >> C, "#include \"src/core/lib/gpr/useful.h\""    print >> C, "#include \"src/core/lib/iomgr/exec_ctx.h\""    print >> C    histo_code = []    for histogram in inst_map['Histogram']:        code, bounds_idx = gen_bucket_code(histogram)        histo_bucket_boundaries.append(bounds_idx)        histo_code.append(code)    for typename, instances in sorted(inst_map.items()):        print >> C, "const char *grpc_stats_%s_name[GRPC_STATS_%s_COUNT] = {" % (            typename.lower(), typename.upper())        for inst in instances:            print >> C, "  %s," % c_str(inst.name)        print >> C, "};"        print >> C, "const char *grpc_stats_%s_doc[GRPC_STATS_%s_COUNT] = {" % (            typename.lower(), typename.upper())        for inst in instances:            print >> C, "  %s," % c_str(inst.doc)        print >> C, "};"    for i, tbl in enumerate(static_tables):        print >> C, "const %s grpc_stats_table_%d[%d] = {%s};" % (            tbl[0], i, len(tbl[1]), ','.join('%s' % x for x in tbl[1]))    for histogram, code in zip(inst_map['Histogram'], histo_code):        print >> C, ("void grpc_stats_inc_%s(int value) {%s}") % (            histogram.name.lower(), code)    print >> C, "const int grpc_stats_histo_buckets[%d] = {%s};" % (        len(inst_map['Histogram']), ','.join('%s' % x for x in histo_buckets))    print >> C, "const int grpc_stats_histo_start[%d] = {%s};" % (        len(inst_map['Histogram']), ','.join('%s' % x for x in histo_start))    print >> C, "const int *const grpc_stats_histo_bucket_boundaries[%d] = {%s};" % (        len(inst_map['Histogram']), ','.join(            'grpc_stats_table_%d' % x for x in histo_bucket_boundaries))    print >> C, "void (*const grpc_stats_inc_histogram[%d])(int x) = {%s};" % (        len(inst_map['Histogram']), ','.join(            'grpc_stats_inc_%s' % histogram.name.lower()            for histogram in inst_map['Histogram']))# patch qps_test bigquery schemaRECORD_EXPLICIT_PERCENTILES = [50, 95, 99]with open('tools/run_tests/performance/scenario_result_schema.json', 'r') as f:    qps_schema = json.loads(f.read())def FindNamed(js, name):    for el in js:        if el['name'] == name:            return eldef RemoveCoreFields(js):    new_fields = []    for field in js['fields']:        if not field['name'].startswith('core_'):            new_fields.append(field)    js['fields'] = new_fieldsRemoveCoreFields(FindNamed(qps_schema, 'clientStats'))RemoveCoreFields(FindNamed(qps_schema, 'serverStats'))def AddCoreFields(js):    for counter in inst_map['Counter']:        js['fields'].append({            'name': 'core_%s' % counter.name,            'type': 'INTEGER',            'mode': 'NULLABLE'        })    for histogram in inst_map['Histogram']:        js['fields'].append({            'name': 'core_%s' % histogram.name,            'type': 'STRING',            'mode': 'NULLABLE'        })        js['fields'].append({            'name': 'core_%s_bkts' % histogram.name,            'type': 'STRING',            'mode': 'NULLABLE'        })        for pctl in RECORD_EXPLICIT_PERCENTILES:            js['fields'].append({                'name': 'core_%s_%dp' % (histogram.name, pctl),                'type': 'FLOAT',                'mode': 'NULLABLE'            })AddCoreFields(FindNamed(qps_schema, 'clientStats'))AddCoreFields(FindNamed(qps_schema, 'serverStats'))with open('tools/run_tests/performance/scenario_result_schema.json', 'w') as f:    f.write(json.dumps(qps_schema, indent=2, sort_keys=True))# and generate a helper script to massage scenario results into the format we'd# like to querywith open('tools/run_tests/performance/massage_qps_stats.py', 'w') as P:    with open(sys.argv[0]) as my_source:        for line in my_source:            if line[0] != '#': break        for line in my_source:            if line[0] == '#':                print >> P, line.rstrip()                break        for line in my_source:            if line[0] != '#':                break            print >> P, line.rstrip()    print >> P    print >> P, '# Autogenerated by tools/codegen/core/gen_stats_data.py'    print >> P    print >> P, 'import massage_qps_stats_helpers'    print >> P, 'def massage_qps_stats(scenario_result):'    print >> P, '  for stats in scenario_result["serverStats"] + scenario_result["clientStats"]:'    print >> P, '    if "coreStats" in stats:'    print >> P, '      # Get rid of the "coreStats" element and replace it by statistics'    print >> P, '      # that correspond to columns in the bigquery schema.'    print >> P, '      core_stats = stats["coreStats"]'    print >> P, '      del stats["coreStats"]'    for counter in inst_map['Counter']:        print >> P, '      stats["core_%s"] = massage_qps_stats_helpers.counter(core_stats, "%s")' % (            counter.name, counter.name)    for i, histogram in enumerate(inst_map['Histogram']):        print >> P, '      h = massage_qps_stats_helpers.histogram(core_stats, "%s")' % histogram.name        print >> P, '      stats["core_%s"] = ",".join("%%f" %% x for x in h.buckets)' % histogram.name        print >> P, '      stats["core_%s_bkts"] = ",".join("%%f" %% x for x in h.boundaries)' % histogram.name        for pctl in RECORD_EXPLICIT_PERCENTILES:            print >> P, '      stats["core_%s_%dp"] = massage_qps_stats_helpers.percentile(h.buckets, %d, h.boundaries)' % (                histogram.name, pctl, pctl)with open('src/core/lib/debug/stats_data_bq_schema.sql', 'w') as S:    columns = []    for counter in inst_map['Counter']:        columns.append(('%s_per_iteration' % counter.name, 'FLOAT'))    print >> S, ',\n'.join('%s:%s' % x for x in columns)
 |