[benchmarks] Remove stats integration (#30900)

* remove old stats cruft

* remove

* remove

* fix

* fix
pull/30909/head
Craig Tiller 2 years ago committed by GitHub
parent b9dfcc092e
commit 1f1f923a72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      Makefile
  2. 11
      build_handwritten.yaml
  3. 15
      tools/bazel.rc
  4. 120
      tools/codegen/core/gen_stats_data.py
  5. 9
      tools/profiling/microbenchmarks/bm_diff/bm_build.py
  6. 22
      tools/profiling/microbenchmarks/bm_diff/bm_diff.py
  7. 14
      tools/profiling/microbenchmarks/bm_diff/bm_main.py
  8. 5
      tools/profiling/microbenchmarks/bm_diff/bm_run.py
  9. 19
      tools/profiling/microbenchmarks/bm_json.py
  10. 80
      tools/profiling/perf/run_perf_unconstrained.sh
  11. 9
      tools/run_tests/generated/configs.json
  12. 1
      tools/run_tests/performance/bq_upload_result.py
  13. 172
      tools/run_tests/performance/massage_qps_stats.py
  14. 62
      tools/run_tests/performance/massage_qps_stats_helpers.py
  15. 520
      tools/run_tests/performance/scenario_result_schema.json
  16. 132
      tools/run_tests/run_microbenchmark.py

26
Makefile generated

@ -107,23 +107,6 @@ CFLAGS_c++-compat = -Wc++-compat
CPPFLAGS_c++-compat = -O0
DEFINES_c++-compat = _DEBUG DEBUG
VALID_CONFIG_counters = 1
CC_counters = $(DEFAULT_CC)
CXX_counters = $(DEFAULT_CXX)
LD_counters = $(DEFAULT_CC)
LDXX_counters = $(DEFAULT_CXX)
CPPFLAGS_counters = -O2 -DGPR_LOW_LEVEL_COUNTERS
DEFINES_counters = NDEBUG
VALID_CONFIG_counters_with_memory_counter = 1
CC_counters_with_memory_counter = $(DEFAULT_CC)
CXX_counters_with_memory_counter = $(DEFAULT_CXX)
LD_counters_with_memory_counter = $(DEFAULT_CC)
LDXX_counters_with_memory_counter = $(DEFAULT_CXX)
CPPFLAGS_counters_with_memory_counter = -O2 -DGPR_LOW_LEVEL_COUNTERS -DGPR_WRAP_MEMORY_COUNTER
LDFLAGS_counters_with_memory_counter = -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free
DEFINES_counters_with_memory_counter = NDEBUG
VALID_CONFIG_dbg = 1
CC_dbg = $(DEFAULT_CC)
CXX_dbg = $(DEFAULT_CXX)
@ -177,15 +160,6 @@ CPPFLAGS_msan = -O0 -stdlib=libc++ -fsanitize-coverage=edge,trace-pc-guard -fsan
LDFLAGS_msan = -stdlib=libc++ -fsanitize=memory -DGTEST_HAS_TR1_TUPLE=0 -DGTEST_USE_OWN_TR1_TUPLE=1 -fPIE -pie $(if $(JENKINS_BUILD),-Wl$(comma)-Ttext-segment=0x7e0000000000,)
DEFINES_msan = NDEBUG
VALID_CONFIG_mutrace = 1
CC_mutrace = $(DEFAULT_CC)
CXX_mutrace = $(DEFAULT_CXX)
LD_mutrace = $(DEFAULT_CC)
LDXX_mutrace = $(DEFAULT_CXX)
CPPFLAGS_mutrace = -O3 -fno-omit-frame-pointer
LDFLAGS_mutrace = -rdynamic
DEFINES_mutrace = NDEBUG
VALID_CONFIG_noexcept = 1
CC_noexcept = $(DEFAULT_CC)
CXX_noexcept = $(DEFAULT_CXX)

@ -68,13 +68,6 @@ configs:
CFLAGS: -Wc++-compat
CPPFLAGS: -O0
DEFINES: _DEBUG DEBUG
counters:
CPPFLAGS: -O2 -DGPR_LOW_LEVEL_COUNTERS
DEFINES: NDEBUG
counters_with_memory_counter:
CPPFLAGS: -O2 -DGPR_LOW_LEVEL_COUNTERS -DGPR_WRAP_MEMORY_COUNTER
DEFINES: NDEBUG
LDFLAGS: -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free
dbg:
CPPFLAGS: -O0
DEFINES: _DEBUG DEBUG
@ -114,10 +107,6 @@ configs:
compile_the_world: true
test_environ:
MSAN_OPTIONS: poison_in_dtor=1
mutrace:
CPPFLAGS: -O3 -fno-omit-frame-pointer
DEFINES: NDEBUG
LDFLAGS: -rdynamic
noexcept:
CPPFLAGS: -O2 -Wframe-larger-than=16384
CXXFLAGS: -fno-exceptions

@ -119,20 +119,5 @@ build:python_single_threaded_unary_stream --test_env="GRPC_SINGLE_THREADED_UNARY
build:python_poller_engine --test_env="GRPC_ASYNCIO_ENGINE=poller"
# "counters" config is based on a legacy config provided by the Makefile (see definition in build_handwritten.yaml).
# It is only used in microbenchmarks.
# TODO(jtattermusch): get rid of the "counters" config when possible
build:counters --compilation_mode=opt
build:counters --copt=-Wframe-larger-than=16384
build:counters --copt=-DGPR_LOW_LEVEL_COUNTERS
# "mutrace" config is based on a legacy config provided by the Makefile (see definition in build_handwritten.yaml).
# It is only used in microbenchmarks (see tools/run_tests/run_microbenchmark.py)
# TODO(jtattermusch): get rid of the "mutrace" config when possible
build:mutrace --copt=-O3
build:mutrace --copt=-fno-omit-frame-pointer
build:mutrace --copt=-DNDEBUG
build:mutrace --linkopt=-rdynamic
# Compile database generation config
build:compdb --build_tag_filters=-nocompdb --features=-layering_check

@ -422,123 +422,3 @@ with open('src/core/lib/debug/stats_data.cc', 'w') as C:
(histogram.max, histogram.buckets)
for histogram in inst_map['Histogram'])),
file=C)
# patch qps_test bigquery schema
RECORD_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 el
def RemoveCoreFields(js):
new_fields = []
for field in js['fields']:
if not field['name'].startswith('core_'):
new_fields.append(field)
js['fields'] = new_fields
RemoveCoreFields(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 query
with 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(line.rstrip(), file=P)
break
for line in my_source:
if line[0] != '#':
break
print(line.rstrip(), file=P)
print(file=P)
print('# Autogenerated by tools/codegen/core/gen_stats_data.py', file=P)
print(file=P)
print('import massage_qps_stats_helpers', file=P)
print('def massage_qps_stats(scenario_result):', file=P)
print(
' for stats in scenario_result["serverStats"] + scenario_result["clientStats"]:',
file=P)
print(' if "coreStats" in stats:', file=P)
print(
' # Get rid of the "coreStats" element and replace it by statistics',
file=P)
print(' # that correspond to columns in the bigquery schema.', file=P)
print(' core_stats = stats["coreStats"]', file=P)
print(' del stats["coreStats"]', file=P)
for counter in inst_map['Counter']:
print(
' stats["core_%s"] = massage_qps_stats_helpers.counter(core_stats, "%s")'
% (counter.name, counter.name),
file=P)
for i, histogram in enumerate(inst_map['Histogram']):
print(
' h = massage_qps_stats_helpers.histogram(core_stats, "%s")' %
histogram.name,
file=P)
print(
' stats["core_%s"] = ",".join("%%f" %% x for x in h.buckets)' %
histogram.name,
file=P)
print(
' stats["core_%s_bkts"] = ",".join("%%f" %% x for x in h.boundaries)'
% histogram.name,
file=P)
for pctl in RECORD_EXPLICIT_PERCENTILES:
print(
' stats["core_%s_%dp"] = massage_qps_stats_helpers.percentile(h.buckets, %d, h.boundaries)'
% (histogram.name, pctl, pctl),
file=P)
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(',\n'.join('%s:%s' % x for x in columns), file=S)

@ -46,9 +46,6 @@ def _args():
help=
'Unique name of this build. To be used as a handle to pass to the other bm* scripts'
)
argp.add_argument('--counters', dest='counters', action='store_true')
argp.add_argument('--no-counters', dest='counters', action='store_false')
argp.set_defaults(counters=True)
args = argp.parse_args()
assert args.name
return args
@ -75,14 +72,12 @@ def _build_config_and_copy(cfg, benchmarks, dest_dir):
] + [cfg_dir])
def build(name, benchmarks, jobs, counters):
def build(name, benchmarks, jobs):
dest_dir = 'bm_diff_%s' % name
shutil.rmtree(dest_dir, ignore_errors=True)
_build_config_and_copy('opt', benchmarks, dest_dir)
if counters:
_build_config_and_copy('counters', benchmarks, dest_dir)
if __name__ == '__main__':
args = _args()
build(args.name, args.benchmarks, args.jobs, args.counters)
build(args.name, args.benchmarks, args.jobs)

