mirror of https://github.com/grpc/grpc.git
[experiments] Add experiment framework (#30775)
* [experiments] Add experiment framework * auto-ci-config * fix * fix * Automated change: Fix sanity tests * support different configs * Automated change: Fix sanity tests * cleaner generated code * Automated change: Fix sanity tests * enforce expiry * make sure expiry isnt far in the future * fix * Automated change: Fix sanity tests * remove specified testing * cleaner * fix * fix * Automated change: Fix sanity tests * fix * fix * docstring * clean up code * Automated change: Fix sanity tests * review feedback * build fix * Automated change: Fix sanity tests * ownership Co-authored-by: ctiller <ctiller@users.noreply.github.com>pull/30807/head
parent
622dd886e6
commit
c580d0d9a7
26 changed files with 460 additions and 26 deletions
@ -0,0 +1,18 @@ |
|||||||
|
# Copyright 2022 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. |
||||||
|
|
||||||
|
# Automatically generated by tools/codegen/core/gen_experiments.py |
||||||
|
|
||||||
|
"""Dictionary of tags to experiments so we know when to test different experiments.""" |
||||||
|
EXPERIMENTS = {"endpoint_test": ["tcp_frame_size_tuning"], "core_end2end_test": ["tcp_frame_size_tuning"]} |
@ -0,0 +1,47 @@ |
|||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
// Automatically generated by tools/codegen/core/gen_experiments.py
|
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/lib/experiments/experiments.h" |
||||||
|
|
||||||
|
#include "src/core/lib/gprpp/global_config.h" |
||||||
|
|
||||||
|
namespace { |
||||||
|
const char* const description_tcp_frame_size_tuning = |
||||||
|
"If set, enables TCP to use RPC size estimation made by higher layers. TCP " |
||||||
|
"would not indicate completion of a read operation until a specified " |
||||||
|
"number of bytes have been read over the socket. Buffers are also " |
||||||
|
"allocated according to estimated RPC sizes."; |
||||||
|
} |
||||||
|
|
||||||
|
GPR_GLOBAL_CONFIG_DEFINE_BOOL(grpc_experimental_enable_tcp_frame_size_tuning, |
||||||
|
false, description_tcp_frame_size_tuning); |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
bool IsTcpFrameSizeTuningEnabled() { |
||||||
|
static const bool enabled = |
||||||
|
GPR_GLOBAL_CONFIG_GET(grpc_experimental_enable_tcp_frame_size_tuning); |
||||||
|
return enabled; |
||||||
|
} |
||||||
|
|
||||||
|
const ExperimentMetadata g_experiment_metadata[] = { |
||||||
|
{"tcp_frame_size_tuning", description_tcp_frame_size_tuning, false, |
||||||
|
IsTcpFrameSizeTuningEnabled}, |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace grpc_core
|
@ -0,0 +1,40 @@ |
|||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
// Automatically generated by tools/codegen/core/gen_experiments.py
|
||||||
|
|
||||||
|
#ifndef GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H |
||||||
|
#define GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include <stddef.h> |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
bool IsTcpFrameSizeTuningEnabled(); |
||||||
|
|
||||||
|
struct ExperimentMetadata { |
||||||
|
const char* name; |
||||||
|
const char* description; |
||||||
|
bool default_value; |
||||||
|
bool (*is_enabled)(); |
||||||
|
}; |
||||||
|
|
||||||
|
constexpr const size_t kNumExperiments = 1; |
||||||
|
extern const ExperimentMetadata g_experiment_metadata[kNumExperiments]; |
||||||
|
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
#endif // GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H
|
@ -0,0 +1,37 @@ |
|||||||
|
# Copyright 2022 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 of each entry: |
||||||
|
# name: name of the experiment |
||||||
|
# description: description of the experiment |
||||||
|
# default: boolean - does this experiment default on |
||||||
|
# expiry: when is the next time this experiment *must* be updated |
||||||
|
# (date, YYYY/MM/DD) |
||||||
|
# test_tags: a set of bazel tags, that if a test declares them signals |
||||||
|
# that that test should be run with this experiment enabled in CI |
||||||
|
# |
||||||
|
# Well known test tags: |
||||||
|
# core_end2end_tests: all tests, fixtures in the core end2end suite |
||||||
|
# endpoint_test: endpoint related iomgr tests |
||||||
|
|
||||||
|
- name: tcp_frame_size_tuning |
||||||
|
description: |
||||||
|
If set, enables TCP to use RPC size estimation made by higher layers. |
||||||
|
TCP would not indicate completion of a read operation until a specified |
||||||
|
number of bytes have been read over the socket. |
||||||
|
Buffers are also allocated according to estimated RPC sizes. |
||||||
|
default: false |
||||||
|
expiry: 2022/09/01 |
||||||
|
owner: ctiller@google.com |
||||||
|
test_tags: ["endpoint_test", "core_end2end_test"] |
@ -0,0 +1,218 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
|
||||||
|
# Copyright 2022 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. |
||||||
|
""" |
||||||
|
Generate experiment related code artifacts. |
||||||
|
|
||||||
|
Invoke as: tools/codegen/core/gen_experiments.py |
||||||
|
Experiment definitions are in src/core/lib/experiments/experiments.yaml |
||||||
|
""" |
||||||
|
|
||||||
|
from __future__ import print_function |
||||||
|
|
||||||
|
import collections |
||||||
|
import ctypes |
||||||
|
import datetime |
||||||
|
import json |
||||||
|
import math |
||||||
|
import os |
||||||
|
import re |
||||||
|
import sys |
||||||
|
|
||||||
|
import yaml |
||||||
|
|
||||||
|
with open('src/core/lib/experiments/experiments.yaml') as f: |
||||||
|
attrs = yaml.load(f.read(), Loader=yaml.FullLoader) |
||||||
|
|
||||||
|
error = False |
||||||
|
today = datetime.date.today() |
||||||
|
two_quarters_from_now = today + datetime.timedelta(days=180) |
||||||
|
for attr in attrs: |
||||||
|
if 'name' not in attr: |
||||||
|
print("experiment with no name: %r" % attr) |
||||||
|
error = True |
||||||
|
continue # can't run other diagnostics because we don't know a name |
||||||
|
if 'description' not in attr: |
||||||
|
print("no description for experiment %s" % attr['name']) |
||||||
|
error = True |
||||||
|
if 'default' not in attr: |
||||||
|
print("no default for experiment %s" % attr['name']) |
||||||
|
error = True |
||||||
|
if 'expiry' not in attr: |
||||||
|
print("no expiry for experiment %s" % attr['name']) |
||||||
|
error = True |
||||||
|
if 'owner' not in attr: |
||||||
|
print("no owner for experiment %s" % attr['name']) |
||||||
|
error = True |
||||||
|
expiry = datetime.datetime.strptime(attr['expiry'], '%Y/%m/%d').date() |
||||||
|
if expiry < today: |
||||||
|
print("experiment %s expired on %s" % (attr['name'], attr['expiry'])) |
||||||
|
error = True |
||||||
|
if expiry > two_quarters_from_now: |
||||||
|
print("experiment %s expires far in the future on %s" % |
||||||
|
(attr['name'], attr['expiry'])) |
||||||
|
print("expiry should be no more than two quarters from now") |
||||||
|
error = True |
||||||
|
|
||||||
|
if error: |
||||||
|
sys.exit(1) |
||||||
|
|
||||||
|
|
||||||
|
def c_str(s, encoding='ascii'): |
||||||
|
if isinstance(s, str): |
||||||
|
s = s.encode(encoding) |
||||||
|
result = '' |
||||||
|
for c in s: |
||||||
|
c = chr(c) if isinstance(c, int) else c |
||||||
|
if not (32 <= ord(c) < 127) or c in ('\\', '"'): |
||||||
|
result += '\\%03o' % ord(c) |
||||||
|
else: |
||||||
|
result += c |
||||||
|
return '"' + result + '"' |
||||||
|
|
||||||
|
|
||||||
|
def snake_to_pascal(s): |
||||||
|
return ''.join(x.capitalize() for x in s.split('_')) |
||||||
|
|
||||||
|
|
||||||
|
# utility: print a big comment block into a set of files |
||||||
|
def put_banner(files, banner, prefix): |
||||||
|
for f in files: |
||||||
|
for line in banner: |
||||||
|
print('%s %s' % (prefix, line), file=f) |
||||||
|
print(file=f) |
||||||
|
|
||||||
|
|
||||||
|
def put_copyright(file, prefix): |
||||||
|
# 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([file], [line[2:].rstrip() for line in copyright], prefix) |
||||||
|
|
||||||
|
|
||||||
|
EXPERIMENT_METADATA = """struct ExperimentMetadata { |
||||||
|
const char* name; |
||||||
|
const char* description; |
||||||
|
bool default_value; |
||||||
|
bool (*is_enabled)(); |
||||||
|
};""" |
||||||
|
|
||||||
|
with open('src/core/lib/experiments/experiments.h', 'w') as H: |
||||||
|
put_copyright(H, "//") |
||||||
|
|
||||||
|
put_banner( |
||||||
|
[H], |
||||||
|
["Automatically generated by tools/codegen/core/gen_experiments.py"], |
||||||
|
"//") |
||||||
|
|
||||||
|
print("#ifndef GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H) |
||||||
|
print("#define GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H) |
||||||
|
print(file=H) |
||||||
|
print("#include <grpc/support/port_platform.h>", file=H) |
||||||
|
print(file=H) |
||||||
|
print("#include <stddef.h>", file=H) |
||||||
|
print(file=H) |
||||||
|
print("namespace grpc_core {", file=H) |
||||||
|
print(file=H) |
||||||
|
for attr in attrs: |
||||||
|
print("bool Is%sEnabled();" % snake_to_pascal(attr['name']), file=H) |
||||||
|
print(file=H) |
||||||
|
print(EXPERIMENT_METADATA, file=H) |
||||||
|
print(file=H) |
||||||
|
print("constexpr const size_t kNumExperiments = %d;" % len(attrs), file=H) |
||||||
|
print( |
||||||
|
"extern const ExperimentMetadata g_experiment_metadata[kNumExperiments];", |
||||||
|
file=H) |
||||||
|
print(file=H) |
||||||
|
print("} // namespace grpc_core", file=H) |
||||||
|
print(file=H) |
||||||
|
print("#endif // GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H) |
||||||
|
|
||||||
|
with open('src/core/lib/experiments/experiments.cc', 'w') as C: |
||||||
|
put_copyright(C, "//") |
||||||
|
|
||||||
|
put_banner( |
||||||
|
[C], |
||||||
|
["Automatically generated by tools/codegen/core/gen_experiments.py"], |
||||||
|
"//") |
||||||
|
|
||||||
|
print("#include <grpc/support/port_platform.h>", file=C) |
||||||
|
print("#include \"src/core/lib/experiments/experiments.h\"", file=C) |
||||||
|
print("#include \"src/core/lib/gprpp/global_config.h\"", file=C) |
||||||
|
print(file=C) |
||||||
|
print("namespace {", file=C) |
||||||
|
for attr in attrs: |
||||||
|
print("const char* const description_%s = %s;" % |
||||||
|
(attr['name'], c_str(attr['description'])), |
||||||
|
file=C) |
||||||
|
print("}", file=C) |
||||||
|
print(file=C) |
||||||
|
for attr in attrs: |
||||||
|
print( |
||||||
|
"GPR_GLOBAL_CONFIG_DEFINE_BOOL(grpc_experimental_enable_%s, %s, description_%s);" |
||||||
|
% (attr['name'], 'true' if attr['default'] else 'false', |
||||||
|
attr['name']), |
||||||
|
file=C) |
||||||
|
print(file=C) |
||||||
|
print("namespace grpc_core {", file=C) |
||||||
|
print(file=C) |
||||||
|
for attr in attrs: |
||||||
|
print("bool Is%sEnabled() {" % snake_to_pascal(attr['name']), file=C) |
||||||
|
print( |
||||||
|
" static const bool enabled = GPR_GLOBAL_CONFIG_GET(grpc_experimental_enable_%s);" |
||||||
|
% attr['name'], |
||||||
|
file=C) |
||||||
|
print(" return enabled;", file=C) |
||||||
|
print("}", file=C) |
||||||
|
print(file=C) |
||||||
|
print("const ExperimentMetadata g_experiment_metadata[] = {", file=C) |
||||||
|
for attr in attrs: |
||||||
|
print(" {%s, description_%s, %s, Is%sEnabled}," % |
||||||
|
(c_str(attr['name']), attr['name'], 'true' |
||||||
|
if attr['default'] else 'false', snake_to_pascal(attr['name'])), |
||||||
|
file=C) |
||||||
|
print("};", file=C) |
||||||
|
print(file=C) |
||||||
|
print("} // namespace grpc_core", file=C) |
||||||
|
|
||||||
|
tags_to_experiments = collections.defaultdict(list) |
||||||
|
for attr in attrs: |
||||||
|
for tag in attr['test_tags']: |
||||||
|
tags_to_experiments[tag].append(attr['name']) |
||||||
|
|
||||||
|
with open('bazel/experiments.bzl', 'w') as B: |
||||||
|
put_copyright(B, "#") |
||||||
|
|
||||||
|
put_banner( |
||||||
|
[B], |
||||||
|
["Automatically generated by tools/codegen/core/gen_experiments.py"], |
||||||
|
"#") |
||||||
|
|
||||||
|
print( |
||||||
|
"\"\"\"Dictionary of tags to experiments so we know when to test different experiments.\"\"\"", |
||||||
|
file=B) |
||||||
|
|
||||||
|
print("EXPERIMENTS=%r" % dict(tags_to_experiments), file=B) |
Loading…
Reference in new issue