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