@ -70,9 +70,6 @@ def _args():
type=str,
default="",
help='Regex to filter benchmarks run')
argp.add_argument('--counters', dest='counters', action='store_true')
argp.add_argument('--no-counters', dest='counters', action='store_false')
argp.set_defaults(counters=True)
argp.add_argument('-n', '--new', type=str, help='New benchmark name')
argp.add_argument('-o', '--old', type=str, help='Old benchmark name')
argp.add_argument('-v',
@ -161,7 +158,7 @@ def fmt_dict(d):
return ''.join([" " + k + ": " + str(d[k]) + "\n" for k in d])
def diff(bms, loops, regex, track, old, new, counters):
def diff(bms, loops, regex, track, old, new):
benchmarks = collections.defaultdict(Benchmark)
badjson_files = {}
@ -181,25 +178,12 @@ def diff(bms, loops, regex, track, old, new, counters):
js_old_opt = _read_json(
'%s.%s.opt.%s.%d.json' % (bm, stripped_line, old, loop),
badjson_files, nonexistant_files)
if counters:
js_new_ctr = _read_json(
'%s.%s.counters.%s.%d.json' %
(bm, stripped_line, new, loop), badjson_files,
nonexistant_files)
js_old_ctr = _read_json(
'%s.%s.counters.%s.%d.json' %
(bm, stripped_line, old, loop), badjson_files,
nonexistant_files)
else:
js_new_ctr = None
js_old_ctr = None
for row in bm_json.expand_json(js_new_ctr, js_new_opt):
for row in bm_json.expand_json(js_new_opt):
name = row['cpp_name']
if name.endswith('_mean') or name.endswith('_stddev'):
continue
benchmarks[name].add_sample(track, row, True)
for row in bm_json.expand_json(js_old_ctr, js_old_opt):
for row in bm_json.expand_json(js_old_opt):
name = row['cpp_name']
if name.endswith('_mean') or name.endswith('_stddev'):
continue

@ -85,9 +85,6 @@ def _args():
type=str,
default="microbenchmarks",
help='Name that Jenkins will use to comment on the PR')
argp.add_argument('--counters', dest='counters', action='store_true')
argp.add_argument('--no-counters', dest='counters', action='store_false')
argp.set_defaults(counters=True)
args = argp.parse_args()
assert args.diff_base or args.old, "One of diff_base or old must be set!"
if args.loops < 3:
@ -111,7 +108,7 @@ def eintr_be_gone(fn):
def main(args):
bm_build.build('new', args.benchmarks, args.jobs, args.counters)
bm_build.build('new', args.benchmarks, args.jobs)
old = args.old
if args.diff_base:
@ -120,24 +117,23 @@ def main(args):
['git', 'rev-parse', '--abbrev-ref', 'HEAD']).strip()
subprocess.check_call(['git', 'checkout', args.diff_base])
try:
bm_build.build(old, args.benchmarks, args.jobs, args.counters)
bm_build.build(old, args.benchmarks, args.jobs)
finally:
subprocess.check_call(['git', 'checkout', where_am_i])
subprocess.check_call(['git', 'submodule', 'update'])
jobs_list = []
jobs_list += bm_run.create_jobs('new', args.benchmarks, args.loops,
args.regex, args.counters)
args.regex)
jobs_list += bm_run.create_jobs(old, args.benchmarks, args.loops,
args.regex, args.counters)
args.regex)
# shuffle all jobs to eliminate noise from GCE CPU drift
random.shuffle(jobs_list, random.SystemRandom().random)
jobset.run(jobs_list, maxjobs=args.jobs)
diff, note, significance = bm_diff.diff(args.benchmarks, args.loops,
args.regex, args.track, old, 'new',
args.counters)
args.regex, args.track, old, 'new')
if diff:
text = '[%s] Performance differences noted:\n%s' % (
args.pr_comment_name, diff)

