diff --git a/include/envoy/server/options.h b/include/envoy/server/options.h index 0b141f86830c7..e5e1ca40127a9 100644 --- a/include/envoy/server/options.h +++ b/include/envoy/server/options.h @@ -28,7 +28,12 @@ enum class Mode { */ Validate, - // TODO(rlazarus): Add a third option for "light validation": Mock out access to the filesystem. + /** + * Completely load and initialize the config, and then exit without running the listener loop. + */ + InitOnly, + + // TODO(rlazarus): Add a fourth option for "light validation": Mock out access to the filesystem. // Perform no validation of files referenced in the config, such as runtime configs, SSL certs, // etc. Validation will pass even if those files are malformed or don't exist, allowing the config // to be validated in a non-prod environment. diff --git a/source/common/common/perf_annotation.cc b/source/common/common/perf_annotation.cc index 73017cc17b6ad..2af99dc69a56e 100644 --- a/source/common/common/perf_annotation.cc +++ b/source/common/common/perf_annotation.cc @@ -122,7 +122,9 @@ std::string PerfAnnotationContext::toString() { // Create format-strings to right justify each column, e.g. {:>14} for a column of width 14. std::vector formats; for (size_t i = 0; i < num_columns; ++i) { - formats.push_back(absl::StrCat("{:>", widths[i], "}")); + // left-justify category & description, but right-justify the numeric columns. + const absl::string_view justify = (i < num_columns - 2) ? ">" : "<"; + formats.push_back(absl::StrCat("{:", justify, widths[i], "}")); } // Write out the table. diff --git a/source/common/stats/BUILD b/source/common/stats/BUILD index 5815f86227384..d2a4bc822d6cb 100644 --- a/source/common/stats/BUILD +++ b/source/common/stats/BUILD @@ -18,6 +18,7 @@ envoy_cc_library( "//include/envoy/stats:stats_interface", "//source/common/common:assert_lib", "//source/common/common:hash_lib", + "//source/common/common:perf_annotation_lib", "//source/common/common:utility_lib", "//source/common/config:well_known_names", "//source/common/protobuf", diff --git a/source/common/stats/stats_impl.cc b/source/common/stats/stats_impl.cc index 3b81301f120a0..5bc04df0025f8 100644 --- a/source/common/stats/stats_impl.cc +++ b/source/common/stats/stats_impl.cc @@ -8,6 +8,7 @@ #include "envoy/common/exception.h" +#include "common/common/perf_annotation.h" #include "common/common/utility.h" #include "common/config/well_known_names.h" @@ -108,6 +109,7 @@ TagExtractorPtr TagExtractorImpl::createTagExtractor(const std::string& name, bool TagExtractorImpl::extractTag(const std::string& stat_name, std::vector& tags, IntervalSet& remove_characters) const { + PERF_OPERATION(perf); std::smatch match; // The regex must match and contain one or more subexpressions (all after the first are ignored). if (std::regex_search(stat_name, match, regex_) && match.size() > 1) { @@ -129,8 +131,10 @@ bool TagExtractorImpl::extractTag(const std::string& stat_name, std::vector std::string::size_type start = remove_subexpr.first - stat_name.begin(); std::string::size_type end = remove_subexpr.second - stat_name.begin(); remove_characters.insert(start, end); + PERF_RECORD(perf, "re-match", name_); return true; } + PERF_RECORD(perf, "re-miss", name_); return false; } diff --git a/source/exe/BUILD b/source/exe/BUILD index d1663a4e16ffd..ef3ac152adf77 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -94,6 +94,7 @@ envoy_cc_library( ":envoy_common_lib", "//source/common/api:os_sys_calls_lib", "//source/common/common:compiler_requirements_lib", + "//source/common/common:perf_annotation_lib", "//source/server:hot_restart_lib", "//source/server:hot_restart_nop_lib", "//source/server:proto_descriptors_lib", diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index c5c70c4c864e4..31891816360fd 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -4,6 +4,7 @@ #include #include "common/common/compiler_requirements.h" +#include "common/common/perf_annotation.h" #include "common/event/libevent.h" #include "common/network/utility.h" #include "common/stats/stats_impl.h" @@ -43,6 +44,7 @@ MainCommonBase::MainCommonBase(OptionsImpl& options) : options_(options) { RELEASE_ASSERT(Envoy::Server::validateProtoDescriptors()); switch (options_.mode()) { + case Server::Mode::InitOnly: case Server::Mode::Serve: { #ifdef ENVOY_HOT_RESTART if (!options.hotRestartDisabled()) { @@ -84,6 +86,9 @@ bool MainCommonBase::run() { auto local_address = Network::Utility::getLocalAddress(options_.localAddressIpVersion()); return Server::validateConfig(options_, local_address, component_factory_); } + case Server::Mode::InitOnly: + PERF_DUMP(); + return true; } NOT_REACHED; } diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index 631b062871f6f..e76fceec728db 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -140,6 +140,8 @@ OptionsImpl::OptionsImpl(int argc, char** argv, const HotRestartVersionCb& hot_r mode_ = Server::Mode::Serve; } else if (mode.getValue() == "validate") { mode_ = Server::Mode::Validate; + } else if (mode.getValue() == "init_only") { + mode_ = Server::Mode::InitOnly; } else { const std::string message = fmt::format("error: unknown mode '{}'", mode.getValue()); std::cerr << message << std::endl; diff --git a/test/common/common/perf_annotation_test.cc b/test/common/common/perf_annotation_test.cc index 1d86e30d20d85..c8498a72096ef 100644 --- a/test/common/common/perf_annotation_test.cc +++ b/test/common/common/perf_annotation_test.cc @@ -28,13 +28,13 @@ TEST_F(PerfAnnotationTest, testMacros) { PERF_RECORD(perf, "beta", "3"); std::string report = PERF_TO_STRING(); EXPECT_TRUE(report.find(" alpha ") != std::string::npos) << report; - EXPECT_TRUE(report.find(" 0\n") != std::string::npos) << report; + EXPECT_TRUE(report.find(" 0 ") != std::string::npos) << report; EXPECT_TRUE(report.find(" beta ") != std::string::npos) << report; - EXPECT_TRUE(report.find(" 1\n") != std::string::npos) << report; + EXPECT_TRUE(report.find(" 1 ") != std::string::npos) << report; EXPECT_TRUE(report.find(" alpha ") != std::string::npos) << report; - EXPECT_TRUE(report.find(" 2\n") != std::string::npos) << report; + EXPECT_TRUE(report.find(" 2 ") != std::string::npos) << report; EXPECT_TRUE(report.find(" beta ") != std::string::npos) << report; - EXPECT_TRUE(report.find(" 3\n") != std::string::npos) << report; + EXPECT_TRUE(report.find(" 3 ") != std::string::npos) << report; PERF_DUMP(); } @@ -51,9 +51,9 @@ TEST_F(PerfAnnotationTest, testFormat) { std::string report = context->toString(); EXPECT_EQ( "Duration(us) # Calls Mean(ns) StdDev(ns) Min(ns) Max(ns) Category Description\n" - " 4600 4 1150000 129099 1000000 1300000 alpha 1\n" - " 200 1 200000 nan 200000 200000 gamma 2\n" - " 87 3 29000 1000 28000 30000 beta 3\n", + " 4600 4 1150000 129099 1000000 1300000 alpha 1 \n" + " 200 1 200000 nan 200000 200000 gamma 2 \n" + " 87 3 29000 1000 28000 30000 beta 3 \n", context->toString()); } diff --git a/test/exe/main_common_test.cc b/test/exe/main_common_test.cc index b912369a133af..50d3bd518d363 100644 --- a/test/exe/main_common_test.cc +++ b/test/exe/main_common_test.cc @@ -9,6 +9,8 @@ #include +#include "common/runtime/runtime_impl.h" + #include "exe/main_common.h" #include "server/options_impl.h" @@ -38,17 +40,33 @@ class MainCommonTest : public testing::Test { MainCommonTest() : config_file_(Envoy::TestEnvironment::getCheckedEnvVar("TEST_RUNDIR") + "/test/config/integration/google_com_proxy_port_0.v2.yaml"), - random_string_(fmt::format("{}", static_cast(getpid()))), - argv_({"envoy-static", "--base-id", random_string_.c_str(), nullptr}) {} + random_string_(fmt::format("{}", computeBaseId())), + argv_({"envoy-static", "--base-id", random_string_.c_str(), "-c", config_file_.c_str(), + nullptr}) {} + + /** + * Computes a numeric ID to incorporate into the names of + * shared-memory segments and domain sockets, to help keep them + * distinct from other tests that might be running concurrently. + * + * The PID is needed to isolate namespaces between concurrent + * processes in CI. The random number generator is needed + * sequentially executed test methods fail with an error in + * bindDomainSocket if the the same base-id is re-used. + * + * @return uint32_t a unique numeric ID based on the PID and a random number. + */ + static uint32_t computeBaseId() { + Runtime::RandomGeneratorImpl random_generator_; + // Pick a prime number to give more of the 32-bits of entropy to the PID, and the + // remainder to the random number. + const uint32_t four_digit_prime = 7919; + return getpid() * four_digit_prime + random_generator_.random() % four_digit_prime; + } char** argv() { return const_cast(&argv_[0]); } int argc() { return argv_.size() - 1; } - void addConfig() { - addArg("-c"); - addArg(config_file_.c_str()); - } - // Adds an argument, assuring that argv remains null-terminated. void addArg(const char* arg) { ASSERT(!argv_.empty()); @@ -68,7 +86,6 @@ TEST_F(MainCommonTest, ConstructDestructHotRestartEnabled) { if (!Envoy::TestEnvironment::shouldRunTestForIpVersion(Network::Address::IpVersion::v4)) { return; } - addConfig(); VERBOSE_EXPECT_NO_THROW(MainCommon main_common(argc(), argv())); } @@ -77,11 +94,22 @@ TEST_F(MainCommonTest, ConstructDestructHotRestartDisabled) { if (!Envoy::TestEnvironment::shouldRunTestForIpVersion(Network::Address::IpVersion::v4)) { return; } - addConfig(); addArg("--disable-hot-restart"); VERBOSE_EXPECT_NO_THROW(MainCommon main_common(argc(), argv())); } +// Exercise init_only explicitly. +TEST_F(MainCommonTest, ConstructDestructHotRestartDisabledNoInit) { + if (!Envoy::TestEnvironment::shouldRunTestForIpVersion(Network::Address::IpVersion::v4)) { + return; + } + addArg("--disable-hot-restart"); + addArg("--mode"); + addArg("init_only"); + MainCommon main_common(argc(), argv()); + EXPECT_TRUE(main_common.run()); +} + // Ensurees that existing users of main_common() can link. TEST_F(MainCommonTest, LegacyMain) { if (!Envoy::TestEnvironment::shouldRunTestForIpVersion(Network::Address::IpVersion::v4)) { @@ -96,6 +124,9 @@ TEST_F(MainCommonTest, LegacyMain) { std::unique_ptr options; int return_code = -1; try { + // Set the mode to init-only so main_common doesn't initiate a server-loop. + addArg("--mode"); + addArg("init_only"); options = std::make_unique(argc(), argv(), &MainCommon::hotRestartVersion, spdlog::level::info); } catch (const Envoy::NoServingException& e) { @@ -104,12 +135,9 @@ TEST_F(MainCommonTest, LegacyMain) { return_code = EXIT_FAILURE; } if (return_code == -1) { - // Note that Envoy::main_common() will run an event loop if properly configured, which - // would hang the test. This is why we don't supply a config file in this testcase; - // we just want to make sure we wake up this code in a test. return_code = Envoy::main_common(*options); } - EXPECT_EQ(EXIT_FAILURE, return_code); + EXPECT_EQ(EXIT_SUCCESS, return_code); } } // namespace Envoy