diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index e0f154ebc1740..1e4cb785eee3b 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -112,7 +112,7 @@ def envoy_api_deps(skip_targets): native.git_repository( name = "envoy_api", remote = REPO_LOCATIONS["envoy_api"], - commit = "9be6aff6da46e024af56cce20cb5d5d3184f19c5", + commit = "ccc1ed57bae22673c003ed18ee23a173b1bf6b5c", ) api_bind_targets = [ "address", diff --git a/include/envoy/stats/stats.h b/include/envoy/stats/stats.h index 32567660e43ed..7ed7f5c1c9bcd 100644 --- a/include/envoy/stats/stats.h +++ b/include/envoy/stats/stats.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "envoy/common/pure.h" @@ -19,21 +20,73 @@ class Instance; namespace Stats { +/** + * General representation of a tag. + */ +struct Tag { + std::string name_; + std::string value_; +}; + +class TagExtractor { +public: + virtual ~TagExtractor() {} + + /** + * Identifier for this tag. + */ + virtual std::string name() const PURE; + + /** + * Updates the tag extracted name and the set of tags by extracting the tag represented by this + * TagExtractor. If the tag is not represented in the current tag_extracted_name, nothing will be + * modified. + * @param name name from which the tag will be removed if found to exist. + * @param tags list of tags updated with the tag name and value if found in the + * name. + * @returns modified tag_extracted_name with the tag removed. + */ + virtual std::string updateTags(const std::string& name, std::vector& tags) const PURE; +}; + +typedef std::unique_ptr TagExtractorPtr; + +/** + * General interface for all stats objects. + */ +class Metric { +public: + virtual ~Metric() {} + /** + * Returns the full name of the Metric. + */ + virtual std::string name() const PURE; + + /** + * Returns a vector of configurable tags to identify this Metric. + */ + virtual const std::vector& tags() const PURE; + + /** + * Returns the name of the Metric with the portions designated as tags removed. + */ + virtual const std::string& tagExtractedName() const PURE; +}; + /** * An always incrementing counter with latching capability. Each increment is added both to a * global counter as well as periodic counter. Calling latch() returns the periodic counter and * clears it. */ -class Counter { +class Counter : public Metric { public: virtual ~Counter() {} virtual void add(uint64_t amount) PURE; virtual void inc() PURE; virtual uint64_t latch() PURE; - virtual std::string name() PURE; virtual void reset() PURE; - virtual bool used() PURE; - virtual uint64_t value() PURE; + virtual bool used() const PURE; + virtual uint64_t value() const PURE; }; typedef std::shared_ptr CounterSharedPtr; @@ -41,18 +94,17 @@ typedef std::shared_ptr CounterSharedPtr; /** * A gauge that can both increment and decrement. */ -class Gauge { +class Gauge : public Metric { public: virtual ~Gauge() {} virtual void add(uint64_t amount) PURE; virtual void dec() PURE; virtual void inc() PURE; - virtual std::string name() PURE; virtual void set(uint64_t value) PURE; virtual void sub(uint64_t amount) PURE; - virtual bool used() PURE; - virtual uint64_t value() PURE; + virtual bool used() const PURE; + virtual uint64_t value() const PURE; }; typedef std::shared_ptr GaugeSharedPtr; @@ -83,16 +135,28 @@ typedef std::unique_ptr TimespanPtr; /** * A timer that can capture timespans. */ -class Timer { +class Timer : public Metric { public: virtual ~Timer() {} virtual TimespanPtr allocateSpan() PURE; - virtual std::string name() PURE; + virtual void recordDuration(std::chrono::milliseconds ms) PURE; }; typedef std::shared_ptr TimerSharedPtr; +/** + * A histogram that captures values one at a time. + */ +class Histogram : public Metric { +public: + virtual ~Histogram() {} + + virtual void recordValue(uint64_t value) PURE; +}; + +typedef std::shared_ptr HistogramSharedPtr; + /** * A sink for stats. Each sink is responsible for writing stats to a backing store. */ @@ -109,12 +173,12 @@ class Sink { /** * Flush a counter delta. */ - virtual void flushCounter(const std::string& name, uint64_t delta) PURE; + virtual void flushCounter(const Metric& counter, uint64_t delta) PURE; /** * Flush a gauge value. */ - virtual void flushGauge(const std::string& name, uint64_t value) PURE; + virtual void flushGauge(const Metric& gauge, uint64_t value) PURE; /** * This will be called after beginFlush(), some number of flushCounter(), and some number of @@ -125,12 +189,12 @@ class Sink { /** * Flush a histogram value. */ - virtual void onHistogramComplete(const std::string& name, uint64_t value) PURE; + virtual void onHistogramComplete(const Metric& histogram, uint64_t value) PURE; /** * Flush a timespan value. */ - virtual void onTimespanComplete(const std::string& name, std::chrono::milliseconds ms) PURE; + virtual void onTimespanComplete(const Metric& timespan, std::chrono::milliseconds ms) PURE; }; typedef std::unique_ptr SinkPtr; @@ -157,12 +221,12 @@ class Scope { /** * Deliver an individual histogram value to all registered sinks. */ - virtual void deliverHistogramToSinks(const std::string& name, uint64_t value) PURE; + virtual void deliverHistogramToSinks(const Metric& histogram, uint64_t value) PURE; /** * Deliver an individual timespan completion to all registered sinks. */ - virtual void deliverTimingToSinks(const std::string& name, std::chrono::milliseconds ms) PURE; + virtual void deliverTimingToSinks(const Metric& timer, std::chrono::milliseconds ms) PURE; /** * @return a counter within the scope's namespace. @@ -178,6 +242,11 @@ class Scope { * @return a timer within the scope's namespace. */ virtual Timer& timer(const std::string& name) PURE; + + /** + * @return a histogram within the scope's namespace. + */ + virtual Histogram& histogram(const std::string& name) PURE; }; /** @@ -196,6 +265,8 @@ class Store : public Scope { virtual std::list gauges() const PURE; }; +typedef std::unique_ptr StorePtr; + /** * The root of the stat store. */ @@ -206,6 +277,11 @@ class StoreRoot : public Store { */ virtual void addSink(Sink& sink) PURE; + /** + * Add an extractor to extract a portion of stats names as a tag. + */ + virtual void setTagExtractors(const std::vector& tag_extractor) PURE; + /** * Initialize the store for threading. This will be called once after all worker threads have * been initialized. At this point the store can initialize itself for multi-threaded operation. diff --git a/include/envoy/stats/stats_macros.h b/include/envoy/stats/stats_macros.h index 74b4fe83b4039..412cb7e42ae71 100644 --- a/include/envoy/stats/stats_macros.h +++ b/include/envoy/stats/stats_macros.h @@ -39,4 +39,5 @@ namespace Envoy { #define POOL_COUNTER(POOL) POOL_COUNTER_PREFIX(POOL, "") #define POOL_GAUGE(POOL) POOL_GAUGE_PREFIX(POOL, "") #define POOL_TIMER(POOL) POOL_TIMER_PREFIX(POOL, "") + } // Envoy diff --git a/source/common/config/BUILD b/source/common/config/BUILD index 4f3895d258971..337a1ccfae7ad 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -295,6 +295,7 @@ envoy_cc_library( envoy_cc_library( name = "well_known_names", + srcs = ["well_known_names.cc"], hdrs = ["well_known_names.h"], deps = ["//source/common/common:singleton"], ) diff --git a/source/common/config/well_known_names.cc b/source/common/config/well_known_names.cc new file mode 100644 index 0000000000000..45b20b8f7aa84 --- /dev/null +++ b/source/common/config/well_known_names.cc @@ -0,0 +1,82 @@ +#include "common/config/well_known_names.h" + +namespace Envoy { +namespace Config { + +std::unordered_map TagNameValues::getRegexMapping() { + std::unordered_map regex_mapping; + + // cluster.. + regex_mapping[CLUSTER_NAME] = "^cluster\\.(([^.]*)\\.)"; + + // listener.. + regex_mapping[LISTENER_PORT] = "^listener\\.((\\d+?)\\.)"; + + // http.. + regex_mapping[HTTP_CONN_MANAGER_PREFIX] = "^http\\.(([^.]*)\\.)"; + + // http..user_agent..[base_stat] = + regex_mapping[HTTP_USER_AGENT] = "^http(?=\\.).*?\\.user_agent\\.(([^.]*)\\.)\\w+?$"; + + // listener..ssl.cipher. + regex_mapping[SSL_CIPHER] = "^listener(?=\\.).*?\\.ssl\\.cipher(\\.([^.]*))$"; + + // auth.clientssl.. + regex_mapping[CLIENTSSL_PREFIX] = "^auth\\.clientssl\\.(([^.]*)\\.)"; + + // mongo.. + regex_mapping[MONGO_PREFIX] = "^mongo\\.(([^.]*)\\.)"; + + // mongo..cmd..[base_stat] = + regex_mapping[MONGO_CMD] = "^mongo(?=\\.).*?\\.cmd\\.(([^.]*)\\.)\\w+?$"; + + // mongo..collection..query.[base_stat] = + regex_mapping[MONGO_COLLECTION] = "^mongo(?=\\.).*?\\.collection\\.(([^.]*)\\.).*?query.\\w+?$"; + + // mongo..collection..callsite..query.[base_stat] = + regex_mapping[MONGO_CALLSITE] = "^mongo(?=\\.).*?\\.callsite\\.(([^.]*)\\.).*?query.\\w+?$"; + + // ratelimit.. + regex_mapping[RATELIMIT_PREFIX] = "^ratelimit\\.(([^.]*)\\.)"; + + // tcp.. + regex_mapping[TCP_PREFIX] = "^tcp\\.(([^.]*)\\.)"; + + // http..fault.. + regex_mapping[FAULT_DOWNSTREAM_CLUSTER] = "^http(?=\\.).*?\\.fault\\.(([^.]*)\\.)"; + + // http..dynamodb.(operation or table..capacity).. + regex_mapping[DYNAMO_OPERATION] = + "^http(?=\\.).*?\\.dynamodb.(?:operation|table(?=\\.).*?\\.capacity)(\\.([^.]*))(?:\\." + "|$)"; + + // http..dynamodb.(table or error).. + regex_mapping[DYNAMO_TABLE] = "^http(?=\\.).*?\\.dynamodb.(?:table|error)\\.(([^.]*)\\.)"; + + // http..dynamodb.table..capacity..__partition_id= + regex_mapping[DYNAMO_PARTITION_ID] = + "^http(?=\\.).*?\\.dynamodb\\..+?(\\.__partition_id=(\\w{7}))$"; + + // cluster..grpc.. + regex_mapping[GRPC_BRIDGE_SERVICE] = "^cluster(?=\\.).*?\\.grpc\\.(([^.]*)\\.)"; + + // cluster..grpc...[base_stat] = + regex_mapping[GRPC_BRIDGE_METHOD] = "^cluster(?=\\.).*?\\.grpc(?=\\.).*\\.(([^.]*)\\.)\\w+?$"; + + // vhost.. + regex_mapping[VIRTUAL_HOST] = "^vhost\\.(([^.]*)\\.)"; + + // vhost..vcluster..[base_stat] = + regex_mapping[VIRTUAL_CLUSTER] = "^vhost(?=\\.).*?\\.vcluster\\.(([^.]*)\\.)\\w+?$"; + + // *_rq_ + regex_mapping[RESPONSE_CODE] = "_rq(_(\\d{3}))$"; + + // *_rq_xx + regex_mapping[RESPONSE_CODE_CLASS] = "_rq(_(\\dxx))$"; + + return regex_mapping; +} + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/well_known_names.h b/source/common/config/well_known_names.h index db097f3bee270..a13e8629f164a 100644 --- a/source/common/config/well_known_names.h +++ b/source/common/config/well_known_names.h @@ -172,5 +172,70 @@ class MetadataEnvoyLbKeyValues { typedef ConstSingleton MetadataEnvoyLbKeys; +/** + * Well known tags values and a mapping from these names to the regexes they + * represent. Note: when names are added to the list, they also must be added to + * the regex map by adding an entry in the getRegexMapping function. + */ +class TagNameValues { +public: + // Cluster name tag + const std::string CLUSTER_NAME = "envoy.cluster_name"; + // Listener port tag + const std::string LISTENER_PORT = "envoy.listener_port"; + // Stats prefix for HttpConnectionManager + const std::string HTTP_CONN_MANAGER_PREFIX = "envoy.http_conn_manager_prefix"; + // User agent for a connection + const std::string HTTP_USER_AGENT = "envoy.http_user_agent"; + // SSL Cipher for a connection + const std::string SSL_CIPHER = "envoy.ssl_cipher"; + // Stats prefix for the Client SSL Auth network filter + const std::string CLIENTSSL_PREFIX = "envoy.clientssl_prefix"; + // Stats prefix for the Mongo Proxy network filter + const std::string MONGO_PREFIX = "envoy.mongo_prefix"; + // Request command for the Mongo Proxy network filter + const std::string MONGO_CMD = "envoy.mongo_cmd"; + // Request collection for the Mongo Proxy network filter + const std::string MONGO_COLLECTION = "envoy.mongo_collection"; + // Request callsite for the Mongo Proxy network filter + const std::string MONGO_CALLSITE = "envoy.mongo_callsite"; + // Stats prefix for the Ratelimit network filter + const std::string RATELIMIT_PREFIX = "envoy.ratelimit_prefix"; + // Stats prefix for the TCP Proxy network filter + const std::string TCP_PREFIX = "envoy.tcp_prefix"; + // Downstream cluster for the Fault http filter + const std::string FAULT_DOWNSTREAM_CLUSTER = "envoy.fault_downstream_cluster"; + // Operation name for the Dynamo http filter + const std::string DYNAMO_OPERATION = "envoy.dynamo_operation"; + // Table name for the Dynamo http filter + const std::string DYNAMO_TABLE = "envoy.dyanmo_table"; + // Partition ID for the Dynamo http filter + const std::string DYNAMO_PARTITION_ID = "envoy.dynamo_partition_id"; + // Request service name GRPC Bridge http filter + const std::string GRPC_BRIDGE_SERVICE = "envoy.grpc_bridge_service"; + // Request method name for the GRPC Bridge http filter + const std::string GRPC_BRIDGE_METHOD = "envoy.grpc_bridge_method"; + // Request virtual host given by the Router http filter + const std::string VIRTUAL_HOST = "envoy.virtual_host"; + // Request virtual cluster given by the Router http filter + const std::string VIRTUAL_CLUSTER = "envoy.virtual_cluster"; + // Request response code + const std::string RESPONSE_CODE = "envoy.response_code"; + // Request response code class + const std::string RESPONSE_CODE_CLASS = "envoy.response_code_class"; + + // Mapping from the names above to their respective regex strings. + const std::unordered_map regex_map_; + + // Constructor to fill map. + TagNameValues() : regex_map_(getRegexMapping()) {} + +private: + // Creates a regex mapping for all tag names. + std::unordered_map getRegexMapping(); +}; + +typedef ConstSingleton TagNames; + } // namespace Config } // namespace Envoy diff --git a/source/common/dynamo/dynamo_filter.cc b/source/common/dynamo/dynamo_filter.cc index 557a02f9b3623..d64d154df4a9d 100644 --- a/source/common/dynamo/dynamo_filter.cc +++ b/source/common/dynamo/dynamo_filter.cc @@ -181,14 +181,16 @@ void DynamoFilter::chargeStatsPerEntity(const std::string& entity, const std::st std::to_string(status))) .inc(); - scope_.deliverTimingToSinks( - fmt::format("{}{}.{}.upstream_rq_time", stat_prefix_, entity_type, entity), latency); - scope_.deliverTimingToSinks( - fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type, entity, group_string), - latency); - scope_.deliverTimingToSinks(fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type, - entity, std::to_string(status)), - latency); + scope_.timer(fmt::format("{}{}.{}.upstream_rq_time", stat_prefix_, entity_type, entity)) + .recordDuration(latency); + scope_ + .timer(fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type, entity, + group_string)) + .recordDuration(latency); + scope_ + .timer(fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type, entity, + std::to_string(status))) + .recordDuration(latency); } void DynamoFilter::chargeUnProcessedKeysStats(const Json::Object& json_body) { diff --git a/source/common/http/codes.cc b/source/common/http/codes.cc index 0d5d82c7cee28..d405f6dee6479 100644 --- a/source/common/http/codes.cc +++ b/source/common/http/codes.cc @@ -81,31 +81,33 @@ void CodeUtility::chargeResponseStat(const ResponseStatInfo& info) { } void CodeUtility::chargeResponseTiming(const ResponseTimingInfo& info) { - info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "upstream_rq_time", info.response_time_); + info.cluster_scope_.timer(info.prefix_ + "upstream_rq_time").recordDuration(info.response_time_); if (info.upstream_canary_) { - info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "canary.upstream_rq_time", - info.response_time_); + info.cluster_scope_.timer(info.prefix_ + "canary.upstream_rq_time") + .recordDuration(info.response_time_); } if (info.internal_request_) { - info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "internal.upstream_rq_time", - info.response_time_); + info.cluster_scope_.timer(info.prefix_ + "internal.upstream_rq_time") + .recordDuration(info.response_time_); } else { - info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "external.upstream_rq_time", - info.response_time_); + info.cluster_scope_.timer(info.prefix_ + "external.upstream_rq_time") + .recordDuration(info.response_time_); } if (!info.request_vcluster_name_.empty()) { - info.global_scope_.deliverTimingToSinks("vhost." + info.request_vhost_name_ + ".vcluster." + - info.request_vcluster_name_ + ".upstream_rq_time", - info.response_time_); + info.global_scope_ + .timer("vhost." + info.request_vhost_name_ + ".vcluster." + info.request_vcluster_name_ + + ".upstream_rq_time") + .recordDuration(info.response_time_); } // Handle per zone stats. if (!info.from_zone_.empty() && !info.to_zone_.empty()) { - info.cluster_scope_.deliverTimingToSinks( - fmt::format("{}zone.{}.{}.upstream_rq_time", info.prefix_, info.from_zone_, info.to_zone_), - info.response_time_); + info.cluster_scope_ + .timer(fmt::format("{}zone.{}.{}.upstream_rq_time", info.prefix_, info.from_zone_, + info.to_zone_)) + .recordDuration(info.response_time_); } } diff --git a/source/common/mongo/proxy.cc b/source/common/mongo/proxy.cc index 04b76fe934a7d..d92340416081c 100644 --- a/source/common/mongo/proxy.cc +++ b/source/common/mongo/proxy.cc @@ -191,12 +191,12 @@ void ProxyFilter::chargeReplyStats(ActiveQuery& active_query, const std::string& reply_documents_byte_size += document->byteSize(); } - scope_.deliverHistogramToSinks(fmt::format("{}.reply_num_docs", prefix), - message.documents().size()); - scope_.deliverHistogramToSinks(fmt::format("{}.reply_size", prefix), reply_documents_byte_size); - scope_.deliverTimingToSinks(fmt::format("{}.reply_time_ms", prefix), - std::chrono::duration_cast( - std::chrono::steady_clock::now() - active_query.start_time_)); + scope_.histogram(fmt::format("{}.reply_num_docs", prefix)) + .recordValue(message.documents().size()); + scope_.histogram(fmt::format("{}.reply_size", prefix)).recordValue(reply_documents_byte_size); + scope_.timer(fmt::format("{}.reply_time_ms", prefix)) + .recordDuration(std::chrono::duration_cast( + std::chrono::steady_clock::now() - active_query.start_time_)); } void ProxyFilter::doDecode(Buffer::Instance& buffer) { diff --git a/source/common/stats/BUILD b/source/common/stats/BUILD index 3c776f9b31a72..bb33c500efce6 100644 --- a/source/common/stats/BUILD +++ b/source/common/stats/BUILD @@ -12,11 +12,15 @@ envoy_cc_library( name = "stats_lib", srcs = ["stats_impl.cc"], hdrs = ["stats_impl.h"], + external_deps = ["envoy_bootstrap"], deps = [ "//include/envoy/common:time_interface", "//include/envoy/stats:stats_interface", "//source/common/common:assert_lib", + "//source/common/common:singleton", "//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 aa07373366c73..811bdad234b83 100644 --- a/source/common/stats/stats_impl.cc +++ b/source/common/stats/stats_impl.cc @@ -5,15 +5,68 @@ #include #include +#include "envoy/common/exception.h" + #include "common/common/utility.h" +#include "common/config/well_known_names.h" namespace Envoy { namespace Stats { +TagExtractorImpl::TagExtractorImpl(const std::string& name, const std::string& regex) + : name_(name), regex_(regex) {} + +TagExtractorPtr TagExtractorImpl::createTagExtractor(const std::string& name, + const std::string& regex) { + + if (name.empty()) { + throw EnvoyException("tag_name cannot be empty"); + } + + if (!regex.empty()) { + return TagExtractorPtr{new TagExtractorImpl(name, regex)}; + } else { + // Look up the default for that name. + const auto tag_names = Config::TagNames::get(); + auto it = tag_names.regex_map_.find(name); + if (it != tag_names.regex_map_.end()) { + return TagExtractorPtr{new TagExtractorImpl(name, it->second)}; + } else { + throw EnvoyException(fmt::format( + "No regex specified for tag specifier and no default regex for name: '{}'", name)); + } + } +} + +std::string TagExtractorImpl::updateTags(const std::string& tag_extracted_name, + std::vector& tags) const { + std::smatch match; + // The regex must match and contain one or more subexpressions (all after the first are ignored). + if (std::regex_search(tag_extracted_name, match, regex_) && match.size() > 1) { + const auto& remove_subexpr = match[1]; + const auto& value_subexpr = match.size() > 2 ? match[2] : remove_subexpr; + + tags.emplace_back(); + Tag& tag = tags.back(); + tag.name_ = name_; + tag.value_ = value_subexpr.str(); + + // This call invalidates match and all derived objects because they contain references to + // tag_extracted_name. + return std::string(match.prefix().first, remove_subexpr.first) + .append(remove_subexpr.second, match.suffix().second); + } + return tag_extracted_name; +} + +void TimerImpl::recordDuration(std::chrono::milliseconds ms) { + parent_.deliverTimingToSinks(*this, ms); +} + void TimerImpl::TimespanImpl::complete(const std::string& dynamic_name) { std::chrono::milliseconds ms = std::chrono::duration_cast( std::chrono::steady_clock::now() - start_); - parent_.parent_.deliverTimingToSinks(dynamic_name, ms); + parent_.parent_.timer(dynamic_name).recordDuration(ms); } RawStatData* HeapRawStatDataAllocator::alloc(const std::string& name) { diff --git a/source/common/stats/stats_impl.h b/source/common/stats/stats_impl.h index b1b1c6ab25637..7304e6a80f437 100644 --- a/source/common/stats/stats_impl.h +++ b/source/common/stats/stats_impl.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -13,10 +14,34 @@ #include "envoy/stats/stats.h" #include "common/common/assert.h" +#include "common/common/singleton.h" +#include "common/protobuf/protobuf.h" + +#include "api/bootstrap.pb.h" namespace Envoy { namespace Stats { +class TagExtractorImpl : public TagExtractor { +public: + /** + * Creates a tag extractor from the regex provided or looks up a default regex. + * @param name name for tag extractor. Used to look up a default tag extractor if regex is empty. + * @param regex optional regex expression. Can be specified as an empty string to trigger a + * default regex lookup. + */ + static TagExtractorPtr createTagExtractor(const std::string& name, const std::string& regex); + + TagExtractorImpl(const std::string& name, const std::string& regex); + std::string name() const override { return name_; } + std::string updateTags(const std::string& tag_extracted_name, + std::vector& tags) const override; + +private: + std::string name_; + std::regex regex_; +}; + /** * This structure is the backing memory for both CounterImpl and GaugeImpl. It is designed so that * it can be allocated from shared memory if needed. @@ -68,7 +93,10 @@ class RawStatDataAllocator { */ class CounterImpl : public Counter { public: - CounterImpl(RawStatData& data, RawStatDataAllocator& alloc) : data_(data), alloc_(alloc) {} + CounterImpl(RawStatData& data, RawStatDataAllocator& alloc, std::string&& tag_extracted_name, + std::vector&& tags) + : data_(data), alloc_(alloc), tag_extracted_name_(std::move(tag_extracted_name)), + tags_(std::move(tags)) {} ~CounterImpl() { alloc_.free(data_); } // Stats::Counter @@ -80,14 +108,20 @@ class CounterImpl : public Counter { void inc() override { add(1); } uint64_t latch() override { return data_.pending_increment_.exchange(0); } - std::string name() override { return data_.name_; } void reset() override { data_.value_ = 0; } - bool used() override { return data_.flags_ & RawStatData::Flags::Used; } - uint64_t value() override { return data_.value_; } + bool used() const override { return data_.flags_ & RawStatData::Flags::Used; } + uint64_t value() const override { return data_.value_; } + + // Stats::Metric + std::string name() const override { return data_.name_; } + const std::string& tagExtractedName() const override { return tag_extracted_name_; } + const std::vector& tags() const override { return tags_; } private: RawStatData& data_; RawStatDataAllocator& alloc_; + const std::string tag_extracted_name_; + const std::vector tags_; }; /** @@ -95,7 +129,10 @@ class CounterImpl : public Counter { */ class GaugeImpl : public Gauge { public: - GaugeImpl(RawStatData& data, RawStatDataAllocator& alloc) : data_(data), alloc_(alloc) {} + GaugeImpl(RawStatData& data, RawStatDataAllocator& alloc, std::string&& tag_extracted_name, + std::vector&& tags) + : data_(data), alloc_(alloc), tag_extracted_name_(std::move(tag_extracted_name)), + tags_(std::move(tags)) {} ~GaugeImpl() { alloc_.free(data_); } // Stats::Gauge @@ -105,7 +142,6 @@ class GaugeImpl : public Gauge { } virtual void dec() override { sub(1); } virtual void inc() override { add(1); } - virtual std::string name() override { return data_.name_; } virtual void set(uint64_t value) override { data_.value_ = value; data_.flags_ |= RawStatData::Flags::Used; @@ -115,12 +151,19 @@ class GaugeImpl : public Gauge { ASSERT(used()); data_.value_ -= amount; } - bool used() override { return data_.flags_ & RawStatData::Flags::Used; } - virtual uint64_t value() override { return data_.value_; } + virtual uint64_t value() const override { return data_.value_; } + bool used() const override { return data_.flags_ & RawStatData::Flags::Used; } + + // Stats::Metric + virtual std::string name() const override { return data_.name_; } + const std::string& tagExtractedName() const override { return tag_extracted_name_; } + const std::vector& tags() const override { return tags_; } private: RawStatData& data_; RawStatDataAllocator& alloc_; + const std::string tag_extracted_name_; + const std::vector tags_; }; /** @@ -128,11 +171,19 @@ class GaugeImpl : public Gauge { */ class TimerImpl : public Timer { public: - TimerImpl(const std::string& name, Store& parent) : name_(name), parent_(parent) {} + TimerImpl(const std::string& name, Store& parent, std::string&& tag_extracted_name, + std::vector&& tags) + : name_(name), parent_(parent), tag_extracted_name_(std::move(tag_extracted_name)), + tags_(std::move(tags)) {} // Stats::Timer TimespanPtr allocateSpan() override { return TimespanPtr{new TimespanImpl(*this)}; } - std::string name() override { return name_; } + void recordDuration(std::chrono::milliseconds ma) override; + + // Stats::Metric + std::string name() const override { return name_; } + const std::string& tagExtractedName() const override { return tag_extracted_name_; } + const std::vector& tags() const override { return tags_; } private: /** @@ -153,6 +204,32 @@ class TimerImpl : public Timer { std::string name_; Store& parent_; + const std::string tag_extracted_name_; + const std::vector tags_; +}; + +/** + * Histogram implementation for the heap. + */ +class HistogramImpl : public Histogram { +public: + HistogramImpl(const std::string& name, Store& parent, std::string&& tag_extracted_name, + std::vector&& tags) + : name_(name), parent_(parent), tag_extracted_name_(std::move(tag_extracted_name)), + tags_(std::move(tags)) {} + + // Stats::Histogram + void recordValue(uint64_t value) override { parent_.deliverHistogramToSinks(*this, value); } + + // Stats::Metric + std::string name() const override { return name_; } + const std::string& tagExtractedName() const override { return tag_extracted_name_; } + const std::vector& tags() const override { return tags_; } + + std::string name_; + Store& parent_; + const std::string tag_extracted_name_; + const std::vector tags_; }; /** @@ -207,23 +284,29 @@ class IsolatedStoreImpl : public Store { public: IsolatedStoreImpl() : counters_([this](const std::string& name) -> CounterImpl* { - return new CounterImpl(*alloc_.alloc(name), alloc_); + return new CounterImpl(*alloc_.alloc(name), alloc_, std::string(name), + std::vector()); }), gauges_([this](const std::string& name) -> GaugeImpl* { - return new GaugeImpl(*alloc_.alloc(name), alloc_); + return new GaugeImpl(*alloc_.alloc(name), alloc_, std::string(name), std::vector()); + }), + timers_([this](const std::string& name) -> TimerImpl* { + return new TimerImpl(name, *this, std::string(name), std::vector()); }), - timers_( - [this](const std::string& name) -> TimerImpl* { return new TimerImpl(name, *this); }) {} + histograms_([this](const std::string& name) -> HistogramImpl* { + return new HistogramImpl(name, *this, std::string(name), std::vector()); + }) {} // Stats::Scope Counter& counter(const std::string& name) override { return counters_.get(name); } ScopePtr createScope(const std::string& name) override { return ScopePtr{new ScopeImpl(*this, name)}; } - void deliverHistogramToSinks(const std::string&, uint64_t) override {} - void deliverTimingToSinks(const std::string&, std::chrono::milliseconds) override {} + void deliverHistogramToSinks(const Metric&, uint64_t) override {} + void deliverTimingToSinks(const Metric&, std::chrono::milliseconds) override {} Gauge& gauge(const std::string& name) override { return gauges_.get(name); } Timer& timer(const std::string& name) override { return timers_.get(name); } + Histogram& histogram(const std::string& name) override { return histograms_.get(name); } // Stats::Store std::list counters() const override { return counters_.toList(); } @@ -238,11 +321,14 @@ class IsolatedStoreImpl : public Store { ScopePtr createScope(const std::string& name) override { return ScopePtr{new ScopeImpl(parent_, prefix_ + name)}; } - void deliverHistogramToSinks(const std::string&, uint64_t) override {} - void deliverTimingToSinks(const std::string&, std::chrono::milliseconds) override {} + void deliverHistogramToSinks(const Metric&, uint64_t) override {} + void deliverTimingToSinks(const Metric&, std::chrono::milliseconds) override {} Counter& counter(const std::string& name) override { return parent_.counter(prefix_ + name); } Gauge& gauge(const std::string& name) override { return parent_.gauge(prefix_ + name); } Timer& timer(const std::string& name) override { return parent_.timer(prefix_ + name); } + Histogram& histogram(const std::string& name) override { + return parent_.histogram(prefix_ + name); + } IsolatedStoreImpl& parent_; const std::string prefix_; @@ -252,6 +338,7 @@ class IsolatedStoreImpl : public Store { IsolatedStatsCache counters_; IsolatedStatsCache gauges_; IsolatedStatsCache timers_; + IsolatedStatsCache histograms_; }; } // namespace Stats diff --git a/source/common/stats/statsd.cc b/source/common/stats/statsd.cc index 1f672fff07da8..f457313e42480 100644 --- a/source/common/stats/statsd.cc +++ b/source/common/stats/statsd.cc @@ -60,16 +60,16 @@ UdpStatsdSink::UdpStatsdSink(ThreadLocal::SlotAllocator& tls, }); } -void UdpStatsdSink::flushCounter(const std::string& name, uint64_t delta) { - tls_->getTyped().writeCounter(name, delta); +void UdpStatsdSink::flushCounter(const Metric& counter, uint64_t delta) { + tls_->getTyped().writeCounter(counter.name(), delta); } -void UdpStatsdSink::flushGauge(const std::string& name, uint64_t value) { - tls_->getTyped().writeGauge(name, value); +void UdpStatsdSink::flushGauge(const Metric& gauge, uint64_t value) { + tls_->getTyped().writeGauge(gauge.name(), value); } -void UdpStatsdSink::onTimespanComplete(const std::string& name, std::chrono::milliseconds ms) { - tls_->getTyped().writeTimer(name, ms); +void UdpStatsdSink::onTimespanComplete(const Metric& timer, std::chrono::milliseconds ms) { + tls_->getTyped().writeTimer(timer.name(), ms); } char TcpStatsdSink::STAT_PREFIX[] = "envoy."; diff --git a/source/common/stats/statsd.h b/source/common/stats/statsd.h index 8ca0689da6dd5..019abf433aaf2 100644 --- a/source/common/stats/statsd.h +++ b/source/common/stats/statsd.h @@ -46,14 +46,14 @@ class UdpStatsdSink : public Sink { // Stats::Sink void beginFlush() override {} - void flushCounter(const std::string& name, uint64_t delta) override; - void flushGauge(const std::string& name, uint64_t value) override; + void flushCounter(const Metric& counter, uint64_t delta) override; + void flushGauge(const Metric& gauge, uint64_t value) override; void endFlush() override {} - void onHistogramComplete(const std::string& name, uint64_t value) override { + void onHistogramComplete(const Metric& histogram, uint64_t value) override { // For statsd histograms are just timers. - onTimespanComplete(name, std::chrono::milliseconds(value)); + onTimespanComplete(histogram, std::chrono::milliseconds(value)); } - void onTimespanComplete(const std::string& name, std::chrono::milliseconds ms) override; + void onTimespanComplete(const Metric& timer, std::chrono::milliseconds ms) override; // Called in unit test to validate writer construction and address. int getFdForTests() { return tls_->getTyped().getFdForTests(); } @@ -74,23 +74,23 @@ class TcpStatsdSink : public Sink { // Stats::Sink void beginFlush() override { tls_->getTyped().beginFlush(true); } - void flushCounter(const std::string& name, uint64_t delta) override { - tls_->getTyped().flushCounter(name, delta); + void flushCounter(const Metric& counter, uint64_t delta) override { + tls_->getTyped().flushCounter(counter.name(), delta); } - void flushGauge(const std::string& name, uint64_t value) override { - tls_->getTyped().flushGauge(name, value); + void flushGauge(const Metric& gauge, uint64_t value) override { + tls_->getTyped().flushGauge(gauge.name(), value); } void endFlush() override { tls_->getTyped().endFlush(true); } - void onHistogramComplete(const std::string& name, uint64_t value) override { + void onHistogramComplete(const Metric& histogram, uint64_t value) override { // For statsd histograms are just timers. - onTimespanComplete(name, std::chrono::milliseconds(value)); + onTimespanComplete(histogram, std::chrono::milliseconds(value)); } - void onTimespanComplete(const std::string& name, std::chrono::milliseconds ms) override { - tls_->getTyped().onTimespanComplete(name, ms); + void onTimespanComplete(const Metric& timer, std::chrono::milliseconds ms) override { + tls_->getTyped().onTimespanComplete(timer.name(), ms); } private: diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 679f3a64b77ee..6006054869a66 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -86,6 +86,16 @@ void ThreadLocalStoreImpl::releaseScopeCrossThread(ScopeImpl* scope) { } } +std::string ThreadLocalStoreImpl::getTagsForName(const std::string& name, std::vector& tags) { + std::string tag_extracted_name = name; + if (tag_extractors_ != nullptr) { + for (const TagExtractorPtr& tag_extractor : *tag_extractors_) { + tag_extracted_name = tag_extractor->updateTags(tag_extracted_name, tags); + } + } + return tag_extracted_name; +} + void ThreadLocalStoreImpl::clearScopeFromCaches(ScopeImpl* scope) { // If we are shutting down we no longer perform cache flushes as workers may be shutting down // at the same time. @@ -134,7 +144,10 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counter(const std::string& name) { CounterSharedPtr& central_ref = central_cache_.counters_[final_name]; if (!central_ref) { SafeAllocData alloc = parent_.safeAlloc(final_name); - central_ref.reset(new CounterImpl(alloc.data_, alloc.free_)); + std::vector tags; + std::string tag_extracted_name = parent_.getTagsForName(final_name, tags); + central_ref.reset( + new CounterImpl(alloc.data_, alloc.free_, std::move(tag_extracted_name), std::move(tags))); } // If we have a TLS location to store or allocation into, do it. @@ -146,7 +159,7 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counter(const std::string& name) { return *central_ref; } -void ThreadLocalStoreImpl::ScopeImpl::deliverHistogramToSinks(const std::string& name, +void ThreadLocalStoreImpl::ScopeImpl::deliverHistogramToSinks(const Metric& histogram, uint64_t value) { // Thread local deliveries must be blocked outright for histograms and timers during shutdown. // This is because the sinks may end up trying to create new connections via the thread local @@ -157,22 +170,20 @@ void ThreadLocalStoreImpl::ScopeImpl::deliverHistogramToSinks(const std::string& return; } - const std::string final_name = prefix_ + name; for (Sink& sink : parent_.timer_sinks_) { - sink.onHistogramComplete(final_name, value); + sink.onHistogramComplete(histogram, value); } } -void ThreadLocalStoreImpl::ScopeImpl::deliverTimingToSinks(const std::string& name, +void ThreadLocalStoreImpl::ScopeImpl::deliverTimingToSinks(const Metric& timer, std::chrono::milliseconds ms) { // See comment in deliverHistogramToSinks() for why we guard this. if (parent_.shutting_down_) { return; } - const std::string final_name = prefix_ + name; for (Sink& sink : parent_.timer_sinks_) { - sink.onTimespanComplete(final_name, ms); + sink.onTimespanComplete(timer, ms); } } @@ -193,7 +204,10 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gauge(const std::string& name) { GaugeSharedPtr& central_ref = central_cache_.gauges_[final_name]; if (!central_ref) { SafeAllocData alloc = parent_.safeAlloc(final_name); - central_ref.reset(new GaugeImpl(alloc.data_, alloc.free_)); + std::vector tags; + std::string tag_extracted_name = parent_.getTagsForName(final_name, tags); + central_ref.reset( + new GaugeImpl(alloc.data_, alloc.free_, std::move(tag_extracted_name), std::move(tags))); } if (tls_ref) { @@ -219,7 +233,39 @@ Timer& ThreadLocalStoreImpl::ScopeImpl::timer(const std::string& name) { std::unique_lock lock(parent_.lock_); TimerSharedPtr& central_ref = central_cache_.timers_[final_name]; if (!central_ref) { - central_ref.reset(new TimerImpl(final_name, parent_)); + std::vector tags; + std::string tag_extracted_name = parent_.getTagsForName(final_name, tags); + central_ref.reset( + new TimerImpl(final_name, parent_, std::move(tag_extracted_name), std::move(tags))); + } + + if (tls_ref) { + *tls_ref = central_ref; + } + + return *central_ref; +} + +Histogram& ThreadLocalStoreImpl::ScopeImpl::histogram(const std::string& name) { + // See comments in counter(). There is no super clean way (via templates or otherwise) to + // share this code so I'm leaving it largely duplicated for now. + std::string final_name = prefix_ + name; + HistogramSharedPtr* tls_ref = nullptr; + if (!parent_.shutting_down_ && parent_.tls_) { + tls_ref = &parent_.tls_->getTyped().scope_cache_[this].histograms_[final_name]; + } + + if (tls_ref && *tls_ref) { + return **tls_ref; + } + + std::unique_lock lock(parent_.lock_); + HistogramSharedPtr& central_ref = central_cache_.histograms_[final_name]; + if (!central_ref) { + std::vector tags; + std::string tag_extracted_name = parent_.getTagsForName(final_name, tags); + central_ref.reset( + new HistogramImpl(final_name, parent_, std::move(tag_extracted_name), std::move(tags))); } if (tls_ref) { diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 22c9cdf27a03b..38e350e73bae5 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -53,14 +53,17 @@ class ThreadLocalStoreImpl : public StoreRoot { // Stats::Scope Counter& counter(const std::string& name) override { return default_scope_->counter(name); } ScopePtr createScope(const std::string& name) override; - void deliverHistogramToSinks(const std::string& name, uint64_t value) override { - return default_scope_->deliverHistogramToSinks(name, value); + void deliverHistogramToSinks(const Metric& histogram, uint64_t value) override { + return default_scope_->deliverHistogramToSinks(histogram, value); } - void deliverTimingToSinks(const std::string& name, std::chrono::milliseconds ms) override { - return default_scope_->deliverTimingToSinks(name, ms); + void deliverTimingToSinks(const Metric& timer, std::chrono::milliseconds ms) override { + return default_scope_->deliverTimingToSinks(timer, ms); } Gauge& gauge(const std::string& name) override { return default_scope_->gauge(name); } Timer& timer(const std::string& name) override { return default_scope_->timer(name); } + Histogram& histogram(const std::string& name) override { + return default_scope_->histogram(name); + }; // Stats::Store std::list counters() const override; @@ -68,6 +71,9 @@ class ThreadLocalStoreImpl : public StoreRoot { // Stats::StoreRoot void addSink(Sink& sink) override { timer_sinks_.push_back(sink); } + void setTagExtractors(const std::vector& tag_extractors) override { + tag_extractors_ = &tag_extractors; + } void initializeThreading(Event::Dispatcher& main_thread_dispatcher, ThreadLocal::Instance& tls) override; void shutdownThreading() override; @@ -77,6 +83,7 @@ class ThreadLocalStoreImpl : public StoreRoot { std::unordered_map counters_; std::unordered_map gauges_; std::unordered_map timers_; + std::unordered_map histograms_; }; struct ScopeImpl : public Scope { @@ -89,10 +96,11 @@ class ThreadLocalStoreImpl : public StoreRoot { ScopePtr createScope(const std::string& name) override { return parent_.createScope(prefix_ + name); } - void deliverHistogramToSinks(const std::string& name, uint64_t value) override; - void deliverTimingToSinks(const std::string& name, std::chrono::milliseconds ms) override; + void deliverHistogramToSinks(const Metric& histogram, uint64_t value) override; + void deliverTimingToSinks(const Metric& timer, std::chrono::milliseconds ms) override; Gauge& gauge(const std::string& name) override; Timer& timer(const std::string& name) override; + Histogram& histogram(const std::string& name) override; ThreadLocalStoreImpl& parent_; const std::string prefix_; @@ -108,6 +116,7 @@ class ThreadLocalStoreImpl : public StoreRoot { RawStatDataAllocator& free_; }; + std::string getTagsForName(const std::string& name, std::vector& tags); void clearScopeFromCaches(ScopeImpl* scope); void releaseScopeCrossThread(ScopeImpl* scope); SafeAllocData safeAlloc(const std::string& name); @@ -119,6 +128,7 @@ class ThreadLocalStoreImpl : public StoreRoot { std::unordered_set scopes_; ScopePtr default_scope_; std::list> timer_sinks_; + const std::vector* tag_extractors_{}; std::atomic shutting_down_{}; Counter& num_last_resort_stats_; HeapRawStatDataAllocator heap_allocator_; diff --git a/source/server/BUILD b/source/server/BUILD index 143a95f74a6e0..177599484f1b5 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -235,6 +235,7 @@ envoy_cc_library( "//source/common/router:rds_lib", "//source/common/runtime:runtime_lib", "//source/common/singleton:manager_impl_lib", + "//source/common/stats:thread_local_store_lib", "//source/common/upstream:cluster_manager_lib", "//source/server/http:admin_lib", ], diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index 6c295a7e3687d..3a8e582109448 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -73,6 +73,9 @@ void ValidationInstance::initialize(Options& options, Json::ObjectSharedPtr config_json = Json::Factory::loadFromFile(options.configPath()); Config::BootstrapJson::translateBootstrap(*config_json, bootstrap); } + + initializeStatsTags(bootstrap); + bootstrap.mutable_node()->set_build_version(VersionInfo::version()); local_info_.reset( @@ -104,5 +107,31 @@ void ValidationInstance::shutdown() { thread_local_.shutdownThread(); } +void ValidationInstance::initializeStatsTags(const envoy::api::v2::Bootstrap& bootstrap) { + // Ensure no tag names are repeated. + std::unordered_set names; + auto add_tag = [&names, this](const std::string& name, const std::string& regex) { + if (!names.emplace(name).second) { + throw EnvoyException(fmt::format("Tag name '{}' specified twice.", name)); + } + + tag_extractors_.emplace_back(Stats::TagExtractorImpl::createTagExtractor(name, regex)); + }; + + // Add defaults. + if (!bootstrap.stats_config().has_use_all_default_tags() || + bootstrap.stats_config().use_all_default_tags().value()) { + for (const std::pair& default_tag : + Config::TagNames::get().regex_map_) { + add_tag(default_tag.first, default_tag.second); + } + } + + // Add custom tags. + for (const envoy::api::v2::TagSpecifier& tag_specifier : bootstrap.stats_config().stats_tags()) { + add_tag(tag_specifier.tag_name(), tag_specifier.regex()); + } +} + } // namespace Server } // namespace Envoy diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index f3029eea3b35f..f3a512af6b84c 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -113,9 +113,11 @@ class ValidationInstance : Logger::Loggable, private: void initialize(Options& options, Network::Address::InstanceConstSharedPtr local_address, ComponentFactory& component_factory); + void initializeStatsTags(const envoy::api::v2::Bootstrap& bootstrap); Options& options_; Stats::IsolatedStoreImpl& stats_store_; + std::vector tag_extractors_; ThreadLocal::InstanceImpl thread_local_; Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; diff --git a/source/server/server.cc b/source/server/server.cc index 0d6ebf4936ea7..57b00fab77633 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include "envoy/event/dispatcher.h" #include "envoy/event/signal.h" @@ -24,6 +25,7 @@ #include "common/router/rds_impl.h" #include "common/runtime/runtime_impl.h" #include "common/singleton/manager_impl.h" +#include "common/stats/thread_local_store.h" #include "common/upstream/cluster_manager_impl.h" #include "server/configuration_impl.h" @@ -41,11 +43,9 @@ InstanceImpl::InstanceImpl(Options& options, Network::Address::InstanceConstShar Thread::BasicLockable& access_log_lock, ComponentFactory& component_factory, ThreadLocal::Instance& tls) : options_(options), restarter_(restarter), start_time_(time(nullptr)), - original_start_time_(start_time_), - stats_store_(store), server_stats_{ALL_SERVER_STATS( - POOL_GAUGE_PREFIX(stats_store_, "server."))}, - thread_local_(tls), api_(new Api::Impl(options.fileFlushIntervalMsec())), - dispatcher_(api_->allocateDispatcher()), singleton_manager_(new Singleton::ManagerImpl()), + original_start_time_(start_time_), stats_store_(store), thread_local_(tls), + api_(new Api::Impl(options.fileFlushIntervalMsec())), dispatcher_(api_->allocateDispatcher()), + singleton_manager_(new Singleton::ManagerImpl()), handler_(new ConnectionHandlerImpl(ENVOY_LOGGER(), *dispatcher_)), listener_component_factory_(*this), worker_factory_(thread_local_, *api_, hooks), dns_resolver_(dispatcher_->createDnsResolver({})), @@ -61,18 +61,15 @@ InstanceImpl::InstanceImpl(Options& options, Network::Address::InstanceConstShar } } - failHealthcheck(false); - uint64_t version_int; if (!StringUtil::atoul(VersionInfo::revision().substr(0, 6).c_str(), version_int, 16)) { throw EnvoyException("compiled GIT SHA is invalid. Invalid build."); } - server_stats_.version_.set(version_int); restarter_.initialize(*dispatcher_, *this); drain_manager_ = component_factory.createDrainManager(*this); + initialize(options, local_address, component_factory, version_int); - initialize(options, local_address, component_factory); } catch (const EnvoyException& e) { ENVOY_LOG(critical, "error initializing configuration '{}': {}", options.configPath(), e.what()); @@ -102,7 +99,7 @@ void InstanceImpl::drainListeners() { void InstanceImpl::failHealthcheck(bool fail) { // We keep liveness state in shared memory so the parent process sees the same state. - server_stats_.live_.set(!fail); + server_stats_->live_.set(!fail); } void InstanceUtil::flushCountersAndGaugesToSinks(const std::list& sinks, @@ -115,7 +112,7 @@ void InstanceUtil::flushCountersAndGaugesToSinks(const std::list uint64_t delta = counter->latch(); if (counter->used()) { for (const auto& sink : sinks) { - sink->flushCounter(counter->name(), delta); + sink->flushCounter(*counter, delta); } } } @@ -123,7 +120,7 @@ void InstanceUtil::flushCountersAndGaugesToSinks(const std::list for (const Stats::GaugeSharedPtr& gauge : store.gauges()) { if (gauge->used()) { for (const auto& sink : sinks) { - sink->flushGauge(gauge->name(), gauge->value()); + sink->flushGauge(*gauge, gauge->value()); } } } @@ -137,13 +134,13 @@ void InstanceImpl::flushStats() { ENVOY_LOG(debug, "flushing stats"); HotRestart::GetParentStatsInfo info; restarter_.getParentStats(info); - server_stats_.uptime_.set(time(nullptr) - original_start_time_); - server_stats_.memory_allocated_.set(Memory::Stats::totalCurrentlyAllocated() + - info.memory_allocated_); - server_stats_.memory_heap_size_.set(Memory::Stats::totalCurrentlyReserved()); - server_stats_.parent_connections_.set(info.num_connections_); - server_stats_.total_connections_.set(numConnections() + info.num_connections_); - server_stats_.days_until_first_cert_expiring_.set( + server_stats_->uptime_.set(time(nullptr) - original_start_time_); + server_stats_->memory_allocated_.set(Memory::Stats::totalCurrentlyAllocated() + + info.memory_allocated_); + server_stats_->memory_heap_size_.set(Memory::Stats::totalCurrentlyReserved()); + server_stats_->parent_connections_.set(info.num_connections_); + server_stats_->total_connections_.set(numConnections() + info.num_connections_); + server_stats_->days_until_first_cert_expiring_.set( sslContextManager().daysUntilFirstCertExpires()); InstanceUtil::flushCountersAndGaugesToSinks(config_->statsSinks(), stats_store_); @@ -155,11 +152,11 @@ void InstanceImpl::getParentStats(HotRestart::GetParentStatsInfo& info) { info.num_connections_ = numConnections(); } -bool InstanceImpl::healthCheckFailed() { return server_stats_.live_.value() == 0; } +bool InstanceImpl::healthCheckFailed() { return server_stats_->live_.value() == 0; } void InstanceImpl::initialize(Options& options, Network::Address::InstanceConstSharedPtr local_address, - ComponentFactory& component_factory) { + ComponentFactory& component_factory, uint64_t version_int) { ENVOY_LOG(warn, "initializing epoch {} (hot restart version={})", options.restartEpoch(), restarter_.version()); @@ -175,6 +172,17 @@ void InstanceImpl::initialize(Options& options, Json::ObjectSharedPtr config_json = Json::Factory::loadFromFile(options.configPath()); Config::BootstrapJson::translateBootstrap(*config_json, bootstrap); } + + // Needs to happen as early as possible in the instantiation to preempt the objects that require + // stats. + initializeStatsTags(bootstrap); + + server_stats_.reset( + new ServerStats{ALL_SERVER_STATS(POOL_GAUGE_PREFIX(stats_store_, "server."))}); + + failHealthcheck(false); + + server_stats_->version_.set(version_int); bootstrap.mutable_node()->set_build_version(VersionInfo::version()); local_info_.reset( @@ -375,5 +383,33 @@ void InstanceImpl::shutdownAdmin() { restarter_.terminateParent(); } +void InstanceImpl::initializeStatsTags(const envoy::api::v2::Bootstrap& bootstrap) { + // Ensure no tag names are repeated. + std::unordered_set names; + auto add_tag = [&names, this](const std::string& name, const std::string& regex) { + if (!names.emplace(name).second) { + throw EnvoyException(fmt::format("Tag name '{}' specified twice.", name)); + } + + tag_extractors_.emplace_back(Stats::TagExtractorImpl::createTagExtractor(name, regex)); + }; + + stats_store_.setTagExtractors(tag_extractors_); + + // Add defaults. + if (!bootstrap.stats_config().has_use_all_default_tags() || + bootstrap.stats_config().use_all_default_tags().value()) { + for (const std::pair& default_tag : + Config::TagNames::get().regex_map_) { + add_tag(default_tag.first, default_tag.second); + } + } + + // Add custom tags. + for (const envoy::api::v2::TagSpecifier& tag_specifier : bootstrap.stats_config().stats_tags()) { + add_tag(tag_specifier.tag_name(), tag_specifier.regex()); + } +} + } // namespace Server } // namespace Envoy diff --git a/source/server/server.h b/source/server/server.h index a8f9546af1831..c1ba1ef3e0c54 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -19,6 +19,7 @@ #include "common/access_log/access_log_manager_impl.h" #include "common/runtime/runtime_impl.h" #include "common/ssl/context_manager_impl.h" +#include "common/stats/stats_impl.h" #include "server/http/admin.h" #include "server/init_manager_impl.h" @@ -154,17 +155,19 @@ class InstanceImpl : Logger::Loggable, public Instance { private: void flushStats(); void initialize(Options& options, Network::Address::InstanceConstSharedPtr local_address, - ComponentFactory& component_factory); + ComponentFactory& component_factory, uint64_t version_int); void loadServerFlags(const Optional& flags_path); uint64_t numConnections(); void startWorkers(); + void initializeStatsTags(const envoy::api::v2::Bootstrap& bootstrap); Options& options_; HotRestart& restarter_; const time_t start_time_; time_t original_start_time_; Stats::StoreRoot& stats_store_; - ServerStats server_stats_; + std::vector tag_extractors_; + std::unique_ptr server_stats_; ThreadLocal::Instance& thread_local_; Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; diff --git a/test/common/dynamo/dynamo_filter_test.cc b/test/common/dynamo/dynamo_filter_test.cc index 5e949fc87d48d..fcf9e2d0eec6d 100644 --- a/test/common/dynamo/dynamo_filter_test.cc +++ b/test/common/dynamo/dynamo_filter_test.cc @@ -15,6 +15,7 @@ #include "gtest/gtest.h" using testing::NiceMock; +using testing::Property; using testing::Return; using testing::ReturnRef; using testing::_; @@ -59,11 +60,21 @@ TEST_F(DynamoFilterTest, operatorPresent) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.Get.upstream_rq_total_200")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.Get.upstream_rq_total")); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.Get.upstream_rq_time_2xx", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.Get.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, deliverTimingToSinks("prefix.dynamodb.operation.Get.upstream_rq_time", _)); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.Get.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.Get.upstream_rq_time_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.Get.upstream_rq_time")); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.operation.Get.upstream_rq_time_2xx"), _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.operation.Get.upstream_rq_time_200"), _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.operation.Get.upstream_rq_time"), _)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, true)); } @@ -149,21 +160,42 @@ TEST_F(DynamoFilterTest, HandleErrorTypeTablePresent) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.GetItem.upstream_rq_total")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.GetItem.upstream_rq_total_4xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.GetItem.upstream_rq_total_400")); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time_4xx", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time_400", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time", _)); + + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time_4xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time_400")); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.GetItem.upstream_rq_time_4xx"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.GetItem.upstream_rq_time_400"), + _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.operation.GetItem.upstream_rq_time"), _)); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_4xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_400")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total")); + + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_4xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_400")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time")); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_4xx", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_4xx"), + _)); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_400", _)); - EXPECT_CALL(stats_, deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_400"), + _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.table.locations.upstream_rq_time"), _)); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->encodeData(error_data, true)); } @@ -197,12 +229,21 @@ TEST_F(DynamoFilterTest, BatchMultipleTables) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200")); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time"), + _)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, true)); } @@ -236,12 +277,21 @@ TEST_F(DynamoFilterTest, BatchMultipleTablesUnprocessedKeys) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200")); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time"), + _)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); @@ -292,12 +342,21 @@ TEST_F(DynamoFilterTest, BatchMultipleTablesNoUnprocessedKeys) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200")); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time"), + _)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); @@ -344,12 +403,21 @@ TEST_F(DynamoFilterTest, BatchMultipleTablesInvalidResponseBody) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200")); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time"), + _)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); @@ -390,22 +458,41 @@ TEST_F(DynamoFilterTest, bothOperationAndTableCorrect) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.GetItem.upstream_rq_total_200")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.GetItem.upstream_rq_total")); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time_2xx", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time", _)); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time")); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.GetItem.upstream_rq_time_2xx"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.GetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.operation.GetItem.upstream_rq_time"), _)); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_200")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time")); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_2xx", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_200"), + _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.table.locations.upstream_rq_time"), _)); Http::TestHeaderMapImpl response_headers{{":status", "200"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, true)); @@ -445,22 +532,41 @@ TEST_F(DynamoFilterTest, PartitionIdStats) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.GetItem.upstream_rq_total_200")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.GetItem.upstream_rq_total")); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time_2xx", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.GetItem.upstream_rq_time", _)); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.GetItem.upstream_rq_time")); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.GetItem.upstream_rq_time_2xx"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.GetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.operation.GetItem.upstream_rq_time"), _)); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_200")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time")); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_2xx", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_200"), + _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.table.locations.upstream_rq_time"), _)); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.capacity.GetItem.__partition_id=ition_1")) @@ -517,12 +623,21 @@ TEST_F(DynamoFilterTest, NoPartitionIdStatsForMultipleTables) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200")); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time"), + _)); EXPECT_CALL( stats_, @@ -580,22 +695,41 @@ TEST_F(DynamoFilterTest, PartitionIdStatsForSingleTableBatchOperation) { EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.operation.BatchGetItem.upstream_rq_total_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200")); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, deliverTimingToSinks( - "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.operation.BatchGetItem.upstream_rq_time", _)); + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time_200"), + _)); + EXPECT_CALL(stats_, deliverTimingToSinks( + Property(&Stats::Metric::name, + "prefix.dynamodb.operation.BatchGetItem.upstream_rq_time"), + _)); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_2xx")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total_200")); EXPECT_CALL(stats_, counter("prefix.dynamodb.table.locations.upstream_rq_total")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_2xx")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time_200")); + EXPECT_CALL(stats_, timer("prefix.dynamodb.table.locations.upstream_rq_time")); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_2xx", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_2xx"), + _)); EXPECT_CALL(stats_, - deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time_200", _)); - EXPECT_CALL(stats_, deliverTimingToSinks("prefix.dynamodb.table.locations.upstream_rq_time", _)); + deliverTimingToSinks(Property(&Stats::Metric::name, + "prefix.dynamodb.table.locations.upstream_rq_time_200"), + _)); + EXPECT_CALL( + stats_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.dynamodb.table.locations.upstream_rq_time"), _)); EXPECT_CALL( stats_, diff --git a/test/common/http/codes_test.cc b/test/common/http/codes_test.cc index d22bf2e505b5e..28b48b728d2e1 100644 --- a/test/common/http/codes_test.cc +++ b/test/common/http/codes_test.cc @@ -15,6 +15,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Property; using testing::_; namespace Envoy { @@ -183,17 +184,32 @@ TEST(CodeUtilityResponseTimingTest, All) { true, true, "vhost_name", "req_vcluster_name", "from_az", "to_az"}; + EXPECT_CALL(cluster_scope, timer("prefix.upstream_rq_time")); EXPECT_CALL(cluster_scope, - deliverTimingToSinks("prefix.upstream_rq_time", std::chrono::milliseconds(5))); + deliverTimingToSinks(Property(&Stats::Metric::name, "prefix.upstream_rq_time"), + std::chrono::milliseconds(5))); + + EXPECT_CALL(cluster_scope, timer("prefix.canary.upstream_rq_time")); EXPECT_CALL(cluster_scope, - deliverTimingToSinks("prefix.canary.upstream_rq_time", std::chrono::milliseconds(5))); - EXPECT_CALL(cluster_scope, deliverTimingToSinks("prefix.internal.upstream_rq_time", - std::chrono::milliseconds(5))); - EXPECT_CALL(global_store, - deliverTimingToSinks("vhost.vhost_name.vcluster.req_vcluster_name.upstream_rq_time", + deliverTimingToSinks(Property(&Stats::Metric::name, "prefix.canary.upstream_rq_time"), std::chrono::milliseconds(5))); - EXPECT_CALL(cluster_scope, deliverTimingToSinks("prefix.zone.from_az.to_az.upstream_rq_time", - std::chrono::milliseconds(5))); + + EXPECT_CALL(cluster_scope, timer("prefix.internal.upstream_rq_time")); + EXPECT_CALL(cluster_scope, deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.internal.upstream_rq_time"), + std::chrono::milliseconds(5))); + EXPECT_CALL(global_store, timer("vhost.vhost_name.vcluster.req_vcluster_name.upstream_rq_time")); + EXPECT_CALL( + global_store, + deliverTimingToSinks(Property(&Stats::Metric::name, + "vhost.vhost_name.vcluster.req_vcluster_name.upstream_rq_time"), + std::chrono::milliseconds(5))); + + EXPECT_CALL(cluster_scope, timer("prefix.zone.from_az.to_az.upstream_rq_time")); + EXPECT_CALL(cluster_scope, + deliverTimingToSinks( + Property(&Stats::Metric::name, "prefix.zone.from_az.to_az.upstream_rq_time"), + std::chrono::milliseconds(5))); CodeUtility::chargeResponseTiming(info); } diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index 84c95ad44eb64..dee612e2d686a 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -25,6 +25,7 @@ using testing::DoAll; using testing::InSequence; using testing::Invoke; using testing::NiceMock; +using testing::Property; using testing::Return; using testing::ReturnRef; using testing::SaveArg; @@ -185,8 +186,11 @@ struct ActiveTestRequest { * Test all timing stats are set. */ TEST_F(Http1ConnPoolImplTest, VerifyTimingStats) { - EXPECT_CALL(cluster_->stats_store_, deliverTimingToSinks("upstream_cx_connect_ms", _)); - EXPECT_CALL(cluster_->stats_store_, deliverTimingToSinks("upstream_cx_length_ms", _)); + + EXPECT_CALL(cluster_->stats_store_, + deliverTimingToSinks(Property(&Stats::Metric::name, "upstream_cx_connect_ms"), _)); + EXPECT_CALL(cluster_->stats_store_, + deliverTimingToSinks(Property(&Stats::Metric::name, "upstream_cx_length_ms"), _)); ActiveTestRequest r1(*this, 0, ActiveTestRequest::Type::CreateConnection); r1.startRequest(); diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index 73f48add07a25..e653b4943814d 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -22,6 +22,7 @@ using testing::DoAll; using testing::InSequence; using testing::Invoke; using testing::NiceMock; +using testing::Property; using testing::Return; using testing::ReturnRef; using testing::SaveArg; @@ -120,8 +121,10 @@ class ActiveTestRequest { TEST_F(Http2ConnPoolImplTest, VerifyConnectionTimingStats) { expectClientCreate(); - EXPECT_CALL(cluster_->stats_store_, deliverTimingToSinks("upstream_cx_connect_ms", _)); - EXPECT_CALL(cluster_->stats_store_, deliverTimingToSinks("upstream_cx_length_ms", _)); + EXPECT_CALL(cluster_->stats_store_, + deliverTimingToSinks(Property(&Stats::Metric::name, "upstream_cx_connect_ms"), _)); + EXPECT_CALL(cluster_->stats_store_, + deliverTimingToSinks(Property(&Stats::Metric::name, "upstream_cx_length_ms"), _)); ActiveTestRequest r1(*this, 0); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); diff --git a/test/common/mongo/proxy_test.cc b/test/common/mongo/proxy_test.cc index 40bcc826ef3aa..87e773fcc744e 100644 --- a/test/common/mongo/proxy_test.cc +++ b/test/common/mongo/proxy_test.cc @@ -22,6 +22,7 @@ using testing::AnyNumber; using testing::AtLeast; using testing::Invoke; using testing::NiceMock; +using testing::Property; using testing::Return; using testing::_; @@ -35,8 +36,9 @@ class MockDecoder : public Decoder { class TestStatStore : public Stats::IsolatedStoreImpl { public: - MOCK_METHOD2(deliverHistogramToSinks, void(const std::string& name, uint64_t value)); - MOCK_METHOD2(deliverTimingToSinks, void(const std::string& name, std::chrono::milliseconds ms)); + MOCK_METHOD2(deliverHistogramToSinks, void(const Stats::Metric& histogram, uint64_t value)); + MOCK_METHOD2(deliverTimingToSinks, + void(const Stats::Metric& timer, std::chrono::milliseconds ms)); }; class TestProxyFilter : public ProxyFilter { @@ -198,9 +200,15 @@ TEST_F(MongoProxyFilterTest, Stats) { })); filter_->onData(fake_data_); - EXPECT_CALL(store_, deliverHistogramToSinks("test.collection.test.query.reply_num_docs", 1)); - EXPECT_CALL(store_, deliverHistogramToSinks("test.collection.test.query.reply_size", 22)); - EXPECT_CALL(store_, deliverTimingToSinks("test.collection.test.query.reply_time_ms", _)); + EXPECT_CALL(store_, + deliverHistogramToSinks( + Property(&Stats::Metric::name, "test.collection.test.query.reply_num_docs"), 1)); + EXPECT_CALL(store_, + deliverHistogramToSinks( + Property(&Stats::Metric::name, "test.collection.test.query.reply_size"), 22)); + EXPECT_CALL(store_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "test.collection.test.query.reply_time_ms"), _)); EXPECT_CALL(*filter_->decoder_, onData(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { ReplyMessagePtr message(new ReplyMessageImpl(0, 0)); @@ -270,9 +278,12 @@ TEST_F(MongoProxyFilterTest, CommandStats) { })); filter_->onData(fake_data_); - EXPECT_CALL(store_, deliverHistogramToSinks("test.cmd.foo.reply_num_docs", 1)); - EXPECT_CALL(store_, deliverHistogramToSinks("test.cmd.foo.reply_size", 22)); - EXPECT_CALL(store_, deliverTimingToSinks("test.cmd.foo.reply_time_ms", _)); + EXPECT_CALL(store_, deliverHistogramToSinks( + Property(&Stats::Metric::name, "test.cmd.foo.reply_num_docs"), 1)); + EXPECT_CALL(store_, deliverHistogramToSinks( + Property(&Stats::Metric::name, "test.cmd.foo.reply_size"), 22)); + EXPECT_CALL(store_, deliverTimingToSinks( + Property(&Stats::Metric::name, "test.cmd.foo.reply_time_ms"), _)); EXPECT_CALL(*filter_->decoder_, onData(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { ReplyMessagePtr message(new ReplyMessageImpl(0, 0)); @@ -312,15 +323,29 @@ TEST_F(MongoProxyFilterTest, CallingFunctionStats) { EXPECT_EQ(1U, store_.counter("test.collection.test.callsite.getByMongoId.query.scatter_get").value()); - EXPECT_CALL(store_, deliverHistogramToSinks("test.collection.test.query.reply_num_docs", 1)); - EXPECT_CALL(store_, deliverHistogramToSinks("test.collection.test.query.reply_size", 22)); - EXPECT_CALL(store_, deliverTimingToSinks("test.collection.test.query.reply_time_ms", _)); + EXPECT_CALL(store_, + deliverHistogramToSinks( + Property(&Stats::Metric::name, "test.collection.test.query.reply_num_docs"), 1)); + EXPECT_CALL(store_, + deliverHistogramToSinks( + Property(&Stats::Metric::name, "test.collection.test.query.reply_size"), 22)); + EXPECT_CALL(store_, + deliverTimingToSinks( + Property(&Stats::Metric::name, "test.collection.test.query.reply_time_ms"), _)); + EXPECT_CALL(store_, + deliverHistogramToSinks( + Property(&Stats::Metric::name, + "test.collection.test.callsite.getByMongoId.query.reply_num_docs"), + 1)); EXPECT_CALL(store_, deliverHistogramToSinks( - "test.collection.test.callsite.getByMongoId.query.reply_num_docs", 1)); - EXPECT_CALL(store_, deliverHistogramToSinks( - "test.collection.test.callsite.getByMongoId.query.reply_size", 22)); - EXPECT_CALL(store_, deliverTimingToSinks( - "test.collection.test.callsite.getByMongoId.query.reply_time_ms", _)); + Property(&Stats::Metric::name, + "test.collection.test.callsite.getByMongoId.query.reply_size"), + 22)); + EXPECT_CALL(store_, + deliverTimingToSinks( + Property(&Stats::Metric::name, + "test.collection.test.callsite.getByMongoId.query.reply_time_ms"), + _)); EXPECT_CALL(*filter_->decoder_, onData(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { ReplyMessagePtr message(new ReplyMessageImpl(0, 0)); diff --git a/test/common/stats/BUILD b/test/common/stats/BUILD index b4fb2ec9e60b3..dfac46334292e 100644 --- a/test/common/stats/BUILD +++ b/test/common/stats/BUILD @@ -18,6 +18,7 @@ envoy_cc_test( name = "statsd_test", srcs = ["statsd_test.cc"], deps = [ + "//source/common/config:well_known_names", "//source/common/event:dispatcher_lib", "//source/common/network:utility_lib", "//source/common/stats:statsd_lib", @@ -27,6 +28,7 @@ envoy_cc_test( "//test/mocks/buffer:buffer_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", + "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", ], @@ -39,6 +41,7 @@ envoy_cc_test( "//source/common/network:address_lib", "//source/common/network:utility_lib", "//source/common/stats:statsd_lib", + "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", diff --git a/test/common/stats/stats_impl_test.cc b/test/common/stats/stats_impl_test.cc index a7d5447fa9903..042c0075c19eb 100644 --- a/test/common/stats/stats_impl_test.cc +++ b/test/common/stats/stats_impl_test.cc @@ -1,5 +1,7 @@ +#include #include +#include "common/config/well_known_names.h" #include "common/stats/stats_impl.h" #include "gtest/gtest.h" @@ -15,21 +17,41 @@ TEST(StatsIsolatedStoreImplTest, All) { Counter& c2 = scope1->counter("c2"); EXPECT_EQ("c1", c1.name()); EXPECT_EQ("scope1.c2", c2.name()); + EXPECT_EQ("c1", c1.tagExtractedName()); + EXPECT_EQ("scope1.c2", c2.tagExtractedName()); + EXPECT_EQ(0, c1.tags().size()); + EXPECT_EQ(0, c1.tags().size()); Gauge& g1 = store.gauge("g1"); Gauge& g2 = scope1->gauge("g2"); EXPECT_EQ("g1", g1.name()); EXPECT_EQ("scope1.g2", g2.name()); + EXPECT_EQ("g1", g1.tagExtractedName()); + EXPECT_EQ("scope1.g2", g2.tagExtractedName()); + EXPECT_EQ(0, g1.tags().size()); + EXPECT_EQ(0, g1.tags().size()); Timer& t1 = store.timer("t1"); Timer& t2 = scope1->timer("t2"); EXPECT_EQ("t1", t1.name()); EXPECT_EQ("scope1.t2", t2.name()); + EXPECT_EQ("t1", t1.tagExtractedName()); + EXPECT_EQ("scope1.t2", t2.tagExtractedName()); + EXPECT_EQ(0, t1.tags().size()); + EXPECT_EQ(0, t1.tags().size()); + t1.recordDuration(std::chrono::milliseconds(200)); + t2.recordDuration(std::chrono::milliseconds(200)); - store.deliverHistogramToSinks("h", 100); - store.deliverTimingToSinks("t", std::chrono::milliseconds(200)); - scope1->deliverHistogramToSinks("h", 100); - scope1->deliverTimingToSinks("t", std::chrono::milliseconds(200)); + Histogram& h1 = store.histogram("h1"); + Histogram& h2 = scope1->histogram("h2"); + EXPECT_EQ("h1", h1.name()); + EXPECT_EQ("scope1.h2", h2.name()); + EXPECT_EQ("h1", h1.tagExtractedName()); + EXPECT_EQ("scope1.h2", h2.tagExtractedName()); + EXPECT_EQ(0, h1.tags().size()); + EXPECT_EQ(0, h1.tags().size()); + h1.recordValue(100); + h2.recordValue(100); ScopePtr scope2 = scope1->createScope("foo."); EXPECT_EQ("scope1.foo.bar", scope2->counter("bar").name()); @@ -38,5 +60,240 @@ TEST(StatsIsolatedStoreImplTest, All) { EXPECT_EQ(2UL, store.gauges().size()); } +TEST(TagExtractorTest, TwoSubexpressions) { + TagExtractorImpl tag_extractor("cluster_name", "^cluster\\.((.+?)\\.)"); + std::string name = "cluster.test_cluster.upstream_cx_total"; + std::vector tags; + std::string tag_extracted_name = tag_extractor.updateTags(name, tags); + EXPECT_EQ("cluster.upstream_cx_total", tag_extracted_name); + ASSERT_EQ(1, tags.size()); + EXPECT_EQ("test_cluster", tags.at(0).value_); + EXPECT_EQ("cluster_name", tags.at(0).name_); +} + +TEST(TagExtractorTest, SingleSubexpression) { + TagExtractorImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)"); + std::string name = "listener.80.downstream_cx_total"; + std::vector tags; + std::string tag_extracted_name = tag_extractor.updateTags(name, tags); + EXPECT_EQ("listener.downstream_cx_total", tag_extracted_name); + ASSERT_EQ(1, tags.size()); + EXPECT_EQ("80.", tags.at(0).value_); + EXPECT_EQ("listner_port", tags.at(0).name_); +} + +class DefaultTagRegexTester { +public: + DefaultTagRegexTester() { + const auto& tag_names = Config::TagNames::get(); + + for (const std::pair& name_and_regex : tag_names.regex_map_) { + tag_extractors_.emplace_back(TagExtractorImpl::createTagExtractor(name_and_regex.first, "")); + } + } + void testRegex(const std::string& stat_name, const std::string& expected_tag_extracted_name, + const std::vector& expected_tags) { + + // Test forward iteration through the regexes + std::string tag_extracted_name = stat_name; + std::vector tags; + for (const TagExtractorPtr& tag_extractor : tag_extractors_) { + tag_extracted_name = tag_extractor->updateTags(tag_extracted_name, tags); + } + + auto cmp = [](const Tag& lhs, const Tag& rhs) { + return lhs.name_ == rhs.name_ && lhs.value_ == rhs.value_; + }; + + EXPECT_EQ(expected_tag_extracted_name, tag_extracted_name); + ASSERT_EQ(expected_tags.size(), tags.size()); + EXPECT_TRUE(std::is_permutation(expected_tags.begin(), expected_tags.end(), tags.begin(), cmp)) + << fmt::format("Stat name '{}' did not produce the expected tags", stat_name); + + // Reverse iteration through regexes to ensure ordering invariance + std::string rev_tag_extracted_name = stat_name; + std::vector rev_tags; + for (auto it = tag_extractors_.rbegin(); it != tag_extractors_.rend(); ++it) { + rev_tag_extracted_name = (*it)->updateTags(rev_tag_extracted_name, rev_tags); + } + + EXPECT_EQ(expected_tag_extracted_name, rev_tag_extracted_name); + ASSERT_EQ(expected_tags.size(), rev_tags.size()); + EXPECT_TRUE( + std::is_permutation(expected_tags.begin(), expected_tags.end(), rev_tags.begin(), cmp)) + << fmt::format("Stat name '{}' did not produce the expected tags when regexes were run in " + "reverse order", + stat_name); + } + + std::vector tag_extractors_; +}; + +TEST(TagExtractorTest, DefaultTagExtractors) { + const auto& tag_names = Config::TagNames::get(); + + // General cluster name + DefaultTagRegexTester regex_tester; + + // Cluster name + Tag cluster_tag; + cluster_tag.name_ = tag_names.CLUSTER_NAME; + cluster_tag.value_ = "ratelimit"; + + regex_tester.testRegex("cluster.ratelimit.upstream_rq_timeout", "cluster.upstream_rq_timeout", + {cluster_tag}); + + // Listener SSL + Tag listener_port; + listener_port.name_ = tag_names.LISTENER_PORT; + listener_port.value_ = "123"; + + Tag cipher_name; + cipher_name.name_ = tag_names.SSL_CIPHER; + cipher_name.value_ = "AES256-SHA"; + + regex_tester.testRegex("listener.123.ssl.cipher.AES256-SHA", "listener.ssl.cipher", + {listener_port, cipher_name}); + + // Mongo + Tag mongo_prefix; + mongo_prefix.name_ = tag_names.MONGO_PREFIX; + mongo_prefix.value_ = "mongo_filter"; + + Tag mongo_command; + mongo_command.name_ = tag_names.MONGO_CMD; + mongo_command.value_ = "foo_cmd"; + + Tag mongo_collection; + mongo_collection.name_ = tag_names.MONGO_COLLECTION; + mongo_collection.value_ = "bar_collection"; + + Tag mongo_callsite; + mongo_callsite.name_ = tag_names.MONGO_CALLSITE; + mongo_callsite.value_ = "baz_callsite"; + + regex_tester.testRegex("mongo.mongo_filter.op_reply", "mongo.op_reply", {mongo_prefix}); + regex_tester.testRegex("mongo.mongo_filter.cmd.foo_cmd.reply_size", "mongo.cmd.reply_size", + {mongo_prefix, mongo_command}); + regex_tester.testRegex("mongo.mongo_filter.collection.bar_collection.query.multi_get", + "mongo.collection.query.multi_get", {mongo_prefix, mongo_collection}); + regex_tester.testRegex( + "mongo.mongo_filter.collection.bar_collection.callsite.baz_callsite.query.scatter_get", + "mongo.collection.callsite.query.scatter_get", + {mongo_prefix, mongo_collection, mongo_callsite}); + + // Ratelimit + Tag ratelimit_prefix; + ratelimit_prefix.name_ = tag_names.RATELIMIT_PREFIX; + ratelimit_prefix.value_ = "foo_ratelimiter"; + regex_tester.testRegex("ratelimit.foo_ratelimiter.over_limit", "ratelimit.over_limit", + {ratelimit_prefix}); + + // Dynamo + Tag dynamo_http_prefix; + dynamo_http_prefix.name_ = tag_names.HTTP_CONN_MANAGER_PREFIX; + dynamo_http_prefix.value_ = "egress_dynamodb_iad"; + + Tag dynamo_operation; + dynamo_operation.name_ = tag_names.DYNAMO_OPERATION; + dynamo_operation.value_ = "Query"; + + Tag dynamo_table; + dynamo_table.name_ = tag_names.DYNAMO_TABLE; + dynamo_table.value_ = "bar_table"; + + Tag dynamo_partition; + dynamo_partition.name_ = tag_names.DYNAMO_PARTITION_ID; + dynamo_partition.value_ = "ABC1234"; + + regex_tester.testRegex("http.egress_dynamodb_iad.downstream_cx_total", "http.downstream_cx_total", + {dynamo_http_prefix}); + regex_tester.testRegex("http.egress_dynamodb_iad.dynamodb.operation.Query.upstream_rq_time", + "http.dynamodb.operation.upstream_rq_time", + {dynamo_http_prefix, dynamo_operation}); + regex_tester.testRegex("http.egress_dynamodb_iad.dynamodb.table.bar_table.upstream_rq_time", + "http.dynamodb.table.upstream_rq_time", + {dynamo_http_prefix, dynamo_table}); + regex_tester.testRegex( + "http.egress_dynamodb_iad.dynamodb.table.bar_table.capacity.Query.__partition_id=ABC1234", + "http.dynamodb.table.capacity", + {dynamo_http_prefix, dynamo_table, dynamo_operation, dynamo_partition}); + + // GRPC Http1.1 Bridge + Tag grpc_cluster; + grpc_cluster.name_ = tag_names.CLUSTER_NAME; + grpc_cluster.value_ = "grpc_cluster"; + + Tag grpc_service; + grpc_service.name_ = tag_names.GRPC_BRIDGE_SERVICE; + grpc_service.value_ = "grpc_service_1"; + + Tag grpc_method; + grpc_method.name_ = tag_names.GRPC_BRIDGE_METHOD; + grpc_method.value_ = "grpc_method_1"; + + regex_tester.testRegex("cluster.grpc_cluster.grpc.grpc_service_1.grpc_method_1.success", + "cluster.grpc.success", {grpc_cluster, grpc_method, grpc_service}); + + // Virtual host and cluster + Tag vhost; + vhost.name_ = tag_names.VIRTUAL_HOST; + vhost.value_ = "vhost_1"; + + Tag vcluster; + vcluster.name_ = tag_names.VIRTUAL_CLUSTER; + vcluster.value_ = "vcluster_1"; + + Tag response_code_class; + response_code_class.name_ = tag_names.RESPONSE_CODE_CLASS; + response_code_class.value_ = "2xx"; + + Tag response_code; + response_code.name_ = tag_names.RESPONSE_CODE; + response_code.value_ = "200"; + + regex_tester.testRegex("vhost.vhost_1.vcluster.vcluster_1.upstream_rq_2xx", + "vhost.vcluster.upstream_rq", {vhost, vcluster, response_code_class}); + regex_tester.testRegex("vhost.vhost_1.vcluster.vcluster_1.upstream_rq_200", + "vhost.vcluster.upstream_rq", {vhost, vcluster, response_code}); + + // User agent + Tag user_agent; + user_agent.name_ = tag_names.HTTP_USER_AGENT; + user_agent.value_ = "ios"; + + regex_tester.testRegex("http.egress_dynamodb_iad.user_agent.ios.downstream_cx_total", + "http.user_agent.downstream_cx_total", {user_agent, dynamo_http_prefix}); + + // Client SSL Prefix + Tag client_ssl; + client_ssl.name_ = tag_names.CLIENTSSL_PREFIX; + client_ssl.value_ = "clientssl_prefix"; + + regex_tester.testRegex("auth.clientssl.clientssl_prefix.auth_ip_white_list", + "auth.clientssl.auth_ip_white_list", {client_ssl}); + + // TCP Prefix + Tag tcp_prefix; + tcp_prefix.name_ = tag_names.TCP_PREFIX; + tcp_prefix.value_ = "tcp_prefix"; + + regex_tester.testRegex("tcp.tcp_prefix.downstream_flow_control_resumed_reading_total", + "tcp.downstream_flow_control_resumed_reading_total", {tcp_prefix}); + + // Fault Downstream Cluster + Tag fault_connection_manager; + fault_connection_manager.name_ = tag_names.HTTP_CONN_MANAGER_PREFIX; + fault_connection_manager.value_ = "fault_connection_manager"; + + Tag fault_downstream_cluster; + fault_downstream_cluster.name_ = tag_names.FAULT_DOWNSTREAM_CLUSTER; + fault_downstream_cluster.value_ = "fault_cluster"; + + regex_tester.testRegex("http.fault_connection_manager.fault.fault_cluster.aborts_injected", + "http.fault.aborts_injected", + {fault_connection_manager, fault_downstream_cluster}); +} + } // namespace Stats } // namespace Envoy diff --git a/test/common/stats/statsd_test.cc b/test/common/stats/statsd_test.cc index 90f2450c6985b..fe1b7823ae21d 100644 --- a/test/common/stats/statsd_test.cc +++ b/test/common/stats/statsd_test.cc @@ -9,6 +9,7 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" +#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" @@ -64,10 +65,15 @@ TEST_F(TcpStatsdSinkTest, EmptyFlush) { TEST_F(TcpStatsdSinkTest, BasicFlow) { InSequence s; + NiceMock counter; + counter.name_ = "test_counter"; + + NiceMock gauge; + gauge.name_ = "test_gauge"; sink_->beginFlush(); - sink_->flushCounter("test_counter", 1); - sink_->flushGauge("test_gauge", 2); + sink_->flushCounter(counter, 1); + sink_->flushGauge(gauge, 2); expectCreateConnection(); EXPECT_CALL(*connection_, @@ -78,11 +84,16 @@ TEST_F(TcpStatsdSinkTest, BasicFlow) { connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); expectCreateConnection(); + + NiceMock timer; + timer.name_ = "test_timer"; EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.test_timer:5|ms\n"))); - sink_->onTimespanComplete("test_timer", std::chrono::milliseconds(5)); + sink_->onTimespanComplete(timer, std::chrono::milliseconds(5)); + NiceMock histogram; + histogram.name_ = "histogram_test_timer"; EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.histogram_test_timer:15|ms\n"))); - sink_->onHistogramComplete("histogram_test_timer", 15); + sink_->onHistogramComplete(histogram, 15); EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)); tls_.shutdownThread(); @@ -91,9 +102,12 @@ TEST_F(TcpStatsdSinkTest, BasicFlow) { TEST_F(TcpStatsdSinkTest, BufferReallocate) { InSequence s; + NiceMock counter; + counter.name_ = "test_counter"; + sink_->beginFlush(); for (int i = 0; i < 2000; i++) { - sink_->flushCounter("test_counter", 1); + sink_->flushCounter(counter, 1); } expectCreateConnection(); @@ -110,18 +124,21 @@ TEST_F(TcpStatsdSinkTest, BufferReallocate) { TEST_F(TcpStatsdSinkTest, Overflow) { InSequence s; + NiceMock counter; + counter.name_ = "test_counter"; + // Synthetically set buffer above high watermark. Make sure we don't write anything. cluster_manager_.thread_local_cluster_.cluster_.info_->stats().upstream_cx_tx_bytes_buffered_.set( 1024 * 1024 * 17); sink_->beginFlush(); - sink_->flushCounter("test_counter", 1); + sink_->flushCounter(counter, 1); sink_->endFlush(); // Lower and make sure we write. cluster_manager_.thread_local_cluster_.cluster_.info_->stats().upstream_cx_tx_bytes_buffered_.set( 1024 * 1024 * 15); sink_->beginFlush(); - sink_->flushCounter("test_counter", 1); + sink_->flushCounter(counter, 1); expectCreateConnection(); EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.test_counter:1|c\n"))); sink_->endFlush(); @@ -130,7 +147,7 @@ TEST_F(TcpStatsdSinkTest, Overflow) { cluster_manager_.thread_local_cluster_.cluster_.info_->stats().upstream_cx_tx_bytes_buffered_.set( 1024 * 1024 * 17); sink_->beginFlush(); - sink_->flushCounter("test_counter", 1); + sink_->flushCounter(counter, 1); EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)); sink_->endFlush(); diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index 28a473b2cd721..7bac12f95f1ac 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -16,6 +16,7 @@ using testing::InSequence; using testing::Invoke; using testing::NiceMock; +using testing::Ref; using testing::Return; using testing::_; @@ -100,12 +101,17 @@ TEST_F(StatsThreadLocalStoreTest, NoTls) { Timer& t1 = store_->timer("t1"); EXPECT_EQ(&t1, &store_->timer("t1")); - - EXPECT_CALL(sink_, onHistogramComplete("h", 100)); - store_->deliverHistogramToSinks("h", 100); - - EXPECT_CALL(sink_, onTimespanComplete("t", std::chrono::milliseconds(200))); - store_->deliverTimingToSinks("t", std::chrono::milliseconds(200)); + EXPECT_CALL(sink_, onTimespanComplete(Ref(t1), std::chrono::milliseconds(200))); + t1.recordDuration(std::chrono::milliseconds(200)); + EXPECT_CALL(sink_, onTimespanComplete(Ref(t1), std::chrono::milliseconds(100))); + store_->deliverTimingToSinks(t1, std::chrono::milliseconds(100)); + + Histogram& h1 = store_->histogram("h1"); + EXPECT_EQ(&h1, &store_->histogram("h1")); + EXPECT_CALL(sink_, onHistogramComplete(Ref(h1), 200)); + h1.recordValue(200); + EXPECT_CALL(sink_, onHistogramComplete(Ref(h1), 100)); + store_->deliverHistogramToSinks(h1, 100); EXPECT_EQ(2UL, store_->counters().size()); EXPECT_EQ(&c1, store_->counters().front().get()); @@ -176,16 +182,23 @@ TEST_F(StatsThreadLocalStoreTest, BasicScope) { Timer& t2 = scope1->timer("t2"); EXPECT_EQ("t1", t1.name()); EXPECT_EQ("scope1.t2", t2.name()); - - EXPECT_CALL(sink_, onHistogramComplete("scope1.h", 100)); - scope1->deliverHistogramToSinks("h", 100); - - EXPECT_CALL(sink_, onTimespanComplete("scope1.t", std::chrono::milliseconds(200))); - scope1->deliverTimingToSinks("t", std::chrono::milliseconds(200)); + EXPECT_CALL(sink_, onTimespanComplete(Ref(t1), std::chrono::milliseconds(100))); + t1.recordDuration(std::chrono::milliseconds(100)); + EXPECT_CALL(sink_, onTimespanComplete(Ref(t2), std::chrono::milliseconds(200))); + t2.recordDuration(std::chrono::milliseconds(200)); + + Histogram& h1 = store_->histogram("h1"); + Histogram& h2 = scope1->histogram("h2"); + EXPECT_EQ("h1", h1.name()); + EXPECT_EQ("scope1.h2", h2.name()); + EXPECT_CALL(sink_, onHistogramComplete(Ref(h1), 100)); + h1.recordValue(100); + EXPECT_CALL(sink_, onHistogramComplete(Ref(h2), 200)); + h2.recordValue(200); store_->shutdownThreading(); - scope1->deliverHistogramToSinks("h", 100); - scope1->deliverTimingToSinks("t", std::chrono::milliseconds(200)); + scope1->deliverHistogramToSinks(h1, 100); + scope1->deliverTimingToSinks(t1, std::chrono::milliseconds(200)); tls_.shutdownThread(); // Includes overflow stat. diff --git a/test/common/stats/udp_statsd_test.cc b/test/common/stats/udp_statsd_test.cc index db13ee24cd0bf..647b9901a2c35 100644 --- a/test/common/stats/udp_statsd_test.cc +++ b/test/common/stats/udp_statsd_test.cc @@ -4,6 +4,7 @@ #include "common/network/utility.h" #include "common/stats/statsd.h" +#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" @@ -33,10 +34,21 @@ TEST_P(UdpStatsdSinkTest, InitWithIpAddress) { EXPECT_NE(fd, -1); // Check that fd has not changed. - sink.flushCounter("test_counter", 1); - sink.flushGauge("test_gauge", 1); - sink.onHistogramComplete("histogram_test_timer", 5); - sink.onTimespanComplete("test_timer", std::chrono::milliseconds(5)); + NiceMock counter; + counter.name_ = "test_counter"; + sink.flushCounter(counter, 1); + + NiceMock gauge; + counter.name_ = "test_gauge"; + sink.flushGauge(gauge, 1); + + NiceMock histogram; + histogram.name_ = "histogram_test_timer"; + sink.onHistogramComplete(histogram, 5); + + NiceMock timer; + histogram.name_ = "test_timer"; + sink.onTimespanComplete(timer, std::chrono::milliseconds(5)); EXPECT_EQ(fd, sink.getFdForTests()); if (GetParam() == Network::Address::IpVersion::v4) { diff --git a/test/integration/server.h b/test/integration/server.h index d6c521713df71..986edc5bd2018 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -100,14 +100,14 @@ class TestScopeWrapper : public Scope { return ScopePtr{new TestScopeWrapper(lock_, wrapped_scope_->createScope(name))}; } - void deliverHistogramToSinks(const std::string& name, uint64_t value) override { + void deliverHistogramToSinks(const Metric& histogram, uint64_t value) override { std::unique_lock lock(lock_); - wrapped_scope_->deliverHistogramToSinks(name, value); + wrapped_scope_->deliverHistogramToSinks(histogram, value); } - void deliverTimingToSinks(const std::string& name, std::chrono::milliseconds ms) override { + void deliverTimingToSinks(const Metric& timer, std::chrono::milliseconds ms) override { std::unique_lock lock(lock_); - wrapped_scope_->deliverTimingToSinks(name, ms); + wrapped_scope_->deliverTimingToSinks(timer, ms); } Counter& counter(const std::string& name) override { @@ -125,6 +125,11 @@ class TestScopeWrapper : public Scope { return wrapped_scope_->timer(name); } + Histogram& histogram(const std::string& name) override { + std::unique_lock lock(lock_); + return wrapped_scope_->histogram(name); + } + private: std::mutex& lock_; ScopePtr wrapped_scope_; @@ -145,8 +150,8 @@ class TestIsolatedStoreImpl : public StoreRoot { std::unique_lock lock(lock_); return ScopePtr{new TestScopeWrapper(lock_, store_.createScope(name))}; } - void deliverHistogramToSinks(const std::string&, uint64_t) override {} - void deliverTimingToSinks(const std::string&, std::chrono::milliseconds) override {} + void deliverHistogramToSinks(const Metric&, uint64_t) override {} + void deliverTimingToSinks(const Metric&, std::chrono::milliseconds) override {} Gauge& gauge(const std::string& name) override { std::unique_lock lock(lock_); return store_.gauge(name); @@ -155,6 +160,10 @@ class TestIsolatedStoreImpl : public StoreRoot { std::unique_lock lock(lock_); return store_.timer(name); } + Histogram& histogram(const std::string& name) override { + std::unique_lock lock(lock_); + return store_.histogram(name); + } // Stats::Store std::list counters() const override { @@ -168,6 +177,7 @@ class TestIsolatedStoreImpl : public StoreRoot { // Stats::StoreRoot void addSink(Sink&) override {} + void setTagExtractors(const std::vector&) override {} void initializeThreading(Event::Dispatcher&, ThreadLocal::Instance&) override {} void shutdownThreading() override {} diff --git a/test/mocks/stats/mocks.cc b/test/mocks/stats/mocks.cc index 9dd2d44aac643..65d17f075d4a4 100644 --- a/test/mocks/stats/mocks.cc +++ b/test/mocks/stats/mocks.cc @@ -3,11 +3,20 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnPointee; +using testing::ReturnRef; using testing::_; namespace Envoy { namespace Stats { +MockMetric::MockMetric() { ON_CALL(*this, name()).WillByDefault(ReturnPointee(&name_)); } + +MockMetric::~MockMetric() {} + MockCounter::MockCounter() {} MockCounter::~MockCounter() {} @@ -17,10 +26,28 @@ MockGauge::~MockGauge() {} MockTimespan::MockTimespan() {} MockTimespan::~MockTimespan() {} +MockTimer::MockTimer() { + ON_CALL(*this, recordDuration(_)).WillByDefault(Invoke([this](std::chrono::milliseconds ms) { + if (store_ != nullptr) { + store_->deliverTimingToSinks(*this, ms); + } + })); +} +MockTimer::~MockTimer() {} + MockSink::MockSink() {} MockSink::~MockSink() {} -MockStore::MockStore() { ON_CALL(*this, counter(_)).WillByDefault(ReturnRef(counter_)); } +MockStore::MockStore() { + ON_CALL(*this, counter(_)).WillByDefault(ReturnRef(counter_)); + ON_CALL(*this, timer(_)).WillByDefault(Invoke([this](const std::string& name) -> Timer& { + auto* timer = new NiceMock; + timer->name_ = name; + timer->store_ = this; + timers_.emplace_back(timer); + return *timer; + })); +} MockStore::~MockStore() {} MockIsolatedStatsStore::MockIsolatedStatsStore() {} diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index 25901feed0c26..e7548b3bc3b23 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -16,6 +16,18 @@ namespace Envoy { namespace Stats { +class MockMetric : public Metric { +public: + MockMetric(); + ~MockMetric(); + + MOCK_CONST_METHOD0(name, std::string()); + MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); + MOCK_CONST_METHOD0(tags, const std::vector&()); + + std::string name_; +}; + class MockCounter : public Counter { public: MockCounter(); @@ -24,10 +36,12 @@ class MockCounter : public Counter { MOCK_METHOD1(add, void(uint64_t amount)); MOCK_METHOD0(inc, void()); MOCK_METHOD0(latch, uint64_t()); - MOCK_METHOD0(name, std::string()); + MOCK_CONST_METHOD0(name, std::string()); + MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); + MOCK_CONST_METHOD0(tags, const std::vector&()); MOCK_METHOD0(reset, void()); - MOCK_METHOD0(used, bool()); - MOCK_METHOD0(value, uint64_t()); + MOCK_CONST_METHOD0(used, bool()); + MOCK_CONST_METHOD0(value, uint64_t()); }; class MockGauge : public Gauge { @@ -38,11 +52,13 @@ class MockGauge : public Gauge { MOCK_METHOD1(add, void(uint64_t amount)); MOCK_METHOD0(dec, void()); MOCK_METHOD0(inc, void()); - MOCK_METHOD0(name, std::string()); + MOCK_CONST_METHOD0(name, std::string()); + MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); + MOCK_CONST_METHOD0(tags, const std::vector&()); MOCK_METHOD1(set, void(uint64_t value)); MOCK_METHOD1(sub, void(uint64_t amount)); - MOCK_METHOD0(used, bool()); - MOCK_METHOD0(value, uint64_t()); + MOCK_CONST_METHOD0(used, bool()); + MOCK_CONST_METHOD0(value, uint64_t()); }; class MockTimespan : public Timespan { @@ -54,17 +70,34 @@ class MockTimespan : public Timespan { MOCK_METHOD1(complete, void(const std::string& dynamic_name)); }; +class MockTimer : public Timer { +public: + MockTimer(); + ~MockTimer(); + + // Note: cannot be mocked because it is accessed as a Property in a gmock EXPECT_CALL. This + // creates a deadlock in gmock and is an unintended use of mock functions. + std::string name() const override { return name_; }; + MOCK_METHOD0(allocateSpan, TimespanPtr()); + MOCK_METHOD1(recordDuration, void(std::chrono::milliseconds ms)); + MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); + MOCK_CONST_METHOD0(tags, const std::vector&()); + + std::string name_; + Store* store_; +}; + class MockSink : public Sink { public: MockSink(); ~MockSink(); MOCK_METHOD0(beginFlush, void()); - MOCK_METHOD2(flushCounter, void(const std::string& name, uint64_t delta)); - MOCK_METHOD2(flushGauge, void(const std::string& name, uint64_t value)); + MOCK_METHOD2(flushCounter, void(const Metric& counter, uint64_t delta)); + MOCK_METHOD2(flushGauge, void(const Metric& gauge, uint64_t value)); MOCK_METHOD0(endFlush, void()); - MOCK_METHOD2(onHistogramComplete, void(const std::string& name, uint64_t value)); - MOCK_METHOD2(onTimespanComplete, void(const std::string& name, std::chrono::milliseconds ms)); + MOCK_METHOD2(onHistogramComplete, void(const Metric& histogram, uint64_t value)); + MOCK_METHOD2(onTimespanComplete, void(const Metric& timer, std::chrono::milliseconds ms)); }; class MockStore : public Store { @@ -74,16 +107,18 @@ class MockStore : public Store { ScopePtr createScope(const std::string& name) override { return ScopePtr{createScope_(name)}; } - MOCK_METHOD2(deliverHistogramToSinks, void(const std::string& name, uint64_t value)); - MOCK_METHOD2(deliverTimingToSinks, void(const std::string&, std::chrono::milliseconds)); + MOCK_METHOD2(deliverHistogramToSinks, void(const Metric& histogram, uint64_t value)); + MOCK_METHOD2(deliverTimingToSinks, void(const Metric& timer, std::chrono::milliseconds ms)); MOCK_METHOD1(counter, Counter&(const std::string&)); MOCK_CONST_METHOD0(counters, std::list()); MOCK_METHOD1(createScope_, Scope*(const std::string& name)); MOCK_METHOD1(gauge, Gauge&(const std::string&)); MOCK_CONST_METHOD0(gauges, std::list()); MOCK_METHOD1(timer, Timer&(const std::string& name)); + MOCK_METHOD1(histogram, Histogram&(const std::string& name)); testing::NiceMock counter_; + std::vector> timers_; }; /** @@ -95,7 +130,7 @@ class MockIsolatedStatsStore : public IsolatedStoreImpl { MockIsolatedStatsStore(); ~MockIsolatedStatsStore(); - MOCK_METHOD2(deliverTimingToSinks, void(const std::string&, std::chrono::milliseconds)); + MOCK_METHOD2(deliverTimingToSinks, void(const Metric& timer, std::chrono::milliseconds)); }; } // namespace Stats diff --git a/test/server/server_test.cc b/test/server/server_test.cc index 7235da0d5e974..81c8e85a7073c 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -12,6 +12,7 @@ #include "gtest/gtest.h" using testing::InSequence; +using testing::Property; using testing::SaveArg; using testing::StrictMock; using testing::_; @@ -27,8 +28,8 @@ TEST(ServerInstanceUtil, flushHelper) { store.gauge("world").set(5); std::unique_ptr sink(new StrictMock()); EXPECT_CALL(*sink, beginFlush()); - EXPECT_CALL(*sink, flushCounter("hello", 1)); - EXPECT_CALL(*sink, flushGauge("world", 5)); + EXPECT_CALL(*sink, flushCounter(Property(&Stats::Metric::name, "hello"), 1)); + EXPECT_CALL(*sink, flushGauge(Property(&Stats::Metric::name, "world"), 5)); EXPECT_CALL(*sink, endFlush()); std::list sinks;