@ -102,14 +102,11 @@ def _collect_bm_data(bm, cfg, name, regex, idx, loops):
return jobs_list
def create_jobs(name, benchmarks, loops, regex, counters):
def create_jobs(name, benchmarks, loops, regex):
jobs_list = []
for loop in range(0, loops):
for bm in benchmarks:
jobs_list += _collect_bm_data(bm, 'opt', name, regex, loop, loops)
if counters:
jobs_list += _collect_bm_data(bm, 'counters', name, regex, loop,
loops)
random.shuffle(jobs_list, random.SystemRandom().random)
return jobs_list

@ -178,11 +178,9 @@ def parse_name(name):
return out
def expand_json(js, js2=None):
if not js and not js2:
raise StopIteration()
def expand_json(js):
if not js:
js = js2
raise StopIteration()
for bm in js['benchmarks']:
if bm['name'].endswith('_stddev') or bm['name'].endswith('_mean'):
continue
@ -210,17 +208,4 @@ def expand_json(js, js2=None):
row.update(bm)
row.update(parse_name(row['name']))
row.update(labels)
# TODO(jtattermusch): add a comment explaining what's the point
# of merging values of some of the columns js2 into the row.
# Empirically, the js contains data from "counters" config
# and js2 contains data from the "opt" config, but the point of merging
# really deserves further explanation.
if js2:
for bm2 in js2['benchmarks']:
if bm['name'] == bm2['name'] and 'already_used' not in bm2:
row['cpu_time'] = bm2['cpu_time']
row['real_time'] = bm2['real_time']
row['iterations'] = bm2['iterations']
bm2['already_used'] = True
break
yield row

