Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion api/envoy/admin/v3/server_info.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ message ServerInfo {
config.core.v3.Node node = 7;
}

// [#next-free-field: 38]
// [#next-free-field: 39]
message CommandLineOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.admin.v2alpha.CommandLineOptions";
Expand Down Expand Up @@ -189,4 +189,7 @@ message CommandLineOptions {

// See :option:`--enable-core-dump` for details.
bool enable_core_dump = 37;

// See :option:`--stats-tag` for details.
repeated string stats_tag = 38;
Comment thread
davinci26 marked this conversation as resolved.
}
6 changes: 6 additions & 0 deletions docs/root/operations/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,9 @@ following are the command line options that Envoy supports.
It enables core dumps by invoking `prctl <https://man7.org/linux/man-pages/man2/prctl.2.html>`_ using the
PR_SET_DUMPABLE option. This is useful for container environments when using capabilities, given that when
Envoy has more capabilities than its base environment core dumping will be disabled by the kernel.

.. option:: --stats-tag

*(optional)* This flag provides a universal tag for all stats generated by Envoy. The format is ``tag:value``. Only,
alphanumeric values are allowed for stats and tags. This flag can be repeated multiple times to set multiple universal tags.
Comment thread
davinci26 marked this conversation as resolved.
Outdated
However, multiple stats with different values are not allowed.
7 changes: 7 additions & 0 deletions envoy/server/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "envoy/common/pure.h"
#include "envoy/config/bootstrap/v3/bootstrap.pb.h"
#include "envoy/network/address.h"
#include "envoy/stats/tag.h"

#include "absl/types/optional.h"
#include "spdlog/spdlog.h"
Expand Down Expand Up @@ -259,6 +260,12 @@ class Options {
* @return the mode of socket file.
*/
virtual mode_t socketMode() const PURE;

/**
* @return the stats tags provided by the cli. Tags may contain duplicates. It is the
* responsibility of the caller to handle the duplicates.
*/
virtual const Stats::TagVector& statsTags() const PURE;
};

} // namespace Server
Expand Down
5 changes: 3 additions & 2 deletions source/common/config/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,9 @@ Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource&
}

Stats::TagProducerPtr
Utility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
return std::make_unique<Stats::TagProducerImpl>(bootstrap.stats_config());
Utility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap,
const Stats::TagVector& cli_tags) {
return std::make_unique<Stats::TagProducerImpl>(bootstrap.stats_config(), cli_tags);
}

Stats::StatsMatcherPtr
Expand Down
4 changes: 3 additions & 1 deletion source/common/config/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,10 +420,12 @@ class Utility {
* Create TagProducer instance. Check all tag names for conflicts to avoid
* unexpected tag name overwriting.
* @param bootstrap bootstrap proto.
* @param cli_tags tags that are provided by the cli
* @throws EnvoyException when the conflict of tag names is found.
*/
static Stats::TagProducerPtr
createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap);
createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap,
const Stats::TagVector& cli_tags);

/**
* Create StatsMatcher instance.
Expand Down
13 changes: 12 additions & 1 deletion source/common/stats/tag_producer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@
namespace Envoy {
namespace Stats {

TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config) {
TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config)
: TagProducerImpl(config, {}) {}
Comment thread
davinci26 marked this conversation as resolved.

TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config,
const Stats::TagVector& cli_tags) {
// To check name conflict.
reserveResources(config);
absl::node_hash_set<std::string> names = addDefaultExtractors(config);

for (const auto& cli_tag : cli_tags) {
if (!names.emplace(cli_tag.name_).second) {
throw EnvoyException(fmt::format("Tag name '{}' specified twice.", cli_tag.name_));
}
default_tags_.emplace_back(cli_tag);
}

for (const auto& tag_specifier : config.stats_tags()) {
const std::string& name = tag_specifier.tag_name();
if (!names.emplace(name).second) {
Expand Down
4 changes: 4 additions & 0 deletions source/common/stats/tag_producer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ namespace Stats {
*/
class TagProducerImpl : public TagProducer {
public:
TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config,
const Stats::TagVector& cli_tags);

TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config);

TagProducerImpl() = default;

/**
Expand Down
2 changes: 1 addition & 1 deletion source/server/config_validation/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ void ValidationInstance::initialize(const Options& options,
InstanceUtil::loadBootstrapConfig(bootstrap_, options,
messageValidationContext().staticValidationVisitor(), *api_);

Config::Utility::createTagProducer(bootstrap_);
Config::Utility::createTagProducer(bootstrap_, options_.statsTags());
if (!bootstrap_.node().user_agent_build_version().has_version()) {
*bootstrap_.mutable_node()->mutable_user_agent_build_version() = VersionInfo::buildVersion();
}
Expand Down
43 changes: 43 additions & 0 deletions source/server/options_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ OptionsImpl::OptionsImpl(std::vector<std::string> args,
"600", "string", cmd);
TCLAP::SwitchArg enable_core_dump("", "enable-core-dump", "Enable core dumps", cmd, false);

TCLAP::MultiArg<std::string> stats_tag(
"", "stats-tag",
"This flag provides a universal tag for all stats generated by Envoy. The format is "
"``tag:value``. Only, alphanumeric values are allowed for stats and tags. This flag can be "
Comment thread
davinci26 marked this conversation as resolved.
Outdated
"repeated multiple times "
"to set multiple universal tags."
"However, multiple stats with different values are not allowed.",
false, "string", cmd);

cmd.setExceptionHandling(false);
TRY_ASSERT_MAIN_THREAD {
cmd.parse(args);
Expand Down Expand Up @@ -282,6 +291,37 @@ OptionsImpl::OptionsImpl(std::vector<std::string> args,
if (!disable_extensions.getValue().empty()) {
disabled_extensions_ = absl::StrSplit(disable_extensions.getValue(), ',');
}

if (!stats_tag.getValue().empty()) {
for (const auto& cli_tag_pair : stats_tag.getValue()) {
std::vector<absl::string_view> splitted_tag_pair_tokens =
Comment thread
davinci26 marked this conversation as resolved.
Outdated
absl::StrSplit(cli_tag_pair, absl::MaxSplits(':', 1));
if (splitted_tag_pair_tokens.size() != 2) {
throw MalformedArgvException(
fmt::format("error: misformatted stats-tag '{}'", cli_tag_pair));
}
std::string name = std::string(splitted_tag_pair_tokens[0]);
std::string value = std::string(splitted_tag_pair_tokens[1]);

for (const auto& token : name) {
if (isspace(token)) {
Comment thread
davinci26 marked this conversation as resolved.
Outdated
throw MalformedArgvException(fmt::format(
"error: misformatted stats-tag '{}' contains whitespace char '{}' in the name",
cli_tag_pair));
}
}

for (const auto& token : value) {
if (isspace(token) || token == '.') {
throw MalformedArgvException(
fmt::format("error: misformatted stats-tag '{}' contains invalid char '{}'",
cli_tag_pair, token));
}
}

stats_tags_.emplace_back(Stats::Tag{name, value});
}
}
}

spdlog::level::level_enum OptionsImpl::parseAndValidateLogLevel(absl::string_view log_level) {
Expand Down Expand Up @@ -396,6 +436,9 @@ Server::CommandLineOptionsPtr OptionsImpl::toCommandLineOptions() const {
}
command_line_options->set_socket_path(socketPath());
command_line_options->set_socket_mode(socketMode());
for (const auto& tag : statsTags()) {
command_line_options->add_stats_tag(fmt::format("{}:{}", tag.name_, tag.value_));
}
return command_line_options;
}

Expand Down
4 changes: 4 additions & 0 deletions source/server/options_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable<Logger::I

void setSocketMode(mode_t socket_mode) { socket_mode_ = socket_mode; }

void setStatsTags(const Stats::TagVector& stats_tags) { stats_tags_ = stats_tags; }

// Server::Options
uint64_t baseId() const override { return base_id_; }
bool useDynamicBaseId() const override { return use_dynamic_base_id_; }
Expand Down Expand Up @@ -146,6 +148,7 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable<Logger::I
bool signalHandlingEnabled() const override { return signal_handling_enabled_; }
bool mutexTracingEnabled() const override { return mutex_tracing_enabled_; }
bool coreDumpEnabled() const override { return core_dump_enabled_; }
const Stats::TagVector& statsTags() const override { return stats_tags_; }
Server::CommandLineOptionsPtr toCommandLineOptions() const override;
void parseComponentLogLevels(const std::string& component_log_levels);
bool cpusetThreadsEnabled() const override { return cpuset_threads_; }
Expand Down Expand Up @@ -207,6 +210,7 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable<Logger::I
bool core_dump_enabled_{false};
bool cpuset_threads_{false};
std::vector<std::string> disabled_extensions_;
Stats::TagVector stats_tags_;
uint32_t count_{0};

// Initialization added here to avoid integration_admin_test failure caused by uninitialized
Expand Down
2 changes: 1 addition & 1 deletion source/server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_add

// Needs to happen as early as possible in the instantiation to preempt the objects that require
// stats.
stats_store_.setTagProducer(Config::Utility::createTagProducer(bootstrap_));
stats_store_.setTagProducer(Config::Utility::createTagProducer(bootstrap_, options_.statsTags()));
stats_store_.setStatsMatcher(
Config::Utility::createStatsMatcher(bootstrap_, stats_store_.symbolTable()));
stats_store_.setHistogramSettings(Config::Utility::createHistogramSettings(bootstrap_));
Expand Down
16 changes: 13 additions & 3 deletions test/common/config/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,24 @@ TEST(UtilityTest, TranslateApiConfigSource) {

TEST(UtilityTest, createTagProducer) {
envoy::config::bootstrap::v3::Bootstrap bootstrap;
auto producer = Utility::createTagProducer(bootstrap);
ASSERT(producer != nullptr);
std::vector<Stats::Tag> tags;
auto producer = Utility::createTagProducer(bootstrap, {});
Comment thread
davinci26 marked this conversation as resolved.
ASSERT_TRUE(producer != nullptr);
Stats::TagVector tags;
auto extracted_name = producer->produceTags("http.config_test.rq_total", tags);
ASSERT_EQ(extracted_name, "http.rq_total");
ASSERT_EQ(tags.size(), 1);
}

TEST(UtilityTest, createTagProducerWithDefaultTgs) {
envoy::config::bootstrap::v3::Bootstrap bootstrap;
auto producer = Utility::createTagProducer(bootstrap, {{"foo", "bar"}});
ASSERT_TRUE(producer != nullptr);
Stats::TagVector tags;
auto extracted_name = producer->produceTags("http.config_test.rq_total", tags);
EXPECT_EQ(extracted_name, "http.rq_total");
EXPECT_EQ(tags.size(), 2);
}

TEST(UtilityTest, CheckFilesystemSubscriptionBackingPath) {
Api::ApiPtr api = Api::createApiForTest();

Expand Down
21 changes: 16 additions & 5 deletions test/common/stats/tag_producer_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,33 @@ TEST(TagProducerTest, CheckConstructor) {
auto& tag_specifier1 = *stats_config.mutable_stats_tags()->Add();
tag_specifier1.set_tag_name("test.x");
tag_specifier1.set_fixed_value("xxx");
EXPECT_NO_THROW(TagProducerImpl{stats_config});
EXPECT_NO_THROW(TagProducerImpl(stats_config, {}));
EXPECT_NO_THROW(TagProducerImpl(stats_config, {{"test.y", "yyy"}}));

// Should raise an error when duplicate tag names between cli and config.
EXPECT_THROW_WITH_MESSAGE(TagProducerImpl(stats_config, {{"test.x", "yyy"}}), EnvoyException,
fmt::format("Tag name '{}' specified twice.", "test.x"));

// Should raise an error when duplicate tag names are specified.
auto& tag_specifier2 = *stats_config.mutable_stats_tags()->Add();
tag_specifier2.set_tag_name("test.x");
tag_specifier2.set_fixed_value("yyy");
EXPECT_THROW_WITH_MESSAGE(TagProducerImpl{stats_config}, EnvoyException,
EXPECT_THROW_WITH_MESSAGE(TagProducerImpl(stats_config, {{"test.y", "yyy"}}), EnvoyException,
fmt::format("Tag name '{}' specified twice.", "test.x"));

// Should raise an error when a cli tag names conflicts with Envoy's default tag names.
EXPECT_THROW_WITH_MESSAGE(
TagProducerImpl(stats_config, {{Config::TagNames::get().CLUSTER_NAME, "yyy"}}),
EnvoyException,
fmt::format("Tag name '{}' specified twice.", Config::TagNames::get().CLUSTER_NAME));

// Also should raise an error when user defined tag name conflicts with Envoy's default tag names.
stats_config.clear_stats_tags();
stats_config.mutable_use_all_default_tags()->set_value(true);
auto& custom_tag_extractor = *stats_config.mutable_stats_tags()->Add();
custom_tag_extractor.set_tag_name(Config::TagNames::get().CLUSTER_NAME);
EXPECT_THROW_WITH_MESSAGE(
TagProducerImpl{stats_config}, EnvoyException,
TagProducerImpl(stats_config, {}), EnvoyException,
fmt::format("Tag name '{}' specified twice.", Config::TagNames::get().CLUSTER_NAME));

// Non-default custom name without regex should throw
Expand All @@ -41,7 +52,7 @@ TEST(TagProducerTest, CheckConstructor) {
custom_tag_extractor = *stats_config.mutable_stats_tags()->Add();
custom_tag_extractor.set_tag_name("test_extractor");
EXPECT_THROW_WITH_MESSAGE(
TagProducerImpl{stats_config}, EnvoyException,
TagProducerImpl(stats_config, {}), EnvoyException,
"No regex specified for tag specifier and no default regex for name: 'test_extractor'");

// Also empty regex should throw
Expand All @@ -51,7 +62,7 @@ TEST(TagProducerTest, CheckConstructor) {
custom_tag_extractor.set_tag_name("test_extractor");
custom_tag_extractor.set_regex("");
EXPECT_THROW_WITH_MESSAGE(
TagProducerImpl{stats_config}, EnvoyException,
TagProducerImpl(stats_config, {}), EnvoyException,
"No regex specified for tag specifier and no default regex for name: 'test_extractor'");
}

Expand Down
1 change: 1 addition & 0 deletions test/mocks/server/options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ MockOptions::MockOptions(const std::string& config_path) : config_path_(config_p
}));
ON_CALL(*this, socketPath()).WillByDefault(ReturnRef(socket_path_));
ON_CALL(*this, socketMode()).WillByDefault(ReturnPointee(&socket_mode_));
ON_CALL(*this, statsTags()).WillByDefault(ReturnRef(stats_tags_));
}

MockOptions::~MockOptions() = default;
Expand Down
2 changes: 2 additions & 0 deletions test/mocks/server/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class MockOptions : public Options {
MOCK_METHOD(Server::CommandLineOptionsPtr, toCommandLineOptions, (), (const));
MOCK_METHOD(const std::string&, socketPath, (), (const));
MOCK_METHOD(mode_t, socketMode, (), (const));
MOCK_METHOD((const Stats::TagVector&), statsTags, (), (const));

std::string config_path_;
envoy::config::bootstrap::v3::Bootstrap config_proto_;
Expand All @@ -76,6 +77,7 @@ class MockOptions : public Options {
std::vector<std::string> disabled_extensions_;
std::string socket_path_;
mode_t socket_mode_;
Stats::TagVector stats_tags_;
};
} // namespace Server
} // namespace Envoy
23 changes: 23 additions & 0 deletions test/server/options_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ TEST_F(OptionsImplTest, All) {
"--disable-hot-restart --cpuset-threads --allow-unknown-static-fields "
"--reject-unknown-dynamic-fields --base-id 5 "
"--use-dynamic-base-id --base-id-path /foo/baz "
"--stats-tag foo:bar --stats-tag baz:bar "
"--socket-path /foo/envoy_domain_socket --socket-mode 644");
EXPECT_EQ(Server::Mode::Validate, options->mode());
EXPECT_EQ(2U, options->concurrency());
Expand Down Expand Up @@ -120,6 +121,7 @@ TEST_F(OptionsImplTest, All) {
EXPECT_EQ("/foo/baz", options->baseIdPath());
EXPECT_EQ("/foo/envoy_domain_socket", options->socketPath());
EXPECT_EQ(0644, options->socketMode());
EXPECT_EQ(2U, options->statsTags().size());

options = createOptionsImpl("envoy --mode init_only");
EXPECT_EQ(Server::Mode::InitOnly, options->mode());
Expand Down Expand Up @@ -181,6 +183,7 @@ TEST_F(OptionsImplTest, SetAll) {
options->setRejectUnknownFieldsDynamic(true);
options->setSocketPath("/foo/envoy_domain_socket");
options->setSocketMode(0644);
options->setStatsTags({{"foo", "bar"}});

EXPECT_EQ(109876, options->baseId());
EXPECT_EQ(true, options->useDynamicBaseId());
Expand Down Expand Up @@ -244,6 +247,8 @@ TEST_F(OptionsImplTest, SetAll) {
EXPECT_EQ(options->cpusetThreadsEnabled(), command_line_options->cpuset_threads());
EXPECT_EQ(options->socketPath(), command_line_options->socket_path());
EXPECT_EQ(options->socketMode(), command_line_options->socket_mode());
EXPECT_EQ(1U, command_line_options->stats_tag().size());
EXPECT_EQ("foo:bar", command_line_options->stats_tag(0));
}

TEST_F(OptionsImplTest, DefaultParams) {
Expand All @@ -257,6 +262,7 @@ TEST_F(OptionsImplTest, DefaultParams) {
EXPECT_EQ(spdlog::level::warn, options->logLevel());
EXPECT_EQ("@envoy_domain_socket", options->socketPath());
EXPECT_EQ(0, options->socketMode());
EXPECT_EQ(0U, options->statsTags().size());
EXPECT_FALSE(options->hotRestartDisabled());
EXPECT_FALSE(options->cpusetThreadsEnabled());

Expand All @@ -275,6 +281,7 @@ TEST_F(OptionsImplTest, DefaultParams) {
EXPECT_FALSE(command_line_options->cpuset_threads());
EXPECT_FALSE(command_line_options->allow_unknown_static_fields());
EXPECT_FALSE(command_line_options->reject_unknown_dynamic_fields());
EXPECT_EQ(0, options->statsTags().size());
}

// Validates that the server_info proto is in sync with the options.
Expand Down Expand Up @@ -392,6 +399,22 @@ TEST_F(OptionsImplTest, AllowedLogLevels) {
OptionsImpl::allowedLogLevels());
}

TEST_F(OptionsImplTest, InvalidStatsTags) {
EXPECT_THROW_WITH_REGEX(createOptionsImpl("envoy --stats-tag foo"), MalformedArgvException,
"error: misformatted stats-tag 'foo'");
}

TEST_F(OptionsImplTest, InvalidCharsInStatsTags) {
EXPECT_THROW_WITH_REGEX(createOptionsImpl("envoy --stats-tag foo:b.ar"), MalformedArgvException,
"error: misformatted stats-tag 'foo:b.ar' contains invalid char '.'");
}

TEST_F(OptionsImplTest, ValidStatsTagsCharacters) {
EXPECT_NO_THROW(createOptionsImpl("envoy --stats-tag foo:bar"));
Comment thread
davinci26 marked this conversation as resolved.
Outdated
EXPECT_NO_THROW(createOptionsImpl("envoy --stats-tag foo:b:ar"));
Comment thread
davinci26 marked this conversation as resolved.
Outdated
EXPECT_NO_THROW(createOptionsImpl("envoy --stats-tag foo:b_-ar"));
}

// Test that the test constructor comes up with the same default values as the main constructor.
TEST_F(OptionsImplTest, SaneTestConstructor) {
std::unique_ptr<OptionsImpl> regular_options_impl(createOptionsImpl("envoy"));
Expand Down