diff options
Diffstat (limited to 'toolkit/components/telemetry/build_scripts/gen_histogram_data.py')
-rw-r--r-- | toolkit/components/telemetry/build_scripts/gen_histogram_data.py | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/build_scripts/gen_histogram_data.py b/toolkit/components/telemetry/build_scripts/gen_histogram_data.py new file mode 100644 index 0000000000..a203dde9f9 --- /dev/null +++ b/toolkit/components/telemetry/build_scripts/gen_histogram_data.py @@ -0,0 +1,297 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Write out histogram information for C++. The histograms are defined +# in a file provided as a command-line argument. + +import sys +from os import path + +import buildconfig +from mozparsers import parse_histograms +from mozparsers.shared_telemetry_utils import ParserError, static_assert + +COMPONENTS_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append( + path.join(COMPONENTS_PATH, "glean", "build_scripts", "glean_parser_ext") +) +from string_table import StringTable + +banner = """/* This file is auto-generated, see gen_histogram_data.py. */ +""" + + +def print_array_entry( + output, + histogram, + name_index, + exp_index, + label_index, + label_count, + key_index, + key_count, + store_index, + store_count, +): + if histogram.record_on_os(buildconfig.substs["OS_TARGET"]): + print( + " { %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %s, %s, %s, %s, %s, %s }," + % ( + histogram.low(), + histogram.high(), + histogram.n_buckets(), + name_index, + exp_index, + label_count, + key_count, + store_count, + label_index, + key_index, + store_index, + " | ".join(histogram.record_in_processes_enum()), + "true" if histogram.keyed() else "false", + histogram.nsITelemetry_kind(), + histogram.dataset(), + " | ".join(histogram.products_enum()), + ), + file=output, + ) + + +def write_histogram_table(output, histograms): + string_table = StringTable() + + label_table = [] + label_count = 0 + keys_table = [] + keys_count = 0 + store_table = [] + total_store_count = 0 + + print("constexpr HistogramInfo gHistogramInfos[] = {", file=output) + for histogram in histograms: + name_index = string_table.stringIndex(histogram.name()) + exp_index = string_table.stringIndex(histogram.expiration()) + + labels = histogram.labels() + label_index = 0 + if len(labels) > 0: + label_index = label_count + label_table.append((histogram.name(), string_table.stringIndexes(labels))) + label_count += len(labels) + + keys = histogram.keys() + key_index = 0 + if len(keys) > 0: + key_index = keys_count + keys_table.append((histogram.name(), string_table.stringIndexes(keys))) + keys_count += len(keys) + + stores = histogram.record_into_store() + store_index = 0 + if stores == ["main"]: + # if count == 1 && offset == UINT16_MAX -> only main store + store_index = "UINT16_MAX" + else: + store_index = total_store_count + store_table.append((histogram.name(), string_table.stringIndexes(stores))) + total_store_count += len(stores) + + print_array_entry( + output, + histogram, + name_index, + exp_index, + label_index, + len(labels), + key_index, + len(keys), + store_index, + len(stores), + ) + print("};\n", file=output) + + strtab_name = "gHistogramStringTable" + string_table.writeDefinition(output, strtab_name) + static_assert(output, "sizeof(%s) <= UINT32_MAX" % strtab_name, "index overflow") + + print("\n#if defined(_MSC_VER) && !defined(__clang__)", file=output) + print("const uint32_t gHistogramLabelTable[] = {", file=output) + print("#else", file=output) + print("constexpr uint32_t gHistogramLabelTable[] = {", file=output) + print("#endif", file=output) + for name, indexes in label_table: + print("/* %s */ %s," % (name, ", ".join(map(str, indexes))), file=output) + print("};", file=output) + static_assert( + output, "sizeof(gHistogramLabelTable) <= UINT16_MAX", "index overflow" + ) + + print("\n#if defined(_MSC_VER) && !defined(__clang__)", file=output) + print("const uint32_t gHistogramKeyTable[] = {", file=output) + print("#else", file=output) + print("constexpr uint32_t gHistogramKeyTable[] = {", file=output) + print("#endif", file=output) + for name, indexes in keys_table: + print("/* %s */ %s," % (name, ", ".join(map(str, indexes))), file=output) + print("};", file=output) + static_assert(output, "sizeof(gHistogramKeyTable) <= UINT16_MAX", "index overflow") + + store_table_name = "gHistogramStoresTable" + print("\n#if defined(_MSC_VER) && !defined(__clang__)", file=output) + print("const uint32_t {}[] = {{".format(store_table_name), file=output) + print("#else", file=output) + print("constexpr uint32_t {}[] = {{".format(store_table_name), file=output) + print("#endif", file=output) + for name, indexes in store_table: + print("/* %s */ %s," % (name, ", ".join(map(str, indexes))), file=output) + print("};", file=output) + static_assert( + output, "sizeof(%s) <= UINT16_MAX" % store_table_name, "index overflow" + ) + + +# Write out static asserts for histogram data. We'd prefer to perform +# these checks in this script itself, but since several histograms +# (generally enumerated histograms) use compile-time constants for +# their upper bounds, we have to let the compiler do the checking. + + +def static_asserts_for_boolean(output, histogram): + pass + + +def static_asserts_for_flag(output, histogram): + pass + + +def static_asserts_for_count(output, histogram): + pass + + +def static_asserts_for_enumerated(output, histogram): + n_values = histogram.high() + static_assert( + output, "%s > 2" % n_values, "Not enough values for %s" % histogram.name() + ) + + +def shared_static_asserts(output, histogram): + name = histogram.name() + low = histogram.low() + high = histogram.high() + n_buckets = histogram.n_buckets() + static_assert(output, "%s < %s" % (low, high), "low >= high for %s" % name) + static_assert(output, "%s > 2" % n_buckets, "Not enough values for %s" % name) + static_assert(output, "%s >= 1" % low, "Incorrect low value for %s" % name) + static_assert( + output, + "%s > %s" % (high, n_buckets), + "high must be > number of buckets for %s;" + " you may want an enumerated histogram" % name, + ) + + +def static_asserts_for_linear(output, histogram): + shared_static_asserts(output, histogram) + + +def static_asserts_for_exponential(output, histogram): + shared_static_asserts(output, histogram) + + +def write_histogram_static_asserts(output, histograms): + print( + """ +// Perform the checks at the beginning of HistogramGet at +// compile time, so that incorrect histogram definitions +// give compile-time errors, not runtime errors.""", + file=output, + ) + + table = { + "boolean": static_asserts_for_boolean, + "flag": static_asserts_for_flag, + "count": static_asserts_for_count, + "enumerated": static_asserts_for_enumerated, + "categorical": static_asserts_for_enumerated, + "linear": static_asserts_for_linear, + "exponential": static_asserts_for_exponential, + } + + target_os = buildconfig.substs["OS_TARGET"] + for histogram in histograms: + kind = histogram.kind() + if not histogram.record_on_os(target_os): + continue + + if kind not in table: + raise Exception( + 'Unknown kind "%s" for histogram "%s".' % (kind, histogram.name()) + ) + fn = table[kind] + fn(output, histogram) + + +def write_histogram_ranges(output, histograms): + # This generates static data to avoid costly initialization of histograms + # (especially exponential ones which require log and exp calls) at runtime. + # The format must exactly match that required in histogram.cc, which is + # 0, buckets..., INT_MAX. Additionally, the list ends in a 0 to aid asserts + # that validate that the length of the ranges list is correct.U cache miss. + print("#if defined(_MSC_VER) && !defined(__clang__)", file=output) + print("const int gHistogramBucketLowerBounds[] = {", file=output) + print("#else", file=output) + print("constexpr int gHistogramBucketLowerBounds[] = {", file=output) + print("#endif", file=output) + + # Print the dummy buckets for expired histograms, and set the offset to match. + print("0,1,2,INT_MAX,", file=output) + offset = 4 + ranges_offsets = {} + + for histogram in histograms: + ranges = tuple(histogram.ranges()) + if ranges not in ranges_offsets: + ranges_offsets[ranges] = offset + # Suffix each ranges listing with INT_MAX, to match histogram.cc's + # expected format. + offset += len(ranges) + 1 + print(",".join(map(str, ranges)), ",INT_MAX,", file=output) + print("0};", file=output) + + if offset > 32767: + raise Exception("Histogram offsets exceeded maximum value for an int16_t.") + + target_os = buildconfig.substs["OS_TARGET"] + print("#if defined(_MSC_VER) && !defined(__clang__)", file=output) + print("const int16_t gHistogramBucketLowerBoundIndex[] = {", file=output) + print("#else", file=output) + print("constexpr int16_t gHistogramBucketLowerBoundIndex[] = {", file=output) + print("#endif", file=output) + for histogram in histograms: + if histogram.record_on_os(target_os): + our_offset = ranges_offsets[tuple(histogram.ranges())] + print("%d," % our_offset, file=output) + + print("};", file=output) + + +def main(output, *filenames): + try: + histograms = list(parse_histograms.from_files(filenames)) + except ParserError as ex: + print("\nError processing histograms:\n" + str(ex) + "\n") + sys.exit(1) + + print(banner, file=output) + write_histogram_table(output, histograms) + write_histogram_ranges(output, histograms) + write_histogram_static_asserts(output, histograms) + + +if __name__ == "__main__": + main(sys.stdout, *sys.argv[1:]) |