@ -1,80 +0,0 @@
#!/bin/bash
# Copyright 2016 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.
# format argument via
# $ echo '{...}' | python -mjson.tool
read -r -d '' SCENARIOS_JSON_ARG <<'EOF'
{
"scenarios": [
{
"benchmark_seconds": 60,
"warmup_seconds": 5,
"client_config": {
"client_channels": 100,
"client_type": "ASYNC_CLIENT",
"histogram_params": {
"max_possible": 60000000000.0,
"resolution": 0.01
},
"load_params": {
"closed_loop": {}
},
"outstanding_rpcs_per_channel": 100,
"payload_config": {
"simple_params": {
"req_size": 0,
"resp_size": 0
}
},
"rpc_type": "UNARY",
"security_params": null
},
"name": "name_goes_here",
"num_clients": 1,
"num_servers": 1,
"server_config": {
"security_params": null,
"server_type": "ASYNC_SERVER"
},
"spawn_local_worker_count": -2
}
]
}
EOF
set -ex
cd $(dirname $0)/../../..
CPUS=`python -c 'import multiprocessing; print multiprocessing.cpu_count()'`
# try to use pypy for generating reports
# each trace dumps 7-8gig of text to disk, and processing this into a report is
# heavyweight - so any speed boost is worthwhile
# TODO(ctiller): consider rewriting report generation in C++ for performance
if which pypy >/dev/null; then
PYTHON=pypy
else
PYTHON=python2.7
fi
export config=mutrace
make CONFIG=$config -j$CPUS qps_json_driver
sudo perf record -F 997 -g bins/$config/qps_json_driver --scenarios_json="$SCENARIOS_JSON_ARG"
sudo perf report

@ -22,12 +22,6 @@
{
"config": "c++-compat"
},
{
"config": "counters"
},
{
"config": "counters_with_memory_counter"
},
{
"config": "dbg"
},
@ -58,9 +52,6 @@
"MSAN_OPTIONS": "poison_in_dtor=1"
}
},
{
"config": "mutrace"
},
{
"config": "noexcept"
},

