#!/usr/bin/env python3 # 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. # Utilities for manipulating JSON data that represents microbenchmark results. import os # template arguments and dynamic arguments of individual benchmark types # Example benchmark name: "BM_UnaryPingPong/0/0" _BM_SPECS = { "BM_UnaryPingPong": { "tpl": ["fixture", "client_mutator", "server_mutator"], "dyn": ["request_size", "response_size"], }, "BM_PumpStreamClientToServer": { "tpl": ["fixture"], "dyn": ["request_size"], }, "BM_PumpStreamServerToClient": { "tpl": ["fixture"], "dyn": ["request_size"], }, "BM_StreamingPingPong": { "tpl": ["fixture", "client_mutator", "server_mutator"], "dyn": ["request_size", "request_count"], }, "BM_StreamingPingPongMsgs": { "tpl": ["fixture", "client_mutator", "server_mutator"], "dyn": ["request_size"], }, "BM_PumpStreamServerToClient_Trickle": { "tpl": [], "dyn": ["request_size", "bandwidth_kilobits"], }, "BM_PumpUnbalancedUnary_Trickle": { "tpl": [], "dyn": ["cli_req_size", "svr_req_size", "bandwidth_kilobits"], }, "BM_ErrorStringOnNewError": { "tpl": ["fixture"], "dyn": [], }, "BM_ErrorStringRepeatedly": { "tpl": ["fixture"], "dyn": [], }, "BM_ErrorGetStatus": { "tpl": ["fixture"], "dyn": [], }, "BM_ErrorGetStatusCode": { "tpl": ["fixture"], "dyn": [], }, "BM_ErrorHttpError": { "tpl": ["fixture"], "dyn": [], }, "BM_HasClearGrpcStatus": { "tpl": ["fixture"], "dyn": [], }, "BM_IsolatedFilter": { "tpl": ["fixture", "client_mutator"], "dyn": [], }, "BM_HpackEncoderEncodeHeader": { "tpl": ["fixture"], "dyn": ["end_of_stream", "request_size"], }, "BM_HpackParserParseHeader": { "tpl": ["fixture"], "dyn": [], }, "BM_CallCreateDestroy": { "tpl": ["fixture"], "dyn": [], }, "BM_Zalloc": { "tpl": [], "dyn": ["request_size"], }, "BM_PollEmptyPollset_SpeedOfLight": { "tpl": [], "dyn": ["request_size", "request_count"], }, "BM_StreamCreateSendInitialMetadataDestroy": { "tpl": ["fixture"], "dyn": [], }, "BM_TransportStreamSend": { "tpl": [], "dyn": ["request_size"], }, "BM_TransportStreamRecv": { "tpl": [], "dyn": ["request_size"], }, "BM_StreamingPingPongWithCoalescingApi": { "tpl": ["fixture", "client_mutator", "server_mutator"], "dyn": ["request_size", "request_count", "end_of_stream"], }, "BM_Base16SomeStuff": { "tpl": [], "dyn": ["request_size"], }, } def numericalize(s): """Convert abbreviations like '100M' or '10k' to a number.""" if not s: return "" if s[-1] == "k": return float(s[:-1]) * 1024 if s[-1] == "M": return float(s[:-1]) * 1024 * 1024 if 0 <= (ord(s[-1]) - ord("0")) <= 9: return float(s) assert "not a number: %s" % s def parse_name(name): cpp_name = name if "<" not in name and "/" not in name and name not in _BM_SPECS: return {"name": name, "cpp_name": name} rest = name out = {} tpl_args = [] dyn_args = [] if "<" in rest: tpl_bit = rest[rest.find("<") + 1 : rest.rfind(">")] arg = "" nesting = 0 for c in tpl_bit: if c == "<": nesting += 1 arg += c elif c == ">": nesting -= 1 arg += c elif c == ",": if nesting == 0: tpl_args.append(arg.strip()) arg = "" else: arg += c else: arg += c tpl_args.append(arg.strip()) rest = rest[: rest.find("<")] + rest[rest.rfind(">") + 1 :] if "/" in rest: s = rest.split("/") rest = s[0] dyn_args = s[1:] name = rest assert name in _BM_SPECS, "_BM_SPECS needs to be expanded for %s" % name assert len(dyn_args) == len(_BM_SPECS[name]["dyn"]) assert len(tpl_args) == len(_BM_SPECS[name]["tpl"]) out["name"] = name out["cpp_name"] = cpp_name out.update( dict( (k, numericalize(v)) for k, v in zip(_BM_SPECS[name]["dyn"], dyn_args) ) ) out.update(dict(zip(_BM_SPECS[name]["tpl"], tpl_args))) return out def expand_json(js): if not js: raise StopIteration() for bm in js["benchmarks"]: if bm["name"].endswith("_stddev") or bm["name"].endswith("_mean"): continue context = js["context"] if "label" in bm: labels_list = [ s.split(":") for s in bm["label"].strip().split(" ") if len(s) and s[0] != "#" ] for el in labels_list: el[0] = el[0].replace("/iter", "_per_iteration") labels = dict(labels_list) else: labels = {} # TODO(jtattermusch): grabbing kokoro env values shouldn't be buried # deep in the JSON conversion logic. # Link the data to a kokoro job run by adding # well known kokoro env variables as metadata for each row row = { "jenkins_build": os.environ.get("KOKORO_BUILD_NUMBER", ""), "jenkins_job": os.environ.get("KOKORO_JOB_NAME", ""), } row.update(context) row.update(bm) row.update(parse_name(row["name"])) row.update(labels) yield row