@ -148,7 +148,6 @@ def _flatten_result_inplace(scenario_result):
'serverCpuUsage', None)
scenario_result['summary'].pop('successfulRequestsPerSecond', None)
scenario_result['summary'].pop('failedRequestsPerSecond', None)
massage_qps_stats.massage_qps_stats(scenario_result)
def _populate_metadata_inplace(scenario_result):

@ -1,172 +0,0 @@
# 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.
# Autogenerated by tools/codegen/core/gen_stats_data.py
import massage_qps_stats_helpers
def massage_qps_stats(scenario_result):
for stats in scenario_result["serverStats"] + scenario_result["clientStats"]:
if "coreStats" in stats:
# Get rid of the "coreStats" element and replace it by statistics
# that correspond to columns in the bigquery schema.
core_stats = stats["coreStats"]
del stats["coreStats"]
stats[
"core_client_calls_created"] = massage_qps_stats_helpers.counter(
core_stats, "client_calls_created")
stats[
"core_server_calls_created"] = massage_qps_stats_helpers.counter(
core_stats, "server_calls_created")
stats[
"core_client_channels_created"] = massage_qps_stats_helpers.counter(
core_stats, "client_channels_created")
stats[
"core_client_subchannels_created"] = massage_qps_stats_helpers.counter(
core_stats, "client_subchannels_created")
stats[
"core_server_channels_created"] = massage_qps_stats_helpers.counter(
core_stats, "server_channels_created")
stats["core_syscall_write"] = massage_qps_stats_helpers.counter(
core_stats, "syscall_write")
stats["core_syscall_read"] = massage_qps_stats_helpers.counter(
core_stats, "syscall_read")
stats["core_tcp_read_alloc_8k"] = massage_qps_stats_helpers.counter(
core_stats, "tcp_read_alloc_8k")
stats[
"core_tcp_read_alloc_64k"] = massage_qps_stats_helpers.counter(
core_stats, "tcp_read_alloc_64k")
stats[
"core_http2_settings_writes"] = massage_qps_stats_helpers.counter(
core_stats, "http2_settings_writes")
stats["core_http2_pings_sent"] = massage_qps_stats_helpers.counter(
core_stats, "http2_pings_sent")
stats[
"core_http2_writes_begun"] = massage_qps_stats_helpers.counter(
core_stats, "http2_writes_begun")
stats[
"core_http2_transport_stalls"] = massage_qps_stats_helpers.counter(
core_stats, "http2_transport_stalls")
stats[
"core_http2_stream_stalls"] = massage_qps_stats_helpers.counter(
core_stats, "http2_stream_stalls")
stats["core_cq_pluck_creates"] = massage_qps_stats_helpers.counter(
core_stats, "cq_pluck_creates")
stats["core_cq_next_creates"] = massage_qps_stats_helpers.counter(
core_stats, "cq_next_creates")
stats[
"core_cq_callback_creates"] = massage_qps_stats_helpers.counter(
core_stats, "cq_callback_creates")
h = massage_qps_stats_helpers.histogram(core_stats,
"call_initial_size")
stats["core_call_initial_size"] = ",".join(
"%f" % x for x in h.buckets)
stats["core_call_initial_size_bkts"] = ",".join(
"%f" % x for x in h.boundaries)
stats[
"core_call_initial_size_50p"] = massage_qps_stats_helpers.percentile(
h.buckets, 50, h.boundaries)
stats[
"core_call_initial_size_95p"] = massage_qps_stats_helpers.percentile(
h.buckets, 95, h.boundaries)
stats[
"core_call_initial_size_99p"] = massage_qps_stats_helpers.percentile(
h.buckets, 99, h.boundaries)
h = massage_qps_stats_helpers.histogram(core_stats,
"tcp_write_size")
stats["core_tcp_write_size"] = ",".join("%f" % x for x in h.buckets)
stats["core_tcp_write_size_bkts"] = ",".join(
"%f" % x for x in h.boundaries)
stats[
"core_tcp_write_size_50p"] = massage_qps_stats_helpers.percentile(
h.buckets, 50, h.boundaries)
stats[
"core_tcp_write_size_95p"] = massage_qps_stats_helpers.percentile(
h.buckets, 95, h.boundaries)
stats[
"core_tcp_write_size_99p"] = massage_qps_stats_helpers.percentile(
h.buckets, 99, h.boundaries)
h = massage_qps_stats_helpers.histogram(core_stats,
"tcp_write_iov_size")
stats["core_tcp_write_iov_size"] = ",".join(
"%f" % x for x in h.buckets)
stats["core_tcp_write_iov_size_bkts"] = ",".join(
"%f" % x for x in h.boundaries)
stats[
"core_tcp_write_iov_size_50p"] = massage_qps_stats_helpers.percentile(
h.buckets, 50, h.boundaries)
stats[
"core_tcp_write_iov_size_95p"] = massage_qps_stats_helpers.percentile(
h.buckets, 95, h.boundaries)
stats[
"core_tcp_write_iov_size_99p"] = massage_qps_stats_helpers.percentile(
h.buckets, 99, h.boundaries)
h = massage_qps_stats_helpers.histogram(core_stats, "tcp_read_size")
stats["core_tcp_read_size"] = ",".join("%f" % x for x in h.buckets)
stats["core_tcp_read_size_bkts"] = ",".join(
"%f" % x for x in h.boundaries)
stats[
"core_tcp_read_size_50p"] = massage_qps_stats_helpers.percentile(
h.buckets, 50, h.boundaries)
stats[
"core_tcp_read_size_95p"] = massage_qps_stats_helpers.percentile(
h.buckets, 95, h.boundaries)
stats[
"core_tcp_read_size_99p"] = massage_qps_stats_helpers.percentile(
h.buckets, 99, h.boundaries)
h = massage_qps_stats_helpers.histogram(core_stats,
"tcp_read_offer")
stats["core_tcp_read_offer"] = ",".join("%f" % x for x in h.buckets)
stats["core_tcp_read_offer_bkts"] = ",".join(
"%f" % x for x in h.boundaries)
stats[
"core_tcp_read_offer_50p"] = massage_qps_stats_helpers.percentile(
h.buckets, 50, h.boundaries)
stats[
"core_tcp_read_offer_95p"] = massage_qps_stats_helpers.percentile(
h.buckets, 95, h.boundaries)
stats[
"core_tcp_read_offer_99p"] = massage_qps_stats_helpers.percentile(
h.buckets, 99, h.boundaries)
h = massage_qps_stats_helpers.histogram(core_stats,
"tcp_read_offer_iov_size")
stats["core_tcp_read_offer_iov_size"] = ",".join(
"%f" % x for x in h.buckets)
stats["core_tcp_read_offer_iov_size_bkts"] = ",".join(
"%f" % x for x in h.boundaries)
stats[
"core_tcp_read_offer_iov_size_50p"] = massage_qps_stats_helpers.percentile(
h.buckets, 50, h.boundaries)
stats[
"core_tcp_read_offer_iov_size_95p"] = massage_qps_stats_helpers.percentile(
h.buckets, 95, h.boundaries)
stats[
"core_tcp_read_offer_iov_size_99p"] = massage_qps_stats_helpers.percentile(
h.buckets, 99, h.boundaries)
h = massage_qps_stats_helpers.histogram(core_stats,
"http2_send_message_size")
stats["core_http2_send_message_size"] = ",".join(
"%f" % x for x in h.buckets)
stats["core_http2_send_message_size_bkts"] = ",".join(
"%f" % x for x in h.boundaries)
stats[
"core_http2_send_message_size_50p"] = massage_qps_stats_helpers.percentile(
h.buckets, 50, h.boundaries)
stats[
"core_http2_send_message_size_95p"] = massage_qps_stats_helpers.percentile(
h.buckets, 95, h.boundaries)
stats[
"core_http2_send_message_size_99p"] = massage_qps_stats_helpers.percentile(
h.buckets, 99, h.boundaries)

@ -1,62 +0,0 @@
# 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 collections
def _threshold_for_count_below(buckets, boundaries, count_below):
count_so_far = 0
for lower_idx in range(0, len(buckets)):
count_so_far += buckets[lower_idx]
if count_so_far >= count_below:
break
if count_so_far == count_below:
# this bucket hits the threshold exactly... we should be midway through
# any run of zero values following the bucket
for upper_idx in range(lower_idx + 1, len(buckets)):
if buckets[upper_idx] != 0:
break
return (boundaries[lower_idx] + boundaries[upper_idx]) / 2.0
else:
# treat values as uniform throughout the bucket, and find where this value
# should lie
lower_bound = boundaries[lower_idx]
upper_bound = boundaries[lower_idx + 1]
return (upper_bound - (upper_bound - lower_bound) *
(count_so_far - count_below) / float(buckets[lower_idx]))
def percentile(buckets, pctl, boundaries):
return _threshold_for_count_below(buckets, boundaries,
sum(buckets) * pctl / 100.0)
def counter(core_stats, name):
for stat in core_stats['metrics']:
if stat['name'] == name:
return int(stat.get('count', 0))
Histogram = collections.namedtuple('Histogram', 'buckets boundaries')
def histogram(core_stats, name):
for stat in core_stats['metrics']:
if stat['name'] == name:
buckets = []
boundaries = []
for b in stat['histogram']['buckets']:
buckets.append(int(b.get('count', 0)))
boundaries.append(int(b.get('start', 0)))
return Histogram(buckets=buckets, boundaries=boundaries)

@ -109,266 +109,6 @@
"mode": "NULLABLE",
"name": "cqPollCount",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_client_calls_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_server_calls_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_client_channels_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_client_subchannels_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_server_channels_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_syscall_write",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_syscall_read",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_alloc_8k",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_alloc_64k",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_settings_writes",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_pings_sent",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_writes_begun",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_transport_stalls",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_stream_stalls",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_cq_pluck_creates",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_cq_next_creates",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_cq_callback_creates",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_99p",
"type": "FLOAT"
}
],
"mode": "REPEATED",
@ -396,266 +136,6 @@
"mode": "NULLABLE",
"name": "cqPollCount",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_client_calls_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_server_calls_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_client_channels_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_client_subchannels_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_server_channels_created",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_syscall_write",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_syscall_read",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_alloc_8k",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_alloc_64k",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_settings_writes",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_pings_sent",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_writes_begun",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_transport_stalls",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_http2_stream_stalls",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_cq_pluck_creates",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_cq_next_creates",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_cq_callback_creates",
"type": "INTEGER"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_call_initial_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_write_iov_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_tcp_read_offer_iov_size_99p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_bkts",
"type": "STRING"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_50p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_95p",
"type": "FLOAT"
},
{
"mode": "NULLABLE",
"name": "core_http2_send_message_size_99p",
"type": "FLOAT"
}
],
"mode": "REPEATED",

@ -84,112 +84,6 @@ def _bazel_build_benchmark(bm_name, cfg):
])
def collect_latency(bm_name, args):
"""generate latency profiles"""
benchmarks = []
profile_analysis = []
cleanup = []
heading('Latency Profiles: %s' % bm_name)
_bazel_build_benchmark(bm_name, 'basicprof')
for line in subprocess.check_output([
'bazel-bin/test/cpp/microbenchmarks/%s' % bm_name,
'--benchmark_list_tests'
]).decode('UTF-8').splitlines():
link(line, '%s.txt' % fnize(line))
benchmarks.append(
jobset.JobSpec([
'bazel-bin/test/cpp/microbenchmarks/%s' % bm_name,
'--benchmark_filter=^%s$' % line, '--benchmark_min_time=0.05'
],
environ={
'GRPC_LATENCY_TRACE': '%s.trace' % fnize(line)
},
shortname='profile-%s' % fnize(line)))
profile_analysis.append(
jobset.JobSpec([
sys.executable,
'tools/profiling/latency_profile/profile_analyzer.py',
'--source',
'%s.trace' % fnize(line), '--fmt', 'simple', '--out',
'reports/%s.txt' % fnize(line)
],
timeout_seconds=20 * 60,
shortname='analyze-%s' % fnize(line)))
cleanup.append(jobset.JobSpec(['rm', '%s.trace' % fnize(line)]))
# periodically flush out the list of jobs: profile_analysis jobs at least
# consume upwards of five gigabytes of ram in some cases, and so analysing
# hundreds of them at once is impractical -- but we want at least some
# concurrency or the work takes too long
if len(benchmarks) >= min(16, multiprocessing.cpu_count()):
# run up to half the cpu count: each benchmark can use up to two cores
# (one for the microbenchmark, one for the data flush)
jobset.run(benchmarks,
maxjobs=max(1,
multiprocessing.cpu_count() / 2))
jobset.run(profile_analysis, maxjobs=multiprocessing.cpu_count())
jobset.run(cleanup, maxjobs=multiprocessing.cpu_count())
benchmarks = []
profile_analysis = []
cleanup = []
# run the remaining benchmarks that weren't flushed
if len(benchmarks):
jobset.run(benchmarks, maxjobs=max(1, multiprocessing.cpu_count() / 2))
jobset.run(profile_analysis, maxjobs=multiprocessing.cpu_count())
jobset.run(cleanup, maxjobs=multiprocessing.cpu_count())
def collect_perf(bm_name, args):
"""generate flamegraphs"""
heading('Flamegraphs: %s' % bm_name)
_bazel_build_benchmark(bm_name, 'mutrace')
benchmarks = []
profile_analysis = []
cleanup = []
for line in subprocess.check_output([
'bazel-bin/test/cpp/microbenchmarks/%s' % bm_name,
'--benchmark_list_tests'
]).decode('UTF-8').splitlines():
link(line, '%s.svg' % fnize(line))
benchmarks.append(
jobset.JobSpec([
'perf', 'record', '-o',
'%s-perf.data' % fnize(line), '-g', '-F', '997',
'bazel-bin/test/cpp/microbenchmarks/%s' % bm_name,
'--benchmark_filter=^%s$' % line, '--benchmark_min_time=10'
],
shortname='perf-%s' % fnize(line)))
profile_analysis.append(
jobset.JobSpec(
[
'tools/run_tests/performance/process_local_perf_flamegraphs.sh'
],
environ={
'PERF_BASE_NAME': fnize(line),
'OUTPUT_DIR': 'reports',
'OUTPUT_FILENAME': fnize(line),
},
shortname='flame-%s' % fnize(line)))
cleanup.append(jobset.JobSpec(['rm', '%s-perf.data' % fnize(line)]))
cleanup.append(jobset.JobSpec(['rm', '%s-out.perf' % fnize(line)]))
# periodically flush out the list of jobs: temporary space required for this
# processing is large
if len(benchmarks) >= 20:
# run up to half the cpu count: each benchmark can use up to two cores
# (one for the microbenchmark, one for the data flush)
jobset.run(benchmarks, maxjobs=1)
jobset.run(profile_analysis, maxjobs=multiprocessing.cpu_count())
jobset.run(cleanup, maxjobs=multiprocessing.cpu_count())
benchmarks = []
profile_analysis = []
cleanup = []
# run the remaining benchmarks that weren't flushed
if len(benchmarks):
jobset.run(benchmarks, maxjobs=1)
jobset.run(profile_analysis, maxjobs=multiprocessing.cpu_count())
jobset.run(cleanup, maxjobs=multiprocessing.cpu_count())
def run_summary(bm_name, cfg, base_json_name):
_bazel_build_benchmark(bm_name, cfg)
cmd = [
@ -205,39 +99,15 @@ def run_summary(bm_name, cfg, base_json_name):
def collect_summary(bm_name, args):
# no counters, run microbenchmark and add summary
# both to HTML report and to console.
nocounters_heading = 'Summary: %s [no counters]' % bm_name
nocounters_heading = 'Summary: %s' % bm_name
nocounters_summary = run_summary(bm_name, 'opt', bm_name)
heading(nocounters_heading)
text(nocounters_summary)
print(nocounters_heading)
print(nocounters_summary)
# with counters, run microbenchmark and add summary
# both to HTML report and to console.
counters_heading = 'Summary: %s [with counters]' % bm_name
counters_summary = run_summary(bm_name, 'counters', bm_name)
heading(counters_heading)
text(counters_summary)
print(counters_heading)
print(counters_summary)
if args.bq_result_table:
with open('%s.csv' % bm_name, 'w') as f:
f.write(
subprocess.check_output([
'tools/profiling/microbenchmarks/bm2bq.py',
'%s.counters.json' % bm_name,
'%s.opt.json' % bm_name
]).decode('UTF-8'))
subprocess.check_call(
['bq', 'load',
'%s' % args.bq_result_table,
'%s.csv' % bm_name])
collectors = {
'latency': collect_latency,
'perf': collect_perf,
'summary': collect_summary,
}

Loading…
Cancel
Save