From f6d3ec41624b679adc1039dd8c959d7506019c43 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 4 Mar 2019 14:59:18 -0500 Subject: [PATCH 01/39] Use SymbolTable API for creating and representing stat names. Signed-off-by: Joshua Marantz --- include/envoy/server/instance.h | 5 + include/envoy/stats/BUILD | 8 +- include/envoy/stats/scope.h | 11 +- include/envoy/stats/stat_data_allocator.h | 11 +- include/envoy/stats/stats.h | 30 ++- include/envoy/stats/symbol_table.h | 1 + source/common/http/BUILD | 1 + source/common/http/user_agent.cc | 3 +- source/common/router/router.cc | 5 + source/common/router/router.h | 17 +- source/common/stats/BUILD | 18 ++ source/common/stats/heap_stat_data.cc | 56 ++-- source/common/stats/heap_stat_data.h | 103 ++++--- source/common/stats/histogram_impl.h | 34 +-- source/common/stats/isolated_store_impl.cc | 56 ++-- source/common/stats/isolated_store_impl.h | 40 ++- source/common/stats/metric_impl.cc | 73 +++++ source/common/stats/metric_impl.h | 35 ++- source/common/stats/raw_stat_data.cc | 10 +- source/common/stats/raw_stat_data.h | 48 +++- source/common/stats/scope_prefixer.cc | 60 +++++ source/common/stats/scope_prefixer.h | 53 ++++ .../common/stats/stat_data_allocator_impl.h | 101 +++---- source/common/stats/symbol_table_impl.h | 2 +- source/common/stats/thread_local_store.cc | 252 +++++++++++------- source/common/stats/thread_local_store.h | 117 ++++---- source/common/stats/utility.cc | 12 +- source/common/stats/utility.h | 4 +- source/common/upstream/cluster_manager_impl.h | 1 + source/exe/BUILD | 1 + source/exe/main_common.cc | 6 +- source/exe/main_common.h | 3 +- source/extensions/stat_sinks/hystrix/BUILD | 1 + .../extensions/stat_sinks/hystrix/hystrix.cc | 15 +- .../extensions/stat_sinks/hystrix/hystrix.h | 6 + source/server/config_validation/server.h | 2 + source/server/hot_restart_impl.cc | 6 +- source/server/hot_restart_impl.h | 2 +- source/server/hot_restart_nop_impl.h | 2 +- source/server/server.h | 1 + test/common/grpc/BUILD | 1 + .../grpc_client_integration_test_harness.h | 5 +- test/common/http/async_client_impl_test.cc | 2 +- test/common/http/codes_speed_test.cc | 3 + .../http/conn_manager_impl_fuzz_test.cc | 1 + test/common/http/conn_manager_impl_test.cc | 2 +- test/common/http/user_agent_test.cc | 2 +- test/common/router/router_test.cc | 2 +- test/common/stats/heap_stat_data_test.cc | 55 +++- test/common/stats/isolated_store_impl_test.cc | 33 +-- test/common/stats/raw_stat_data_test.cc | 3 +- .../stats/thread_local_store_speed_test.cc | 17 +- test/common/stats/thread_local_store_test.cc | 34 ++- test/common/tcp_proxy/tcp_proxy_test.cc | 6 +- .../upstream/cluster_manager_impl_test.cc | 21 +- .../filters/http/ext_authz/ext_authz_test.cc | 23 +- .../filters/http/ratelimit/ratelimit_test.cc | 2 +- .../common/statsd/udp_statsd_test.cc | 4 + .../stats_sinks/hystrix/hystrix_test.cc | 13 +- test/integration/BUILD | 1 + test/integration/integration.cc | 1 + test/integration/integration.h | 4 + test/integration/integration_admin_test.cc | 2 +- test/integration/server.cc | 21 +- test/integration/server.h | 41 ++- test/integration/utility.h | 2 +- test/mocks/server/mocks.cc | 2 +- test/mocks/server/mocks.h | 6 +- test/mocks/stats/BUILD | 2 + test/mocks/stats/mocks.cc | 41 ++- test/mocks/stats/mocks.h | 102 ++++--- test/mocks/upstream/host.h | 4 +- .../config_validation/cluster_manager_test.cc | 1 + test/server/hot_restart_impl_test.cc | 5 +- test/server/http/admin_test.cc | 14 +- test/test_common/BUILD | 1 + test/test_common/global.h | 1 + test/test_common/utility.cc | 9 +- test/test_common/utility.h | 16 +- 79 files changed, 1174 insertions(+), 543 deletions(-) create mode 100644 source/common/stats/metric_impl.cc create mode 100644 source/common/stats/scope_prefixer.cc create mode 100644 source/common/stats/scope_prefixer.h diff --git a/include/envoy/server/instance.h b/include/envoy/server/instance.h index a3d548eb3c77..9a09d81b7d14 100644 --- a/include/envoy/server/instance.h +++ b/include/envoy/server/instance.h @@ -203,6 +203,11 @@ class Instance { */ virtual TimeSource& timeSource() PURE; + /** + * @return the statistics symbol table. + */ + virtual Stats::SymbolTable& symbolTable() PURE; + /** * @return the flush interval of stats sinks. */ diff --git a/include/envoy/stats/BUILD b/include/envoy/stats/BUILD index 9f1e513135b6..9162d1b1ca41 100644 --- a/include/envoy/stats/BUILD +++ b/include/envoy/stats/BUILD @@ -25,12 +25,18 @@ envoy_cc_library( "tag_extractor.h", "tag_producer.h", ], - deps = ["//include/envoy/common:interval_set_interface"], + deps = [ + ":symbol_table_interface", + "//include/envoy/common:interval_set_interface", + ], ) envoy_cc_library( name = "symbol_table_interface", hdrs = ["symbol_table.h"], + deps = [ + "//source/common/common:hash_lib", + ], ) envoy_cc_library( diff --git a/include/envoy/stats/scope.h b/include/envoy/stats/scope.h index ef913edea6e0..70be7f9222a0 100644 --- a/include/envoy/stats/scope.h +++ b/include/envoy/stats/scope.h @@ -2,11 +2,11 @@ #include #include -#include #include "envoy/common/pure.h" #include "envoy/stats/histogram.h" #include "envoy/stats/stats_options.h" +#include "envoy/stats/symbol_table.h" namespace Envoy { namespace Stats { @@ -44,16 +44,19 @@ class Scope { /** * @return a counter within the scope's namespace. */ + virtual Counter& counterx(StatName name) PURE; virtual Counter& counter(const std::string& name) PURE; /** * @return a gauge within the scope's namespace. */ + virtual Gauge& gaugex(StatName name) PURE; virtual Gauge& gauge(const std::string& name) PURE; /** * @return a histogram within the scope's namespace with a particular value type. */ + virtual Histogram& histogramx(StatName name) PURE; virtual Histogram& histogram(const std::string& name) PURE; /** @@ -61,6 +64,12 @@ class Scope { * maximum allowable object name length and stat suffix length. */ virtual const Stats::StatsOptions& statsOptions() const PURE; + + /** + * @return a reference to the symbol table. + */ + virtual const SymbolTable& symbolTable() const PURE; + virtual SymbolTable& symbolTable() PURE; }; } // namespace Stats diff --git a/include/envoy/stats/stat_data_allocator.h b/include/envoy/stats/stat_data_allocator.h index f0ea93e266d0..ebb6117840b6 100644 --- a/include/envoy/stats/stat_data_allocator.h +++ b/include/envoy/stats/stat_data_allocator.h @@ -34,8 +34,8 @@ class StatDataAllocator { * @return CounterSharedPtr a counter, or nullptr if allocation failed, in which case * tag_extracted_name and tags are not moved. */ - virtual CounterSharedPtr makeCounter(absl::string_view name, std::string&& tag_extracted_name, - std::vector&& tags) PURE; + virtual CounterSharedPtr makeCounter(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags) PURE; /** * @param name the full name of the stat. @@ -44,14 +44,17 @@ class StatDataAllocator { * @return GaugeSharedPtr a gauge, or nullptr if allocation failed, in which case * tag_extracted_name and tags are not moved. */ - virtual GaugeSharedPtr makeGauge(absl::string_view name, std::string&& tag_extracted_name, - std::vector&& tags) PURE; + virtual GaugeSharedPtr makeGauge(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags) PURE; /** * Determines whether this stats allocator requires bounded stat-name size. */ virtual bool requiresBoundedStatNameSize() const PURE; + virtual const SymbolTable& symbolTable() const PURE; + virtual SymbolTable& symbolTable() PURE; + // TODO(jmarantz): create a parallel mechanism to instantiate histograms. At // the moment, histograms don't fit the same pattern of counters and gauges // as they are not actually created in the context of a stats allocator. diff --git a/include/envoy/stats/stats.h b/include/envoy/stats/stats.h index 6d191bf24f9f..d93fb76166d0 100644 --- a/include/envoy/stats/stats.h +++ b/include/envoy/stats/stats.h @@ -6,12 +6,14 @@ #include #include "envoy/common/pure.h" +#include "envoy/stats/symbol_table.h" #include "absl/strings/string_view.h" namespace Envoy { namespace Stats { +class StatDataAllocator; struct Tag; /** @@ -32,32 +34,34 @@ class Metric { virtual std::string name() const PURE; /** - * Returns the full name of the Metric as a nul-terminated string. The - * intention is use this as a hash-map key, so that the stat name storage - * is not duplicated in every map. You cannot use name() above for this, - * as it returns a std::string by value, as not all stat implementations - * contain the name as a std::string. - * - * Note that in the future, the plan is to replace this method with one that - * returns a reference to a symbolized representation of the elaborated string - * (see source/common/stats/symbol_table_impl.h). + * Returns the full name of the Metric as an encoded array of symbols. */ - virtual const char* nameCStr() const PURE; + virtual StatName statName() const PURE; /** * Returns a vector of configurable tags to identify this Metric. */ - virtual const std::vector& tags() const PURE; + virtual std::vector tags() const PURE; /** - * Returns the name of the Metric with the portions designated as tags removed. + * Returns the name of the Metric with the portions designated as tags + * removed as a string. */ - virtual const std::string& tagExtractedName() const PURE; + virtual std::string tagExtractedName() const PURE; + + /** + * Returns the name of the Metric with the portions designated as tags + * removed as a StatName + */ + virtual StatName tagExtractedStatName() const PURE; /** * Indicates whether this metric has been updated since the server was started. */ virtual bool used() const PURE; + + virtual SymbolTable& symbolTable() PURE; + virtual const SymbolTable& symbolTable() const PURE; }; /** diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index b0efd1cbd75d..7b88c6d4665a 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -135,6 +135,7 @@ class SymbolTable { #endif private: + friend struct HeapStatData; friend class StatNameStorage; friend class StatNameList; diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 903f86182865..350d97a651a9 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -89,6 +89,7 @@ envoy_cc_library( "//include/envoy/stats:stats_interface", "//source/common/common:enum_to_int", "//source/common/common:utility_lib", + "//source/common/stats:symbol_table_lib", "@envoy_api//envoy/type:http_status_cc", ], ) diff --git a/source/common/http/user_agent.cc b/source/common/http/user_agent.cc index 57cb82e57103..75c4befe294c 100644 --- a/source/common/http/user_agent.cc +++ b/source/common/http/user_agent.cc @@ -21,7 +21,8 @@ void UserAgent::completeConnectionLength(Stats::Timespan& span) { return; } - scope_->histogram(prefix_ + "downstream_cx_length_ms").recordValue(span.getRawDuration().count()); + Stats::Histogram& histogram = scope_->histogram(prefix_ + "downstream_cx_length_ms"); + histogram.recordValue(span.getRawDuration().count()); } void UserAgent::initializeFromHeaders(const HeaderMap& headers, const std::string& prefix, diff --git a/source/common/router/router.cc b/source/common/router/router.cc index f794dcbbd400..5f56f114d8d8 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -179,6 +179,11 @@ FilterUtility::finalTimeout(const RouteEntry& route, Http::HeaderMap& request_he return timeout; } +Filter::Filter(FilterConfig& config) + : config_(config), downstream_response_started_(false), downstream_end_stream_(false), + do_shadowing_(false), is_retry_(false), + attempting_internal_redirect_with_complete_stream_(false) {} + Filter::~Filter() { // Upstream resources should already have been cleaned. ASSERT(!upstream_request_); diff --git a/source/common/router/router.h b/source/common/router/router.h index eb3d13f8e69e..b0d78d80dc41 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -103,8 +103,8 @@ class FilterConfig { : scope_(scope), local_info_(local_info), cm_(cm), runtime_(runtime), random_(random), stats_{ALL_ROUTER_STATS(POOL_COUNTER_PREFIX(scope, stat_prefix))}, emit_dynamic_stats_(emit_dynamic_stats), start_child_span_(start_child_span), - suppress_envoy_headers_(suppress_envoy_headers), http_context_(http_context), - shadow_writer_(std::move(shadow_writer)), time_source_(time_source) {} + suppress_envoy_headers_(suppress_envoy_headers), shadow_writer_(std::move(shadow_writer)), + time_source_(time_source), http_context_(http_context) {} FilterConfig(const std::string& stat_prefix, Server::Configuration::FactoryContext& context, ShadowWriterPtr&& shadow_writer, @@ -121,6 +121,7 @@ class FilterConfig { ShadowWriter& shadowWriter() { return *shadow_writer_; } TimeSource& timeSource() { return time_source_; } + Http::Context& httpContext() { return http_context_; } Stats::Scope& scope_; const LocalInfo::LocalInfo& local_info_; @@ -132,11 +133,11 @@ class FilterConfig { const bool start_child_span_; const bool suppress_envoy_headers_; std::list upstream_logs_; - Http::Context& http_context_; private: ShadowWriterPtr shadow_writer_; TimeSource& time_source_; + Http::Context& http_context_; }; typedef std::shared_ptr FilterConfigSharedPtr; @@ -148,12 +149,8 @@ class Filter : Logger::Loggable, public Http::StreamDecoderFilter, public Upstream::LoadBalancerContextBase { public: - Filter(FilterConfig& config) - : config_(config), downstream_response_started_(false), downstream_end_stream_(false), - do_shadowing_(false), is_retry_(false), - attempting_internal_redirect_with_complete_stream_(false) {} - - ~Filter(); + explicit Filter(FilterConfig& config); + ~Filter() override; // Http::StreamFilterBase void onDestroy() override; @@ -389,7 +386,7 @@ class Filter : Logger::Loggable, // and handle difference between gRPC and non-gRPC requests. void handleNon5xxResponseHeaders(const Http::HeaderMap& headers, bool end_stream); TimeSource& timeSource() { return config_.timeSource(); } - Http::Context& httpContext() { return config_.http_context_; } + Http::Context& httpContext() { return config_.httpContext(); } FilterConfig& config_; Http::StreamDecoderFilterCallbacks* callbacks_{}; diff --git a/source/common/stats/BUILD b/source/common/stats/BUILD index 85052c236194..b01c94da80e3 100644 --- a/source/common/stats/BUILD +++ b/source/common/stats/BUILD @@ -13,6 +13,7 @@ envoy_cc_library( srcs = ["heap_stat_data.cc"], hdrs = ["heap_stat_data.h"], deps = [ + ":metric_impl_lib", ":stat_data_allocator_lib", "//source/common/common:assert_lib", "//source/common/common:hash_lib", @@ -42,7 +43,9 @@ envoy_cc_library( srcs = ["isolated_store_impl.cc"], hdrs = ["isolated_store_impl.h"], deps = [ + ":fake_symbol_table_lib", ":histogram_lib", + ":scope_prefixer_lib", ":stats_lib", ":stats_options_lib", "//include/envoy/stats:stats_macros", @@ -52,8 +55,10 @@ envoy_cc_library( envoy_cc_library( name = "metric_impl_lib", + srcs = ["metric_impl.cc"], hdrs = ["metric_impl.h"], deps = [ + ":symbol_table_lib", "//include/envoy/stats:stats_interface", "//source/common/common:assert_lib", ], @@ -73,6 +78,17 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "scope_prefixer_lib", + srcs = ["scope_prefixer.cc"], + hdrs = ["scope_prefixer.h"], + deps = [ + ":symbol_table_lib", + ":utility_lib", + "//include/envoy/stats:stats_interface", + ], +) + envoy_cc_library( name = "source_impl_lib", srcs = ["source_impl.cc"], @@ -191,6 +207,7 @@ envoy_cc_library( hdrs = ["thread_local_store.h"], deps = [ ":heap_stat_data_lib", + ":scope_prefixer_lib", ":stats_lib", ":stats_matcher_lib", ":tag_producer_lib", @@ -202,4 +219,5 @@ envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], hdrs = ["utility.h"], + deps = [":symbol_table_lib"], ) diff --git a/source/common/stats/heap_stat_data.cc b/source/common/stats/heap_stat_data.cc index 7177ea19087c..9ccca38aafd8 100644 --- a/source/common/stats/heap_stat_data.cc +++ b/source/common/stats/heap_stat_data.cc @@ -1,5 +1,7 @@ #include "common/stats/heap_stat_data.h" +#include + #include "common/common/lock_guard.h" #include "common/common/thread.h" #include "common/common/utility.h" @@ -7,31 +9,36 @@ namespace Envoy { namespace Stats { -HeapStatData::HeapStatData(absl::string_view key) { - StringUtil::strlcpy(name_, key.data(), key.size() + 1); -} +HeapStatDataAllocator::~HeapStatDataAllocator() { ASSERT(stats_.empty()); } -HeapStatDataAllocator::HeapStatDataAllocator() {} +HeapStatData* HeapStatData::alloc(StatName stat_name, SymbolTable& symbol_table) { + void* memory = ::malloc(sizeof(HeapStatData) + stat_name.size()); + ASSERT(memory); + symbol_table.incRefCount(stat_name); + return new (memory) HeapStatData(stat_name); +} -HeapStatDataAllocator::~HeapStatDataAllocator() { ASSERT(stats_.empty()); } +void HeapStatData::free(SymbolTable& symbol_table) { + symbol_table.free(statName()); + this->~HeapStatData(); + ::free(this); // matches malloc() call above. +} -HeapStatData* HeapStatDataAllocator::alloc(absl::string_view name) { - // Any expected truncation of name is done at the callsite. No truncation is - // required to use this allocator. Note that data must be freed by calling - // its free() method, and not by destruction, thus the more complex use of - // unique_ptr. - std::unique_ptr> data( - HeapStatData::alloc(name), [](HeapStatData* d) { d->free(); }); +HeapStatData& HeapStatDataAllocator::alloc(StatName name) { + std::unique_ptr> data_ptr( + HeapStatData::alloc(name, symbolTable()), + [this](HeapStatData* d) { d->free(symbolTable()); }); Thread::ReleasableLockGuard lock(mutex_); - auto ret = stats_.insert(data.get()); + auto ret = stats_.insert(data_ptr.get()); HeapStatData* existing_data = *ret.first; lock.release(); if (ret.second) { - return data.release(); + // symbolTable().incRefCount(existing_data->statName()); + return *data_ptr.release(); } ++existing_data->ref_count_; - return existing_data; + return *existing_data; } void HeapStatDataAllocator::free(HeapStatData& data) { @@ -46,19 +53,18 @@ void HeapStatDataAllocator::free(HeapStatData& data) { ASSERT(key_removed == 1); } - data.free(); -} - -HeapStatData* HeapStatData::alloc(absl::string_view name) { - void* memory = ::malloc(sizeof(HeapStatData) + name.size() + 1); - ASSERT(memory); - return new (memory) HeapStatData(name); + data.free(symbolTable()); } -void HeapStatData::free() { - this->~HeapStatData(); - ::free(this); // matches malloc() call above. +#ifndef ENVOY_CONFIG_COVERAGE +void HeapStatDataAllocator::debugPrint() { + Thread::LockGuard lock(mutex_); + for (HeapStatData* heap_stat_data : stats_) { + std::cout << symbolTable().toString(heap_stat_data->statName()) << std::endl; + } + std::cout << std::flush; } +#endif template class StatDataAllocatorImpl; diff --git a/source/common/stats/heap_stat_data.h b/source/common/stats/heap_stat_data.h index dc14303ec626..b87fe813be3c 100644 --- a/source/common/stats/heap_stat_data.h +++ b/source/common/stats/heap_stat_data.h @@ -4,10 +4,14 @@ #include #include +#include "envoy/stats/stats.h" + #include "common/common/hash.h" #include "common/common/thread.h" #include "common/common/thread_annotations.h" +#include "common/stats/metric_impl.h" #include "common/stats/stat_data_allocator_impl.h" +#include "common/stats/symbol_table_impl.h" #include "absl/container/flat_hash_set.h" @@ -19,71 +23,92 @@ namespace Stats { * so that it can be allocated efficiently from the heap on demand. */ struct HeapStatData { - /** - * @returns absl::string_view the name as a string_view. - */ - absl::string_view key() const { return name_; } +private: + explicit HeapStatData(StatName stat_name) { stat_name.copyToStorage(symbol_storage_); } - /** - * @returns std::string the name as a const char*. - */ - const char* name() const { return name_; } +public: + static HeapStatData* alloc(StatName stat_name, SymbolTable& symbol_table); - static HeapStatData* alloc(absl::string_view name); - void free(); + void free(SymbolTable& symbol_table); + StatName statName() const { return StatName(symbol_storage_); } + + bool operator==(const HeapStatData& rhs) const { return statName() == rhs.statName(); } + uint64_t hash() const { return statName().hash(); } std::atomic value_{0}; std::atomic pending_increment_{0}; std::atomic flags_{0}; std::atomic ref_count_{1}; - char name_[]; + SymbolTable::Storage symbol_storage_; +}; -private: - /** - * You cannot construct/destruct a HeapStatData directly with new/delete as - * it's variable-size. Use alloc()/free() methods above. - */ - explicit HeapStatData(absl::string_view name); - ~HeapStatData() {} +template class HeapStat : public Stat { +public: + HeapStat(HeapStatData& data, StatDataAllocatorImpl& alloc, + absl::string_view tag_extracted_name, const std::vector& tags) + : Stat(data, alloc, tag_extracted_name, tags) {} + + StatName statName() const override { return this->data_.statName(); } }; -/** - * Implementation of StatDataAllocator using a pure heap-based strategy, so that - * Envoy implementations that do not require hot-restart can use less memory. - */ +// Partially implements a StatDataAllocator, leaving alloc & free for subclasses. +// We templatize on StatData rather than defining a virtual base StatData class +// for performance reasons; stat increment is on the hot path. +// +// The two production derivations cover using a fixed block of shared-memory for +// hot restart stat continuity, and heap allocation for more efficient RAM usage +// for when hot-restart is not required. +// +// Also note that RawStatData needs to live in a shared memory block, and it's +// possible, but not obvious, that a vptr would be usable across processes. In +// any case, RawStatData is allocated from a shared-memory block rather than via +// new, so the usual C++ compiler assistance for setting up vptrs will not be +// available. This could be resolved with placed new, or another nesting level. class HeapStatDataAllocator : public StatDataAllocatorImpl { public: - HeapStatDataAllocator(); - ~HeapStatDataAllocator(); + HeapStatDataAllocator(SymbolTable& symbol_table) : StatDataAllocatorImpl(symbol_table) {} + virtual ~HeapStatDataAllocator(); - // StatDataAllocatorImpl - HeapStatData* alloc(absl::string_view name) override; + HeapStatData& alloc(StatName name); void free(HeapStatData& data) override; // StatDataAllocator bool requiresBoundedStatNameSize() const override { return false; } + CounterSharedPtr makeCounter(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags) override { + return std::make_shared>>(alloc(name), *this, + tag_extracted_name, tags); + } + + GaugeSharedPtr makeGauge(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags) override { + return std::make_shared>>(alloc(name), *this, + tag_extracted_name, tags); + } + +#ifndef ENVOY_CONFIG_COVERAGE + void debugPrint(); +#endif + private: struct HeapStatHash { - size_t operator()(const HeapStatData* a) const { return HashUtil::xxHash64(a->key()); } + size_t operator()(const HeapStatData* a) const { return a->hash(); } }; struct HeapStatCompare { - bool operator()(const HeapStatData* a, const HeapStatData* b) const { - return (a->key() == b->key()); - } + bool operator()(const HeapStatData* a, const HeapStatData* b) const { return *a == *b; } }; - // TODO(jmarantz): See https://github.com/envoyproxy/envoy/pull/3927 and - // https://github.com/envoyproxy/envoy/issues/3585, which can help reorganize - // the heap stats using a ref-counted symbol table to compress the stat strings. - using StatSet = absl::flat_hash_set; - // An unordered set of HeapStatData pointers which keys off the key() - // field in each object. This necessitates a custom comparator and hasher. + // field in each object. This necessitates a custom comparator and hasher, which key off of the + // StatNamePtr's own StatNamePtrHash and StatNamePtrCompare operators. + using StatSet = absl::flat_hash_set; StatSet stats_ GUARDED_BY(mutex_); - // A mutex is needed here to protect the stats_ object from both alloc() and free() operations. - // Although alloc() operations are called under existing locking, free() operations are made from - // the destructors of the individual stat objects, which are not protected by locks. + + // A mutex is needed here to protect both the stats_ object from both + // alloc() and free() operations. Although alloc() operations are called under existing locking, + // free() operations are made from the destructors of the individual stat objects, which are not + // protected by locks. Thread::MutexBasicLockable mutex_; }; diff --git a/source/common/stats/histogram_impl.h b/source/common/stats/histogram_impl.h index ef11744ac716..2fafc9a9f181 100644 --- a/source/common/stats/histogram_impl.h +++ b/source/common/stats/histogram_impl.h @@ -52,40 +52,40 @@ class HistogramStatisticsImpl : public HistogramStatistics, NonCopyable { */ class HistogramImpl : public Histogram, public MetricImpl { public: - HistogramImpl(const std::string& name, Store& parent, std::string&& tag_extracted_name, - std::vector&& tags) - : MetricImpl(std::move(tag_extracted_name), std::move(tags)), parent_(parent), name_(name) {} - - // Stats:;Metric - std::string name() const override { return name_; } - const char* nameCStr() const override { return name_.c_str(); } + HistogramImpl(StatName name, Store& parent, const std::string& tag_extracted_name, + const std::vector& tags) + : MetricImpl(tag_extracted_name, tags, parent.symbolTable()), + name_(name, parent.symbolTable()), parent_(parent) {} + ~HistogramImpl() { + name_.free(symbolTable()); + MetricImpl::clear(); + } // Stats::Histogram void recordValue(uint64_t value) override { parent_.deliverHistogramToSinks(*this, value); } bool used() const override { return true; } + StatName statName() const override { return name_.statName(); } + const SymbolTable& symbolTable() const override { return parent_.symbolTable(); } + SymbolTable& symbolTable() override { return parent_.symbolTable(); } private: + StatNameStorage name_; + // This is used for delivering the histogram data to sinks. Store& parent_; - - const std::string name_; }; /** * Null histogram implementation. * No-ops on all calls and requires no underlying metric or data. */ -class NullHistogramImpl : public Histogram { +class NullHistogramImpl : public Histogram, NullMetricImpl { public: - NullHistogramImpl() {} - ~NullHistogramImpl() {} - std::string name() const override { return ""; } - const char* nameCStr() const override { return ""; } - const std::string& tagExtractedName() const override { CONSTRUCT_ON_FIRST_USE(std::string, ""); } - const std::vector& tags() const override { CONSTRUCT_ON_FIRST_USE(std::vector, {}); } + explicit NullHistogramImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} + ~NullHistogramImpl() { MetricImpl::clear(); } + void recordValue(uint64_t) override {} - bool used() const override { return false; } }; } // namespace Stats diff --git a/source/common/stats/isolated_store_impl.cc b/source/common/stats/isolated_store_impl.cc index d85d38ef0321..bbee9bc1f87e 100644 --- a/source/common/stats/isolated_store_impl.cc +++ b/source/common/stats/isolated_store_impl.cc @@ -6,49 +6,43 @@ #include #include "common/common/utility.h" +#include "common/stats/fake_symbol_table_impl.h" #include "common/stats/histogram_impl.h" +#include "common/stats/scope_prefixer.h" #include "common/stats/utility.h" namespace Envoy { namespace Stats { IsolatedStoreImpl::IsolatedStoreImpl() - : counters_([this](const std::string& name) -> CounterSharedPtr { - std::string tag_extracted_name = name; - std::vector tags; - return alloc_.makeCounter(name, std::move(tag_extracted_name), std::move(tags)); + : IsolatedStoreImpl(std::make_unique()) {} + +IsolatedStoreImpl::IsolatedStoreImpl(std::unique_ptr symbol_table) + : IsolatedStoreImpl(*symbol_table) { + symbol_table_storage_ = std::move(symbol_table); +} + +IsolatedStoreImpl::IsolatedStoreImpl(SymbolTable& symbol_table) + : symbol_table_(symbol_table), alloc_(symbol_table_), + counters_([this](StatName name) -> CounterSharedPtr { + return alloc_.makeCounter(name, alloc_.symbolTable().toString(name), std::vector()); }), - gauges_([this](const std::string& name) -> GaugeSharedPtr { - std::string tag_extracted_name = name; - std::vector tags; - return alloc_.makeGauge(name, std::move(tag_extracted_name), std::move(tags)); + gauges_([this](StatName name) -> GaugeSharedPtr { + return alloc_.makeGauge(name, alloc_.symbolTable().toString(name), std::vector()); }), - histograms_([this](const std::string& name) -> HistogramSharedPtr { - return std::make_shared(name, *this, std::string(name), std::vector()); + histograms_([this](StatName name) -> HistogramSharedPtr { + return std::make_shared(name, *this, alloc_.symbolTable().toString(name), + std::vector()); }) {} -struct IsolatedScopeImpl : public Scope { - IsolatedScopeImpl(IsolatedStoreImpl& parent, const std::string& prefix) - : parent_(parent), prefix_(Utility::sanitizeStatsName(prefix)) {} - - // Stats::Scope - ScopePtr createScope(const std::string& name) override { - return ScopePtr{new IsolatedScopeImpl(parent_, prefix_ + name)}; - } - void deliverHistogramToSinks(const Histogram&, uint64_t) 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); } - Histogram& histogram(const std::string& name) override { - return parent_.histogram(prefix_ + name); - } - const Stats::StatsOptions& statsOptions() const override { return parent_.statsOptions(); } - - IsolatedStoreImpl& parent_; - const std::string prefix_; -}; - ScopePtr IsolatedStoreImpl::createScope(const std::string& name) { - return ScopePtr{new IsolatedScopeImpl(*this, name)}; + return std::make_unique(name, *this); +} + +void IsolatedStoreImpl::clear() { + counters_.clear(); + gauges_.clear(); + histograms_.clear(); } } // namespace Stats diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index 7765fd50e3e7..459cd76dcdcf 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -12,8 +12,11 @@ #include "common/common/utility.h" #include "common/stats/heap_stat_data.h" #include "common/stats/stats_options_impl.h" +#include "common/stats/symbol_table_impl.h" #include "common/stats/utility.h" +#include "absl/container/flat_hash_map.h" + namespace Envoy { namespace Stats { @@ -22,18 +25,18 @@ namespace Stats { */ template class IsolatedStatsCache { public: - typedef std::function(const std::string& name)> Allocator; + using Allocator = std::function(StatName name)>; IsolatedStatsCache(Allocator alloc) : alloc_(alloc) {} - Base& get(const std::string& name) { + Base& get(StatName name) { auto stat = stats_.find(name); if (stat != stats_.end()) { return *stat->second; } std::shared_ptr new_stat = alloc_(name); - stats_.emplace(name, new_stat); + stats_.emplace(new_stat->statName(), new_stat); return *new_stat; } @@ -47,25 +50,31 @@ template class IsolatedStatsCache { return vec; } + void clear() { stats_.clear(); } + private: - std::unordered_map> stats_; + StatNameHashMap> stats_; Allocator alloc_; }; class IsolatedStoreImpl : public Store { public: IsolatedStoreImpl(); + explicit IsolatedStoreImpl(std::unique_ptr symbol_table); + explicit IsolatedStoreImpl(SymbolTable& symbol_table); // Stats::Scope - Counter& counter(const std::string& name) override { return counters_.get(name); } + Counter& counterx(StatName name) override { return counters_.get(name); } ScopePtr createScope(const std::string& name) override; void deliverHistogramToSinks(const Histogram&, uint64_t) override {} - Gauge& gauge(const std::string& name) override { return gauges_.get(name); } - Histogram& histogram(const std::string& name) override { + Gauge& gaugex(StatName name) override { return gauges_.get(name); } + Histogram& histogramx(StatName name) override { Histogram& histogram = histograms_.get(name); return histogram; } const Stats::StatsOptions& statsOptions() const override { return stats_options_; } + const SymbolTable& symbolTable() const override { return symbol_table_; } + virtual SymbolTable& symbolTable() override { return symbol_table_; } // Stats::Store std::vector counters() const override { return counters_.toVector(); } @@ -74,7 +83,24 @@ class IsolatedStoreImpl : public Store { return std::vector{}; } + Counter& counter(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return counterx(storage.statName()); + } + Gauge& gauge(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return gaugex(storage.statName()); + } + Histogram& histogram(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return histogramx(storage.statName()); + } + + void clear(); + private: + std::unique_ptr symbol_table_storage_; + SymbolTable& symbol_table_; HeapStatDataAllocator alloc_; IsolatedStatsCache counters_; IsolatedStatsCache gauges_; diff --git a/source/common/stats/metric_impl.cc b/source/common/stats/metric_impl.cc new file mode 100644 index 000000000000..3eed2af53931 --- /dev/null +++ b/source/common/stats/metric_impl.cc @@ -0,0 +1,73 @@ +#include "common/stats/metric_impl.h" + +#include "envoy/stats/tag.h" + +#include "common/stats/symbol_table_impl.h" + +namespace Envoy { +namespace Stats { + +MetricImpl::~MetricImpl() { + // The storage must be cleaned by a subclass of MetricImpl in its + // destructor, because the symbol-table is owned by the subclass. + // Simply call MetricImpl::clear() in the subclass dtor. + ASSERT(!stat_names_.populated()); +} + +MetricImpl::MetricImpl(absl::string_view tag_extracted_name, const std::vector& tags, + SymbolTable& symbol_table) { + // Encode all the names and tags into transient storage so we can count the + // required bytes. + std::vector names; + names.resize(1 /* tag_extracted_name */ + 2 * tags.size()); + names[0] = tag_extracted_name; + int index = 0; + for (auto& tag : tags) { + names[++index] = tag.name_; + names[++index] = tag.value_; + } + stat_names_.populate(names, symbol_table); +} + +void MetricImpl::clear() { stat_names_.clear(symbolTable()); } + +std::string MetricImpl::tagExtractedName() const { + return symbolTable().toString(tagExtractedStatName()); +} + +StatName MetricImpl::tagExtractedStatName() const { + StatName stat_name; + stat_names_.iterate([&stat_name](StatName s) -> bool { + stat_name = s; + return false; + }); + return stat_name; +} + +std::vector MetricImpl::tags() const { + std::vector tags; + enum { TagExtractedName, Name, Value } state = TagExtractedName; + Tag tag; + const SymbolTable& symbol_table = symbolTable(); + stat_names_.iterate([&tags, &state, &tag, &symbol_table](StatName stat_name) -> bool { + switch (state) { + case TagExtractedName: + state = Name; + break; + case Name: + tag.name_ = symbol_table.toString(stat_name); + state = Value; + break; + case Value: + tag.value_ = symbol_table.toString(stat_name); + tags.emplace_back(tag); + state = Name; + } + return true; + }); + ASSERT(state != Value); + return tags; +} + +} // namespace Stats +} // namespace Envoy diff --git a/source/common/stats/metric_impl.h b/source/common/stats/metric_impl.h index 3e65e0d79d86..57fb28b5a082 100644 --- a/source/common/stats/metric_impl.h +++ b/source/common/stats/metric_impl.h @@ -3,10 +3,12 @@ #include #include +#include "envoy/stats/stat_data_allocator.h" #include "envoy/stats/stats.h" #include "envoy/stats/tag.h" #include "common/common/assert.h" +#include "common/stats/symbol_table_impl.h" namespace Envoy { namespace Stats { @@ -20,11 +22,14 @@ namespace Stats { */ class MetricImpl : public virtual Metric { public: - MetricImpl(std::string&& tag_extracted_name, std::vector&& tags) - : tag_extracted_name_(std::move(tag_extracted_name)), tags_(std::move(tags)) {} + MetricImpl(absl::string_view tag_extracted_name, const std::vector& tags, + SymbolTable& symbol_table); + ~MetricImpl(); - const std::string& tagExtractedName() const override { return tag_extracted_name_; } - const std::vector& tags() const override { return tags_; } + std::string name() const override { return symbolTable().toString(statName()); } + std::string tagExtractedName() const override; + std::vector tags() const override; + StatName tagExtractedStatName() const override; protected: /** @@ -34,9 +39,27 @@ class MetricImpl : public virtual Metric { static const uint8_t Used = 0x1; }; + void clear(); + +private: + StatNameList stat_names_; +}; + +class NullMetricImpl : public MetricImpl { +public: + explicit NullMetricImpl(SymbolTable& symbol_table) + : MetricImpl("", std::vector(), symbol_table), symbol_table_(symbol_table), + stat_name_storage_("", symbol_table) {} + ~NullMetricImpl() { stat_name_storage_.free(symbol_table_); } + + const SymbolTable& symbolTable() const override { return symbol_table_; } + SymbolTable& symbolTable() override { return symbol_table_; } + bool used() const override { return false; } + StatName statName() const override { return stat_name_storage_.statName(); } + private: - const std::string tag_extracted_name_; - const std::vector tags_; + SymbolTable& symbol_table_; + StatNameStorage stat_name_storage_; }; } // namespace Stats diff --git a/source/common/stats/raw_stat_data.cc b/source/common/stats/raw_stat_data.cc index 7ef23654bb7b..cf2d141fcb68 100644 --- a/source/common/stats/raw_stat_data.cc +++ b/source/common/stats/raw_stat_data.cc @@ -24,6 +24,8 @@ uint64_t roundUpMultipleNaturalAlignment(uint64_t val) { } // namespace +RawStatDataAllocator::~RawStatDataAllocator() {} + // Normally the compiler would do this, but because name_ is a flexible-array-length // element, the compiler can't. RawStatData is put into an array in HotRestartImpl, so // it's important that each element starts on the required alignment for the type. @@ -37,7 +39,13 @@ uint64_t RawStatData::structSizeWithOptions(const StatsOptions& stats_options) { void RawStatData::initialize(absl::string_view key, const StatsOptions& stats_options) { ASSERT(!initialized()); - ASSERT(key.size() <= stats_options.maxNameLength()); + if (key.size() > stats_options.maxNameLength()) { + ENVOY_LOG_MISC( + warn, + "Statistic '{}' is too long with {} characters, it will be truncated to {} characters", key, + key.size(), stats_options.maxNameLength()); + key = key.substr(0, stats_options.maxNameLength()); + } ref_count_ = 1; memcpy(name_, key.data(), key.size()); name_[key.size()] = '\0'; diff --git a/source/common/stats/raw_stat_data.h b/source/common/stats/raw_stat_data.h index 882a35549a97..3c4461f0f417 100644 --- a/source/common/stats/raw_stat_data.h +++ b/source/common/stats/raw_stat_data.h @@ -94,16 +94,56 @@ struct RawStatData { using RawStatDataSet = BlockMemoryHashSet; +template class RawStat : public Stat { +public: + RawStat(StatName stat_name, RawStatData& data, StatDataAllocatorImpl& alloc, + absl::string_view tag_extracted_name, const std::vector& tags) + : Stat(data, alloc, tag_extracted_name, tags), + stat_name_storage_(stat_name, alloc.symbolTable()) {} + ~RawStat() { stat_name_storage_.free(this->symbolTable()); } + + StatName statName() const override { return stat_name_storage_.statName(); } + +private: + StatNameStorage stat_name_storage_; +}; + class RawStatDataAllocator : public StatDataAllocatorImpl { public: RawStatDataAllocator(Thread::BasicLockable& mutex, RawStatDataSet& stats_set, - const StatsOptions& options) - : mutex_(mutex), stats_set_(stats_set), options_(options) {} + const StatsOptions& options, SymbolTable& symbol_table) + : StatDataAllocatorImpl(symbol_table), mutex_(mutex), stats_set_(stats_set), + options_(options) {} + ~RawStatDataAllocator(); + + virtual RawStatData* alloc(absl::string_view name); // Virtual only for mocking. + void free(Stats::RawStatData& data) override; + RawStatData* allocStatName(StatName stat_name) { + return alloc(symbolTable().toString(stat_name)); + } // StatDataAllocator bool requiresBoundedStatNameSize() const override { return true; } - Stats::RawStatData* alloc(absl::string_view name) override; - void free(Stats::RawStatData& data) override; + + template + std::shared_ptr makeStat(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags) { + RawStatData* raw_stat_data = allocStatName(name); + if (raw_stat_data == nullptr) { + return nullptr; + } + return std::make_shared>(name, *raw_stat_data, *this, tag_extracted_name, tags); + } + + CounterSharedPtr makeCounter(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags) override { + return makeStat>(name, tag_extracted_name, tags); + } + + GaugeSharedPtr makeGauge(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags) override { + return makeStat>(name, tag_extracted_name, tags); + } private: Thread::BasicLockable& mutex_; diff --git a/source/common/stats/scope_prefixer.cc b/source/common/stats/scope_prefixer.cc new file mode 100644 index 000000000000..ddb82340cee9 --- /dev/null +++ b/source/common/stats/scope_prefixer.cc @@ -0,0 +1,60 @@ +#include "common/stats/scope_prefixer.h" + +#include "envoy/stats/scope.h" + +#include "common/stats/symbol_table_impl.h" +#include "common/stats/utility.h" + +namespace Envoy { +namespace Stats { + +// Variant of ScopePrefixer that owns the scope being prefixed, and will +// delete it upon destruction. +ScopePrefixer::ScopePrefixer(absl::string_view prefix, Scope* scope) + : prefix_(Utility::sanitizeStatsName(prefix), scope->symbolTable()), scope_(scope), + owns_scope_(true) {} + +// Variant of ScopePrefixer that references the scope being prefixed, but does +// not own it. +ScopePrefixer::ScopePrefixer(absl::string_view prefix, Scope& scope) + : prefix_(Utility::sanitizeStatsName(prefix), scope.symbolTable()), scope_(&scope), + owns_scope_(false) {} + +ScopePrefixer::~ScopePrefixer() { + prefix_.free(scope_->symbolTable()); + if (owns_scope_) { + delete scope_; + } +} + +ScopePtr ScopePrefixer::createScope(const std::string& name) { + // StatNameStorage scope_stat_name(name, symbolTable()); // Takes a lock. + // StatNameStorage joiner(prefix_.statName(), scope_stat_name.statName()); + return std::make_unique(symbolTable().toString(prefix_.statName()) + "." + name, + *scope_); +} + +Counter& ScopePrefixer::counterx(StatName name) { + Stats::SymbolTable::StoragePtr stat_name_storage = + scope_->symbolTable().join({prefix_.statName(), name}); + return scope_->counterx(StatName(stat_name_storage.get())); +} + +Gauge& ScopePrefixer::gaugex(StatName name) { + Stats::SymbolTable::StoragePtr stat_name_storage = + scope_->symbolTable().join({prefix_.statName(), name}); + return scope_->gaugex(StatName(stat_name_storage.get())); +} + +Histogram& ScopePrefixer::histogramx(StatName name) { + Stats::SymbolTable::StoragePtr stat_name_storage = + scope_->symbolTable().join({prefix_.statName(), name}); + return scope_->histogramx(StatName(stat_name_storage.get())); +} + +void ScopePrefixer::deliverHistogramToSinks(const Histogram& histograms, uint64_t val) { + scope_->deliverHistogramToSinks(histograms, val); +} + +} // namespace Stats +} // namespace Envoy diff --git a/source/common/stats/scope_prefixer.h b/source/common/stats/scope_prefixer.h new file mode 100644 index 000000000000..c8abcc548176 --- /dev/null +++ b/source/common/stats/scope_prefixer.h @@ -0,0 +1,53 @@ +#include "envoy/stats/scope.h" + +#include "common/stats/symbol_table_impl.h" + +namespace Envoy { +namespace Stats { + +// Implements a Scope that delegates to a passed-in scope, prefixing all names +// prior to creation. +class ScopePrefixer : public Scope { +public: + // Variant of ScopePrefixer that owns the scope being prefixed, and will + // delete it upon destruction. + ScopePrefixer(absl::string_view prefix, Scope* scope); + + // Variant of ScopePrefixer that references the scope being prefixed, but does + // not own it. + ScopePrefixer(absl::string_view prefix, Scope& scope); + + virtual ~ScopePrefixer(); + + // Scope + ScopePtr createScope(const std::string& name) override; + Counter& counterx(StatName name) override; + Gauge& gaugex(StatName name) override; + Histogram& histogramx(StatName name) override; + void deliverHistogramToSinks(const Histogram& histograms, uint64_t val) override; + + Counter& counter(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return counterx(storage.statName()); + } + Gauge& gauge(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return gaugex(storage.statName()); + } + Histogram& histogram(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return histogramx(storage.statName()); + } + + const Stats::StatsOptions& statsOptions() const override { return scope_->statsOptions(); } + const SymbolTable& symbolTable() const override { return scope_->symbolTable(); } + virtual SymbolTable& symbolTable() override { return scope_->symbolTable(); } + +private: + StatNameStorage prefix_; + Scope* scope_; + const bool owns_scope_; +}; + +} // namespace Stats +} // namespace Envoy diff --git a/source/common/stats/stat_data_allocator_impl.h b/source/common/stats/stat_data_allocator_impl.h index 81df5f426957..e1354263f518 100644 --- a/source/common/stats/stat_data_allocator_impl.h +++ b/source/common/stats/stat_data_allocator_impl.h @@ -29,11 +29,7 @@ namespace Stats { // available. This could be resolved with placed new, or another nesting level. template class StatDataAllocatorImpl : public StatDataAllocator { public: - // StatDataAllocator - CounterSharedPtr makeCounter(absl::string_view name, std::string&& tag_extracted_name, - std::vector&& tags) override; - GaugeSharedPtr makeGauge(absl::string_view name, std::string&& tag_extracted_name, - std::vector&& tags) override; + explicit StatDataAllocatorImpl(SymbolTable& symbol_table) : symbol_table_(symbol_table) {} /** * @param name the full name of the stat. @@ -42,7 +38,7 @@ template class StatDataAllocatorImpl : public StatDataAllocator * by name if one already exists with the same name. This is used for intra-process * scope swapping as well as inter-process hot restart. */ - virtual StatData* alloc(absl::string_view name) PURE; + // virtual StatData* alloc(StatName name) PURE; /** * Free a raw stat data block. The allocator should handle reference counting and only truly @@ -50,6 +46,15 @@ template class StatDataAllocatorImpl : public StatDataAllocator * @param data the data returned by alloc(). */ virtual void free(StatData& data) PURE; + + SymbolTable& symbolTable() override { return symbol_table_; } + const SymbolTable& symbolTable() const override { return symbol_table_; } + +private: + // SymbolTable encodes encodes stat names as back into strings. This does not + // get guarded by a mutex, since it has its own internal mutex to guarantee + // thread safety. + SymbolTable& symbol_table_; }; /** @@ -62,13 +67,12 @@ template class StatDataAllocatorImpl : public StatDataAllocator template class CounterImpl : public Counter, public MetricImpl { public: CounterImpl(StatData& data, StatDataAllocatorImpl& alloc, - std::string&& tag_extracted_name, std::vector&& tags) - : MetricImpl(std::move(tag_extracted_name), std::move(tags)), data_(data), alloc_(alloc) {} - ~CounterImpl() { alloc_.free(data_); } - - // Stats::Metric - std::string name() const override { return std::string(data_.name()); } - const char* nameCStr() const override { return data_.name(); } + absl::string_view tag_extracted_name, const std::vector& tags) + : MetricImpl(tag_extracted_name, tags, alloc.symbolTable()), data_(data), alloc_(alloc) {} + ~CounterImpl() { + alloc_.free(data_); + MetricImpl::clear(); + } // Stats::Counter void add(uint64_t amount) override { @@ -83,7 +87,10 @@ template class CounterImpl : public Counter, public MetricImpl bool used() const override { return data_.flags_ & Flags::Used; } uint64_t value() const override { return data_.value_; } -private: + const SymbolTable& symbolTable() const override { return alloc_.symbolTable(); } + SymbolTable& symbolTable() override { return alloc_.symbolTable(); } + +protected: StatData& data_; StatDataAllocatorImpl& alloc_; }; @@ -92,19 +99,15 @@ template class CounterImpl : public Counter, public MetricImpl * Null counter implementation. * No-ops on all calls and requires no underlying metric or data. */ -class NullCounterImpl : public Counter { +class NullCounterImpl : public Counter, NullMetricImpl { public: - NullCounterImpl() {} - ~NullCounterImpl() {} - std::string name() const override { return ""; } - const char* nameCStr() const override { return ""; } - const std::string& tagExtractedName() const override { CONSTRUCT_ON_FIRST_USE(std::string, ""); } - const std::vector& tags() const override { CONSTRUCT_ON_FIRST_USE(std::vector, {}); } + explicit NullCounterImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} + ~NullCounterImpl() { MetricImpl::clear(); } + void add(uint64_t) override {} void inc() override {} uint64_t latch() override { return 0; } void reset() override {} - bool used() const override { return false; } uint64_t value() const override { return 0; } }; @@ -114,13 +117,12 @@ class NullCounterImpl : public Counter { template class GaugeImpl : public Gauge, public MetricImpl { public: GaugeImpl(StatData& data, StatDataAllocatorImpl& alloc, - std::string&& tag_extracted_name, std::vector&& tags) - : MetricImpl(std::move(tag_extracted_name), std::move(tags)), data_(data), alloc_(alloc) {} - ~GaugeImpl() { alloc_.free(data_); } - - // Stats::Metric - std::string name() const override { return std::string(data_.name()); } - const char* nameCStr() const override { return data_.name(); } + absl::string_view tag_extracted_name, const std::vector& tags) + : MetricImpl(tag_extracted_name, tags, alloc.symbolTable()), data_(data), alloc_(alloc) {} + ~GaugeImpl() { + alloc_.free(data_); + MetricImpl::clear(); + } // Stats::Gauge virtual void add(uint64_t amount) override { @@ -141,7 +143,10 @@ template class GaugeImpl : public Gauge, public MetricImpl { virtual uint64_t value() const override { return data_.value_; } bool used() const override { return data_.flags_ & Flags::Used; } -private: + const SymbolTable& symbolTable() const override { return alloc_.symbolTable(); } + SymbolTable& symbolTable() override { return alloc_.symbolTable(); } + +protected: StatData& data_; StatDataAllocatorImpl& alloc_; }; @@ -150,46 +155,18 @@ template class GaugeImpl : public Gauge, public MetricImpl { * Null gauge implementation. * No-ops on all calls and requires no underlying metric or data. */ -class NullGaugeImpl : public Gauge { +class NullGaugeImpl : public Gauge, NullMetricImpl { public: - NullGaugeImpl() {} - ~NullGaugeImpl() {} - std::string name() const override { return ""; } - const char* nameCStr() const override { return ""; } - const std::string& tagExtractedName() const override { CONSTRUCT_ON_FIRST_USE(std::string, ""); } - const std::vector& tags() const override { CONSTRUCT_ON_FIRST_USE(std::vector, {}); } + explicit NullGaugeImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} + ~NullGaugeImpl() { MetricImpl::clear(); } + void add(uint64_t) override {} void inc() override {} void dec() override {} void set(uint64_t) override {} void sub(uint64_t) override {} - bool used() const override { return false; } uint64_t value() const override { return 0; } }; -template -CounterSharedPtr StatDataAllocatorImpl::makeCounter(absl::string_view name, - std::string&& tag_extracted_name, - std::vector&& tags) { - StatData* data = alloc(name); - if (data == nullptr) { - return nullptr; - } - return std::make_shared>(*data, *this, std::move(tag_extracted_name), - std::move(tags)); -} - -template -GaugeSharedPtr StatDataAllocatorImpl::makeGauge(absl::string_view name, - std::string&& tag_extracted_name, - std::vector&& tags) { - StatData* data = alloc(name); - if (data == nullptr) { - return nullptr; - } - return std::make_shared>(*data, *this, std::move(tag_extracted_name), - std::move(tags)); -} - } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index f7c33ebd1d93..c65c41c033df 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -157,7 +157,7 @@ class SymbolTableImpl : public SymbolTable { uint32_t ref_count_; }; - // This must be called during both encode() and free(). + // This must be held during both encode() and free(). mutable Thread::MutexBasicLockable lock_; /** diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 2c72dbf94b98..41fdf0d93fc1 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -13,6 +13,7 @@ #include "envoy/stats/stats_options.h" #include "common/common/lock_guard.h" +#include "common/stats/scope_prefixer.h" #include "common/stats/stats_matcher_impl.h" #include "common/stats/tag_producer_impl.h" @@ -26,16 +27,23 @@ ThreadLocalStoreImpl::ThreadLocalStoreImpl(const StatsOptions& stats_options, : stats_options_(stats_options), alloc_(alloc), default_scope_(createScope("")), tag_producer_(std::make_unique()), stats_matcher_(std::make_unique()), - num_last_resort_stats_(default_scope_->counter("stats.overflow")), source_(*this) {} + stats_overflow_("stats.overflow", alloc.symbolTable()), + num_last_resort_stats_(default_scope_->counterx(stats_overflow_.statName())), + heap_allocator_(alloc.symbolTable()), source_(*this), null_counter_(alloc.symbolTable()), + null_gauge_(alloc.symbolTable()), null_histogram_(alloc.symbolTable()) {} ThreadLocalStoreImpl::~ThreadLocalStoreImpl() { ASSERT(shutting_down_); default_scope_.reset(); ASSERT(scopes_.empty()); + stats_overflow_.free(symbolTable()); } void ThreadLocalStoreImpl::setStatsMatcher(StatsMatcherPtr&& stats_matcher) { stats_matcher_ = std::move(stats_matcher); + if (stats_matcher_->acceptsAll()) { + return; + } // The Filesystem and potentially other stat-registering objects are // constructed prior to the stat-matcher, and those add stats @@ -51,13 +59,13 @@ void ThreadLocalStoreImpl::setStatsMatcher(StatsMatcherPtr&& stats_matcher) { template void ThreadLocalStoreImpl::removeRejectedStats(StatMapClass& map, StatListClass& list) { - std::vector remove_list; + std::vector remove_list; for (auto& stat : map) { if (rejects(stat.first)) { remove_list.push_back(stat.first); } } - for (const char* stat_name : remove_list) { + for (StatName stat_name : remove_list) { auto p = map.find(stat_name); ASSERT(p != map.end()); list.push_back(p->second); // Save SharedPtr to the list to avoid invalidating refs to stat. @@ -65,17 +73,27 @@ void ThreadLocalStoreImpl::removeRejectedStats(StatMapClass& map, StatListClass& } } -bool ThreadLocalStoreImpl::rejects(const std::string& name) const { +bool ThreadLocalStoreImpl::rejects(StatName stat_name) const { + // Don't both elaborating the StatName there are no pattern-based + // exclusions;/inclusions. + if (stats_matcher_->acceptsAll()) { + return false; + } + // TODO(ambuc): If stats_matcher_ depends on regexes, this operation (on the // hot path) could become prohibitively expensive. Revisit this usage in the // future. - return stats_matcher_->rejects(name); + // + // Also note that the elaboration of the stat-name into a string is expensive, + // so I think it might be better to move the matcher test until after caching, + // unless its acceptsAll/rejectsAll. + return stats_matcher_->rejectsAll() || stats_matcher_->rejects(symbolTable().toString(stat_name)); } std::vector ThreadLocalStoreImpl::counters() const { // Handle de-dup due to overlapping scopes. std::vector ret; - CharStarHashSet names; + StatNameHashSet names; Thread::LockGuard lock(lock_); for (ScopeImpl* scope : scopes_) { for (auto& counter : scope->central_cache_.counters_) { @@ -89,7 +107,7 @@ std::vector ThreadLocalStoreImpl::counters() const { } ScopePtr ThreadLocalStoreImpl::createScope(const std::string& name) { - std::unique_ptr new_scope(new ScopeImpl(*this, name)); + auto new_scope = std::make_unique(*this, name); Thread::LockGuard lock(lock_); scopes_.emplace(new_scope.get()); return std::move(new_scope); @@ -98,7 +116,7 @@ ScopePtr ThreadLocalStoreImpl::createScope(const std::string& name) { std::vector ThreadLocalStoreImpl::gauges() const { // Handle de-dup due to overlapping scopes. std::vector ret; - CharStarHashSet names; + StatNameHashSet names; Thread::LockGuard lock(lock_); for (ScopeImpl* scope : scopes_) { for (auto& gauge : scope->central_cache_.gauges_) { @@ -187,10 +205,12 @@ void ThreadLocalStoreImpl::releaseScopeCrossThread(ScopeImpl* scope) { } } +/* std::string ThreadLocalStoreImpl::getTagsForName(const std::string& name, std::vector& tags) const { return tag_producer_->produceTags(name, tags); } +*/ void ThreadLocalStoreImpl::clearScopeFromCaches(uint64_t scope_id) { // If we are shutting down we no longer perform cache flushes as workers may be shutting down @@ -208,31 +228,71 @@ absl::string_view ThreadLocalStoreImpl::truncateStatNameIfNeeded(absl::string_vi // allocation. if (alloc_.requiresBoundedStatNameSize()) { const uint64_t max_length = stats_options_.maxNameLength(); - name = name.substr(0, max_length); + if (name.size() > max_length) { + ENVOY_LOG_MISC( + warn, + "Statistic '{}' is too long with {} characters, it will be truncated to {} characters", + name, name.size(), max_length); + name = name.substr(0, max_length); + } } return name; } std::atomic ThreadLocalStoreImpl::ScopeImpl::next_scope_id_; -ThreadLocalStoreImpl::ScopeImpl::~ScopeImpl() { parent_.releaseScopeCrossThread(this); } +ThreadLocalStoreImpl::ScopeImpl::ScopeImpl(ThreadLocalStoreImpl& parent, const std::string& prefix) + : scope_id_(next_scope_id_++), parent_(parent), + prefix_(Utility::sanitizeStatsName(prefix), parent.symbolTable()) {} + +ThreadLocalStoreImpl::ScopeImpl::~ScopeImpl() { + parent_.releaseScopeCrossThread(this); + prefix_.free(symbolTable()); +} + +/* +void ThreadLocalStoreImpl::ScopeImpl::extractTagsAndTruncate( + StatName& name, std::unique_ptr& truncated_name_storage, + std::vector& tags, + std::string& tag_extracted_name) { + + // Tag extraction occurs on the original, untruncated name so the extraction + // can complete properly, even if the tag values are partially truncated. + std::string name_str = name.toString(parent_.symbolTable()); + tag_extracted_name = parent_.getTagsForName(name_str, tags); + absl::string_view truncated_name = parent_.truncateStatNameIfNeeded(name_str); + if (truncated_name.size() < name_str.size()) { + truncated_name_storage = std::make_unique(truncated_name, symbolTable()); + name = truncated_name_storage->statName(); + } + }*/ + +// Manages the truncation and tag-extration of stat names. Tag extraction occurs +// on the original, untruncated name so the extraction can complete properly, +// even if the tag values are partially truncated. +class TagExtraction { +public: + TagExtraction(ThreadLocalStoreImpl& tls, StatName name) { + std::string name_str = tls.symbolTable().toString(name); + tag_extracted_name_ = tls.tagProducer().produceTags(name_str, tags_); + } + + const std::vector& tags() { return tags_; } + const std::string& tagExtractedName() { return tag_extracted_name_; } + +private: + std::vector tags_; + std::string tag_extracted_name_; +}; template StatType& ThreadLocalStoreImpl::ScopeImpl::safeMakeStat( - const std::string& name, StatMap>& central_cache_map, + StatName name, StatMap>& central_cache_map, MakeStatFn make_stat, StatMap>* tls_cache) { - const char* stat_key = name.c_str(); - std::unique_ptr truncation_buffer; - absl::string_view truncated_name = parent_.truncateStatNameIfNeeded(name); - if (truncated_name.size() < name.size()) { - truncation_buffer = std::make_unique(std::string(truncated_name)); - stat_key = truncation_buffer->c_str(); // must be nul-terminated. - } - // If we have a valid cache entry, return it. if (tls_cache) { - auto pos = tls_cache->find(stat_key); + auto pos = tls_cache->find(name); if (pos != tls_cache->end()) { return *pos->second; } @@ -241,50 +301,36 @@ StatType& ThreadLocalStoreImpl::ScopeImpl::safeMakeStat( // We must now look in the central store so we must be locked. We grab a reference to the // central store location. It might contain nothing. In this case, we allocate a new stat. Thread::LockGuard lock(parent_.lock_); - auto p = central_cache_map.find(stat_key); + auto p = central_cache_map.find(name); std::shared_ptr* central_ref = nullptr; if (p != central_cache_map.end()) { central_ref = &(p->second); } else { - // If we had to truncate, warn now that we've missed all caches. - if (truncation_buffer != nullptr) { - ENVOY_LOG_MISC( - warn, - "Statistic '{}' is too long with {} characters, it will be truncated to {} characters", - name, name.size(), truncation_buffer->size()); - } - - std::vector tags; - - // Tag extraction occurs on the original, untruncated name so the extraction - // can complete properly, even if the tag values are partially truncated. - std::string tag_extracted_name = parent_.getTagsForName(name, tags); + TagExtraction extraction(parent_, name); + // std::shared_ptr stat = make_stat(parent_.alloc_, extraction.truncatedStatName(), + // extraction.tagExtractedName(), extraction.tags()); std::shared_ptr stat = - make_stat(parent_.alloc_, truncated_name, std::move(tag_extracted_name), std::move(tags)); + make_stat(parent_.alloc_, name, extraction.tagExtractedName(), extraction.tags()); if (stat == nullptr) { - // TODO(jmarantz): If make_stat fails, the actual move does not actually occur - // for tag_extracted_name and tags, so there is no use-after-move problem. - // In order to increase the readability of the code, refactoring is done here. parent_.num_last_resort_stats_.inc(); - stat = make_stat(parent_.heap_allocator_, truncated_name, - std::move(tag_extracted_name), // NOLINT(bugprone-use-after-move) - std::move(tags)); // NOLINT(bugprone-use-after-move) + stat = make_stat(parent_.heap_allocator_, name, extraction.tagExtractedName(), + extraction.tags()); ASSERT(stat != nullptr); } - central_ref = ¢ral_cache_map[stat->nameCStr()]; + central_ref = ¢ral_cache_map[stat->statName()]; *central_ref = stat; } // If we have a TLS cache, insert the stat. if (tls_cache) { - tls_cache->insert(std::make_pair((*central_ref)->nameCStr(), *central_ref)); + tls_cache->insert(std::make_pair((*central_ref)->statName(), *central_ref)); } // Finally we return the reference. return **central_ref; } -Counter& ThreadLocalStoreImpl::ScopeImpl::counter(const std::string& name) { +Counter& ThreadLocalStoreImpl::ScopeImpl::counterx(StatName name) { // Determine the final name based on the prefix and the passed name. // // Note that we can do map.find(final_name.c_str()), but we cannot do @@ -294,9 +340,11 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counter(const std::string& name) { // after we construct the stat we can insert it into the required maps. This // strategy costs an extra hash lookup for each miss, but saves time // re-copying the string and significant memory overhead. - std::string final_name = prefix_ + name; - if (parent_.rejects(final_name)) { - return null_counter_; + Stats::SymbolTable::StoragePtr final_name = symbolTable().join({prefix_.statName(), name}); + StatName final_stat_name(final_name.get()); + + if (parent_.rejects(final_stat_name)) { + return parent_.null_counter_; } // We now find the TLS cache. This might remain null if we don't have TLS @@ -306,13 +354,13 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counter(const std::string& name) { tls_cache = &parent_.tls_->getTyped().scope_cache_[this->scope_id_].counters_; } - return safeMakeStat( - final_name, central_cache_.counters_, - [](StatDataAllocator& allocator, absl::string_view name, std::string&& tag_extracted_name, - std::vector&& tags) -> CounterSharedPtr { - return allocator.makeCounter(name, std::move(tag_extracted_name), std::move(tags)); - }, - tls_cache); + return safeMakeStat(final_stat_name, central_cache_.counters_, + [](StatDataAllocator& allocator, StatName name, + absl::string_view tag_extracted_name, + const std::vector& tags) -> CounterSharedPtr { + return allocator.makeCounter(name, tag_extracted_name, tags); + }, + tls_cache); } void ThreadLocalStoreImpl::ScopeImpl::deliverHistogramToSinks(const Histogram& histogram, @@ -331,8 +379,8 @@ void ThreadLocalStoreImpl::ScopeImpl::deliverHistogramToSinks(const Histogram& h } } -Gauge& ThreadLocalStoreImpl::ScopeImpl::gauge(const std::string& name) { - // See comments in counter(). There is no super clean way (via templates or otherwise) to +Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugex(StatName name) { + // See comments in counterx(). There is no super clean way (via templates or otherwise) to // share this code so I'm leaving it largely duplicated for now. // // Note that we can do map.find(final_name.c_str()), but we cannot do @@ -340,9 +388,11 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gauge(const std::string& name) { // a temporary, and address sanitization errors would follow. Instead we must // do a find() first, using that if it succeeds. If it fails, then after we // construct the stat we can insert it into the required maps. - std::string final_name = prefix_ + name; - if (parent_.rejects(final_name)) { - return null_gauge_; + Stats::SymbolTable::StoragePtr final_name = symbolTable().join({prefix_.statName(), name}); + StatName final_stat_name(final_name.get()); + + if (parent_.rejects(final_stat_name)) { + return parent_.null_gauge_; } StatMap* tls_cache = nullptr; @@ -350,17 +400,17 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gauge(const std::string& name) { tls_cache = &parent_.tls_->getTyped().scope_cache_[this->scope_id_].gauges_; } - return safeMakeStat( - final_name, central_cache_.gauges_, - [](StatDataAllocator& allocator, absl::string_view name, std::string&& tag_extracted_name, - std::vector&& tags) -> GaugeSharedPtr { - return allocator.makeGauge(name, std::move(tag_extracted_name), std::move(tags)); - }, - tls_cache); + return safeMakeStat(final_stat_name, central_cache_.gauges_, + [](StatDataAllocator& allocator, StatName name, + absl::string_view tag_extracted_name, + const std::vector& tags) -> GaugeSharedPtr { + return allocator.makeGauge(name, tag_extracted_name, tags); + }, + tls_cache); } -Histogram& ThreadLocalStoreImpl::ScopeImpl::histogram(const std::string& name) { - // See comments in counter(). There is no super clean way (via templates or otherwise) to +Histogram& ThreadLocalStoreImpl::ScopeImpl::histogramx(StatName name) { + // See comments in counterx(). There is no super clean way (via templates or otherwise) to // share this code so I'm leaving it largely duplicated for now. // // Note that we can do map.find(final_name.c_str()), but we cannot do @@ -368,81 +418,87 @@ Histogram& ThreadLocalStoreImpl::ScopeImpl::histogram(const std::string& name) { // a temporary, and address sanitization errors would follow. Instead we must // do a find() first, using that if it succeeds. If it fails, then after we // construct the stat we can insert it into the required maps. - std::string final_name = prefix_ + name; - if (parent_.rejects(final_name)) { - return null_histogram_; + Stats::SymbolTable::StoragePtr final_name = symbolTable().join({prefix_.statName(), name}); + StatName final_stat_name(final_name.get()); + + if (parent_.rejects(final_stat_name)) { + return parent_.null_histogram_; } StatMap* tls_cache = nullptr; if (!parent_.shutting_down_ && parent_.tls_) { tls_cache = &parent_.tls_->getTyped().scope_cache_[this->scope_id_].parent_histograms_; - auto p = tls_cache->find(final_name.c_str()); + auto p = tls_cache->find(final_stat_name); if (p != tls_cache->end()) { return *p->second; } } Thread::LockGuard lock(parent_.lock_); - auto p = central_cache_.histograms_.find(final_name.c_str()); + auto p = central_cache_.histograms_.find(final_stat_name); ParentHistogramImplSharedPtr* central_ref = nullptr; if (p != central_cache_.histograms_.end()) { central_ref = &p->second; } else { - std::vector tags; - std::string tag_extracted_name = parent_.getTagsForName(final_name, tags); + TagExtraction extraction(parent_, final_stat_name); auto stat = std::make_shared( - final_name, parent_, *this, std::move(tag_extracted_name), std::move(tags)); - central_ref = ¢ral_cache_.histograms_[stat->nameCStr()]; + final_stat_name, parent_, *this, extraction.tagExtractedName(), extraction.tags()); + central_ref = ¢ral_cache_.histograms_[stat->statName()]; *central_ref = stat; } if (tls_cache != nullptr) { - tls_cache->insert(std::make_pair((*central_ref)->nameCStr(), *central_ref)); + tls_cache->insert(std::make_pair((*central_ref)->statName(), *central_ref)); } return **central_ref; } -Histogram& ThreadLocalStoreImpl::ScopeImpl::tlsHistogram(const std::string& name, +Histogram& ThreadLocalStoreImpl::ScopeImpl::tlsHistogram(StatName name, ParentHistogramImpl& parent) { if (parent_.rejects(name)) { - return null_histogram_; + return parent_.null_histogram_; } - // See comments in counter() which explains the logic here. + // See comments in counterx() which explains the logic here. StatMap* tls_cache = nullptr; if (!parent_.shutting_down_ && parent_.tls_) { tls_cache = &parent_.tls_->getTyped().scope_cache_[this->scope_id_].histograms_; - auto p = tls_cache->find(name.c_str()); + auto p = tls_cache->find(name); if (p != tls_cache->end()) { return *p->second; } } std::vector tags; - std::string tag_extracted_name = parent_.getTagsForName(name, tags); - TlsHistogramSharedPtr hist_tls_ptr = std::make_shared( - name, std::move(tag_extracted_name), std::move(tags)); + std::string tag_extracted_name = + parent_.tagProducer().produceTags(symbolTable().toString(name), tags); + TlsHistogramSharedPtr hist_tls_ptr = + std::make_shared(name, tag_extracted_name, tags, symbolTable()); parent.addTlsHistogram(hist_tls_ptr); if (tls_cache) { - tls_cache->insert(std::make_pair(hist_tls_ptr->nameCStr(), hist_tls_ptr)); + tls_cache->insert(std::make_pair(hist_tls_ptr->statName(), hist_tls_ptr)); } return *hist_tls_ptr; } -ThreadLocalHistogramImpl::ThreadLocalHistogramImpl(const std::string& name, - std::string&& tag_extracted_name, - std::vector&& tags) - : MetricImpl(std::move(tag_extracted_name), std::move(tags)), current_active_(0), flags_(0), - created_thread_id_(std::this_thread::get_id()), name_(name) { +ThreadLocalHistogramImpl::ThreadLocalHistogramImpl(StatName name, + absl::string_view tag_extracted_name, + const std::vector& tags, + SymbolTable& symbol_table) + : MetricImpl(tag_extracted_name, tags, symbol_table), current_active_(0), flags_(0), + created_thread_id_(std::this_thread::get_id()), name_(name, symbol_table), + symbol_table_(symbol_table) { histograms_[0] = hist_alloc(); histograms_[1] = hist_alloc(); } ThreadLocalHistogramImpl::~ThreadLocalHistogramImpl() { + MetricImpl::clear(); + name_.free(symbolTable()); hist_free(histograms_[0]); hist_free(histograms_[1]); } @@ -459,21 +515,23 @@ void ThreadLocalHistogramImpl::merge(histogram_t* target) { hist_clear(*other_histogram); } -ParentHistogramImpl::ParentHistogramImpl(const std::string& name, Store& parent, - TlsScope& tls_scope, std::string&& tag_extracted_name, - std::vector&& tags) - : MetricImpl(std::move(tag_extracted_name), std::move(tags)), parent_(parent), +ParentHistogramImpl::ParentHistogramImpl(StatName name, Store& parent, TlsScope& tls_scope, + absl::string_view tag_extracted_name, + const std::vector& tags) + : MetricImpl(tag_extracted_name, tags, parent.symbolTable()), parent_(parent), tls_scope_(tls_scope), interval_histogram_(hist_alloc()), cumulative_histogram_(hist_alloc()), interval_statistics_(interval_histogram_), cumulative_statistics_(cumulative_histogram_), - merged_(false), name_(name) {} + merged_(false), name_(name, parent.symbolTable()) {} ParentHistogramImpl::~ParentHistogramImpl() { + MetricImpl::clear(); + name_.free(symbolTable()); hist_free(interval_histogram_); hist_free(cumulative_histogram_); } void ParentHistogramImpl::recordValue(uint64_t value) { - Histogram& tls_histogram = tls_scope_.tlsHistogram(name(), *this); + Histogram& tls_histogram = tls_scope_.tlsHistogram(statName(), *this); tls_histogram.recordValue(value); parent_.deliverHistogramToSinks(*this, value); } diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 4f85c763567c..01b727cc6d61 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -12,6 +12,7 @@ #include "common/stats/heap_stat_data.h" #include "common/stats/histogram_impl.h" #include "common/stats/source_impl.h" +#include "common/stats/symbol_table_impl.h" #include "common/stats/utility.h" #include "absl/container/flat_hash_map.h" @@ -28,9 +29,9 @@ namespace Stats { */ class ThreadLocalHistogramImpl : public Histogram, public MetricImpl { public: - ThreadLocalHistogramImpl(const std::string& name, std::string&& tag_extracted_name, - std::vector&& tags); - ~ThreadLocalHistogramImpl(); + ThreadLocalHistogramImpl(StatName name, absl::string_view tag_extracted_name, + const std::vector& tags, SymbolTable& symbol_table); + ~ThreadLocalHistogramImpl() override; void merge(histogram_t* target); @@ -49,8 +50,9 @@ class ThreadLocalHistogramImpl : public Histogram, public MetricImpl { bool used() const override { return flags_ & Flags::Used; } // Stats::Metric - std::string name() const override { return name_; } - const char* nameCStr() const override { return name_.c_str(); } + StatName statName() const override { return name_.statName(); } + SymbolTable& symbolTable() override { return symbol_table_; } + const SymbolTable& symbolTable() const override { return symbol_table_; } private: uint64_t otherHistogramIndex() const { return 1 - current_active_; } @@ -58,10 +60,11 @@ class ThreadLocalHistogramImpl : public Histogram, public MetricImpl { histogram_t* histograms_[2]; std::atomic flags_; std::thread::id created_thread_id_; - const std::string name_; + StatNameStorage name_; + SymbolTable& symbol_table_; }; -typedef std::shared_ptr TlsHistogramSharedPtr; +using TlsHistogramSharedPtr = std::shared_ptr; class TlsScope; @@ -70,9 +73,9 @@ class TlsScope; */ class ParentHistogramImpl : public ParentHistogram, public MetricImpl { public: - ParentHistogramImpl(const std::string& name, Store& parent, TlsScope& tlsScope, - std::string&& tag_extracted_name, std::vector&& tags); - ~ParentHistogramImpl(); + ParentHistogramImpl(StatName name, Store& parent, TlsScope& tlsScope, + absl::string_view tag_extracted_name, const std::vector& tags); + ~ParentHistogramImpl() override; void addTlsHistogram(const TlsHistogramSharedPtr& hist_ptr); bool used() const override; @@ -94,8 +97,9 @@ class ParentHistogramImpl : public ParentHistogram, public MetricImpl { const std::string bucketSummary() const override; // Stats::Metric - std::string name() const override { return name_; } - const char* nameCStr() const override { return name_.c_str(); } + StatName statName() const override { return name_.statName(); } + SymbolTable& symbolTable() override { return parent_.symbolTable(); } + const SymbolTable& symbolTable() const override { return parent_.symbolTable(); } private: bool usedLockHeld() const EXCLUSIVE_LOCKS_REQUIRED(merge_lock_); @@ -109,10 +113,10 @@ class ParentHistogramImpl : public ParentHistogram, public MetricImpl { mutable Thread::MutexBasicLockable merge_lock_; std::list tls_histograms_ GUARDED_BY(merge_lock_); bool merged_; - const std::string name_; + StatNameStorage name_; }; -typedef std::shared_ptr ParentHistogramImplSharedPtr; +using ParentHistogramImplSharedPtr = std::shared_ptr; /** * Class used to create ThreadLocalHistogram in the scope. @@ -126,7 +130,7 @@ class TlsScope : public Scope { * @return a ThreadLocalHistogram within the scope's namespace. * @param name name of the histogram with scope prefix attached. */ - virtual Histogram& tlsHistogram(const std::string& name, ParentHistogramImpl& parent) PURE; + virtual Histogram& tlsHistogram(StatName name, ParentHistogramImpl& parent) PURE; }; /** @@ -136,18 +140,22 @@ class TlsScope : public Scope { class ThreadLocalStoreImpl : Logger::Loggable, public StoreRoot { public: ThreadLocalStoreImpl(const Stats::StatsOptions& stats_options, StatDataAllocator& alloc); - ~ThreadLocalStoreImpl(); + ~ThreadLocalStoreImpl() override; // Stats::Scope + Counter& counterx(StatName name) override { return default_scope_->counterx(name); } Counter& counter(const std::string& name) override { return default_scope_->counter(name); } ScopePtr createScope(const std::string& name) override; void deliverHistogramToSinks(const Histogram& histogram, uint64_t value) override { return default_scope_->deliverHistogramToSinks(histogram, value); } + Gauge& gaugex(StatName name) override { return default_scope_->gaugex(name); } Gauge& gauge(const std::string& name) override { return default_scope_->gauge(name); } - Histogram& histogram(const std::string& name) override { - return default_scope_->histogram(name); - }; + Histogram& histogramx(StatName name) override { return default_scope_->histogramx(name); } + Histogram& histogram(const std::string& name) override { return default_scope_->histogram(name); } + const SymbolTable& symbolTable() const override { return alloc_.symbolTable(); } + SymbolTable& symbolTable() override { return alloc_.symbolTable(); } + const TagProducer& tagProducer() const { return *tag_producer_; } // Stats::Store std::vector counters() const override; @@ -169,9 +177,10 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo Source& source() override { return source_; } const Stats::StatsOptions& statsOptions() const override { return stats_options_; } + absl::string_view truncateStatNameIfNeeded(absl::string_view name); private: - template using StatMap = CharStarHashMap; + template using StatMap = StatNameHashMap; struct TlsCacheEntry { StatMap counters_; @@ -187,27 +196,40 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo }; struct ScopeImpl : public TlsScope { - ScopeImpl(ThreadLocalStoreImpl& parent, const std::string& prefix) - : scope_id_(next_scope_id_++), parent_(parent), - prefix_(Utility::sanitizeStatsName(prefix)) {} - ~ScopeImpl(); + ScopeImpl(ThreadLocalStoreImpl& parent, const std::string& prefix); + ~ScopeImpl() override; // Stats::Scope - Counter& counter(const std::string& name) override; - ScopePtr createScope(const std::string& name) override { - return parent_.createScope(prefix_ + name); - } + Counter& counterx(StatName name) override; void deliverHistogramToSinks(const Histogram& histogram, uint64_t value) override; - Gauge& gauge(const std::string& name) override; - Histogram& histogram(const std::string& name) override; - Histogram& tlsHistogram(const std::string& name, ParentHistogramImpl& parent) override; + Gauge& gaugex(StatName name) override; + Histogram& histogramx(StatName name) override; + Histogram& tlsHistogram(StatName name, ParentHistogramImpl& parent) override; const Stats::StatsOptions& statsOptions() const override { return parent_.statsOptions(); } + ScopePtr createScope(const std::string& name) override { + return parent_.createScope(symbolTable().toString(prefix_.statName()) + "." + name); + } + const SymbolTable& symbolTable() const override { return parent_.symbolTable(); } + SymbolTable& symbolTable() override { return parent_.symbolTable(); } + + Counter& counter(const std::string& name) override { + // std::cerr << "counter(" << name << ")" << std::endl; + StatNameTempStorage storage(name, symbolTable()); + return counterx(storage.statName()); + } + Gauge& gauge(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return gaugex(storage.statName()); + } + Histogram& histogram(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return histogramx(storage.statName()); + } template - using MakeStatFn = - std::function(StatDataAllocator&, absl::string_view name, - std::string&& tag_extracted_name, - std::vector&& tags)>; + using MakeStatFn = std::function(StatDataAllocator&, StatName name, + absl::string_view tag_extracted_name, + const std::vector& tags)>; /** * Makes a stat either by looking it up in the central cache, @@ -221,20 +243,20 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo * used if non-empty, or filled in if empty (and non-null). */ template - StatType& - safeMakeStat(const std::string& name, StatMap>& central_cache_map, - MakeStatFn make_stat, StatMap>* tls_cache); + StatType& safeMakeStat(StatName name, StatMap>& central_cache_map, + MakeStatFn make_stat, + StatMap>* tls_cache); + + void extractTagsAndTruncate(StatName& name, + std::unique_ptr& truncated_name_storage, + std::vector& tags, std::string& tag_extracted_name); static std::atomic next_scope_id_; const uint64_t scope_id_; ThreadLocalStoreImpl& parent_; - const std::string prefix_; + StatNameStorage prefix_; CentralCacheEntry central_cache_; - - NullCounterImpl null_counter_; - NullGaugeImpl null_gauge_; - NullHistogramImpl null_histogram_; }; struct TlsCache : public ThreadLocal::ThreadLocalObject { @@ -252,8 +274,8 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo void clearScopeFromCaches(uint64_t scope_id); void releaseScopeCrossThread(ScopeImpl* scope); void mergeInternal(PostMergeCb mergeCb); - absl::string_view truncateStatNameIfNeeded(absl::string_view name); - bool rejects(const std::string& name) const; + bool rejects(StatName name) const; + // absl::string_view truncateStatNameIfNeeded(absl::string_view name); template void removeRejectedStats(StatMapClass& map, StatListClass& list); @@ -269,10 +291,15 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo StatsMatcherPtr stats_matcher_; std::atomic shutting_down_{}; std::atomic merge_in_progress_{}; + StatNameStorage stats_overflow_; Counter& num_last_resort_stats_; HeapStatDataAllocator heap_allocator_; SourceImpl source_; + NullCounterImpl null_counter_; + NullGaugeImpl null_gauge_; + NullHistogramImpl null_histogram_; + // Retain storage for deleted stats; these are no longer in maps because the // matcher-pattern was established after they were created. Since the stats // are held by reference in code that expects them to be there, we can't diff --git a/source/common/stats/utility.cc b/source/common/stats/utility.cc index afc4e8c4f4ca..7d4cd4cb2d37 100644 --- a/source/common/stats/utility.cc +++ b/source/common/stats/utility.cc @@ -3,11 +3,19 @@ #include #include +#include "absl/strings/match.h" + namespace Envoy { namespace Stats { -std::string Utility::sanitizeStatsName(const std::string& name) { - std::string stats_name = name; +std::string Utility::sanitizeStatsName(absl::string_view name) { + if (absl::EndsWith(name, ".")) { + name.remove_suffix(1); + } + if (absl::StartsWith(name, ".")) { + name.remove_prefix(1); + } + std::string stats_name = std::string(name); std::replace(stats_name.begin(), stats_name.end(), ':', '_'); std::replace(stats_name.begin(), stats_name.end(), '\0', '_'); return stats_name; diff --git a/source/common/stats/utility.h b/source/common/stats/utility.h index 18081360640c..f1cf255e47d7 100644 --- a/source/common/stats/utility.h +++ b/source/common/stats/utility.h @@ -2,6 +2,8 @@ #include +#include "absl/strings/string_view.h" + namespace Envoy { namespace Stats { @@ -12,7 +14,7 @@ class Utility { public: // ':' is a reserved char in statsd. Do a character replacement to avoid costly inline // translations later. - static std::string sanitizeStatsName(const std::string& name); + static std::string sanitizeStatsName(absl::string_view name); }; } // namespace Stats diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 07d63b062ef0..6e220538150c 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -450,6 +450,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable(options_); + restarter_ = std::make_unique(options_, symbol_table_); } #endif if (restarter_ == nullptr) { - restarter_ = std::make_unique(); + restarter_ = std::make_unique(symbol_table_); } tls_ = std::make_unique(); @@ -88,7 +88,7 @@ MainCommonBase::MainCommonBase(const OptionsImpl& options, Event::TimeSystem& ti break; } case Server::Mode::Validate: - restarter_ = std::make_unique(); + restarter_ = std::make_unique(symbol_table_); logging_context_ = std::make_unique(options_.logLevel(), options_.logFormat(), restarter_->logLock()); break; diff --git a/source/exe/main_common.h b/source/exe/main_common.h index 2af78ce27c71..e56a6013bbd5 100644 --- a/source/exe/main_common.h +++ b/source/exe/main_common.h @@ -5,6 +5,7 @@ #include "common/common/thread.h" #include "common/event/real_time_system.h" +#include "common/stats/fake_symbol_table_impl.h" #include "common/stats/thread_local_store.h" #include "common/thread_local/thread_local_impl.h" @@ -64,7 +65,7 @@ class MainCommonBase { protected: const Envoy::OptionsImpl& options_; - + Stats::FakeSymbolTableImpl symbol_table_; Server::ComponentFactory& component_factory_; Thread::ThreadFactory& thread_factory_; diff --git a/source/extensions/stat_sinks/hystrix/BUILD b/source/extensions/stat_sinks/hystrix/BUILD index 418c680513cc..388ad3dde8bc 100644 --- a/source/extensions/stat_sinks/hystrix/BUILD +++ b/source/extensions/stat_sinks/hystrix/BUILD @@ -37,5 +37,6 @@ envoy_cc_library( "//source/common/common:logger_lib", "//source/common/config:well_known_names", "//source/common/http:headers_lib", + "//source/common/stats:symbol_table_lib", ], ) diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc index 339b7c02d1cd..2725e75f5687 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.cc +++ b/source/extensions/stat_sinks/hystrix/hystrix.cc @@ -267,7 +267,8 @@ const std::string HystrixSink::printRollingWindows() { HystrixSink::HystrixSink(Server::Instance& server, const uint64_t num_buckets) : server_(server), current_index_(num_buckets > 0 ? num_buckets : DEFAULT_NUM_BUCKETS), - window_size_(current_index_ + 1) { + window_size_(current_index_ + 1), + cluster_upstream_rq_time_("cluster.upstream_rq_time", server.symbolTable()) { Server::Admin& admin = server_.admin(); ENVOY_LOG(debug, "adding hystrix_event_stream endpoint to enable connection to hystrix dashboard"); @@ -275,6 +276,8 @@ HystrixSink::HystrixSink(Server::Instance& server, const uint64_t num_buckets) MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false); } +HystrixSink::~HystrixSink() { cluster_upstream_rq_time_.free(server_.symbolTable()); } + Http::Code HystrixSink::handlerHystrixEventStream(absl::string_view, Http::HeaderMap& response_headers, Buffer::Instance&, @@ -327,12 +330,12 @@ void HystrixSink::flush(Stats::Source& source) { // Save a map of the relevant histograms per cluster in a convenient format. std::unordered_map time_histograms; for (const Stats::ParentHistogramSharedPtr& histogram : source.cachedHistograms()) { - if (histogram->tagExtractedName() == "cluster.upstream_rq_time") { + if (histogram->tagExtractedStatName() == cluster_upstream_rq_time_.statName()) { // TODO(mrice32): add an Envoy utility function to look up and return a tag for a metric. - auto it = std::find_if(histogram->tags().begin(), histogram->tags().end(), - [](const Stats::Tag& tag) { - return (tag.name_ == Config::TagNames::get().CLUSTER_NAME); - }); + auto tags = histogram->tags(); + auto it = std::find_if(tags.begin(), tags.end(), [](const Stats::Tag& tag) { + return (tag.name_ == Config::TagNames::get().CLUSTER_NAME); + }); // Make sure we found the cluster name tag ASSERT(it != histogram->tags().end()); diff --git a/source/extensions/stat_sinks/hystrix/hystrix.h b/source/extensions/stat_sinks/hystrix/hystrix.h index 54d26953abc8..4079a05fa144 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.h +++ b/source/extensions/stat_sinks/hystrix/hystrix.h @@ -10,6 +10,8 @@ #include "envoy/stats/sink.h" #include "envoy/stats/source.h" +#include "common/stats/symbol_table_impl.h" + namespace Envoy { namespace Extensions { namespace StatSinks { @@ -47,6 +49,7 @@ typedef std::unique_ptr ClusterStatsCachePtr; class HystrixSink : public Stats::Sink, public Logger::Loggable { public: HystrixSink(Server::Instance& server, uint64_t num_buckets); + ~HystrixSink() override; Http::Code handlerHystrixEventStream(absl::string_view, Http::HeaderMap& response_headers, Buffer::Instance&, Server::AdminStream& admin_stream); void flush(Stats::Source& source) override; @@ -155,6 +158,9 @@ class HystrixSink : public Stats::Sink, public Logger::Loggable cluster_stats_cache_map_; + + // Saved StatName for "cluster.upstream_rq_time" for fast comparisons in loop. + Stats::StatNameStorage cluster_upstream_rq_time_; }; typedef std::unique_ptr HystrixSinkPtr; diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index 2369cda8777c..c5a4790e2f8c 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -92,8 +92,10 @@ class ValidationInstance : Logger::Loggable, Http::Context& httpContext() override { return http_context_; } ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } + Stats::SymbolTable& symbolTable() override { return stats_store_.symbolTable(); } TimeSource& timeSource() override { return api_->timeSource(); } Envoy::MutexTracer* mutexTracer() override { return mutex_tracer_; } + // Http::CodeStats& codeStats() override { return code_stats_; } std::chrono::milliseconds statsFlushInterval() const override { return config_.statsFlushInterval(); diff --git a/source/server/hot_restart_impl.cc b/source/server/hot_restart_impl.cc index 334ec322ecf1..52f4827dfcb9 100644 --- a/source/server/hot_restart_impl.cc +++ b/source/server/hot_restart_impl.cc @@ -122,7 +122,7 @@ std::string SharedMemory::version(uint64_t max_num_stats, stats_options.maxNameLength()); } -HotRestartImpl::HotRestartImpl(const Options& options) +HotRestartImpl::HotRestartImpl(const Options& options, Stats::SymbolTable& symbol_table) : options_(options), stats_set_options_(blockMemHashOptions(options.maxStats())), shmem_(SharedMemory::initialize( Stats::RawStatDataSet::numBytes(stats_set_options_, options_.statsOptions()), options_)), @@ -136,8 +136,8 @@ HotRestartImpl::HotRestartImpl(const Options& options) std::make_unique(stats_set_options_, options.restartEpoch() == 0, shmem_.stats_set_data_, options_.statsOptions()); } - stats_allocator_ = std::make_unique(stat_lock_, *stats_set_, - options_.statsOptions()); + stats_allocator_ = std::make_unique( + stat_lock_, *stats_set_, options_.statsOptions(), symbol_table); my_domain_socket_ = bindDomainSocket(options.restartEpoch()); child_address_ = createDomainSocketAddress((options.restartEpoch() + 1)); initDomainSocketAddress(&parent_address_); diff --git a/source/server/hot_restart_impl.h b/source/server/hot_restart_impl.h index 5d432c42ad44..8f77c7d61669 100644 --- a/source/server/hot_restart_impl.h +++ b/source/server/hot_restart_impl.h @@ -114,7 +114,7 @@ class ProcessSharedMutex : public Thread::BasicLockable { */ class HotRestartImpl : public HotRestart, Logger::Loggable { public: - HotRestartImpl(const Options& options); + HotRestartImpl(const Options& options, Stats::SymbolTable& symbol_table); // Server::HotRestart void drainParentListeners() override; diff --git a/source/server/hot_restart_nop_impl.h b/source/server/hot_restart_nop_impl.h index bc31f897f29a..756bb8d217fd 100644 --- a/source/server/hot_restart_nop_impl.h +++ b/source/server/hot_restart_nop_impl.h @@ -15,7 +15,7 @@ namespace Server { */ class HotRestartNopImpl : public Server::HotRestart { public: - HotRestartNopImpl() {} + explicit HotRestartNopImpl(Stats::SymbolTable& symbol_table) : stats_allocator_(symbol_table) {} // Server::HotRestart void drainParentListeners() override {} diff --git a/source/server/server.h b/source/server/server.h index 27b02a0722f4..bdd2eaea1d27 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -184,6 +184,7 @@ class InstanceImpl : Logger::Loggable, public Instance { Http::Context& httpContext() override { return http_context_; } ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } + Stats::SymbolTable& symbolTable() override { return stats_store_.symbolTable(); } TimeSource& timeSource() override { return time_source_; } std::chrono::milliseconds statsFlushInterval() const override { diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index 3d262d90ee43..46657034b178 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -108,6 +108,7 @@ envoy_cc_test_library( "//test/mocks/local_info:local_info_mocks", "//test/mocks/server:server_mocks", "//test/proto:helloworld_proto_cc", + "//test/test_common:global_lib", "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index 9e1b8e1570db..8351f4536b14 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -13,6 +13,7 @@ #include "common/http/async_client_impl.h" #include "common/http/codes.h" #include "common/http/http2/conn_pool.h" +#include "common/stats/fake_symbol_table_impl.h" #include "common/network/connection_impl.h" #include "common/network/raw_buffer_socket.h" @@ -29,6 +30,7 @@ #include "test/mocks/upstream/mocks.h" #include "test/proto/helloworld.pb.h" #include "test/test_common/environment.h" +#include "test/test_common/global.h" #include "test/test_common/test_time.h" #include "test/test_common/utility.h" @@ -413,7 +415,8 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { FakeHttpConnectionPtr fake_connection_; std::vector fake_streams_; const Protobuf::MethodDescriptor* method_descriptor_; - Stats::IsolatedStoreImpl* stats_store_ = new Stats::IsolatedStoreImpl(); + Envoy::Test::Global symbol_table_; + Stats::IsolatedStoreImpl* stats_store_ = new Stats::IsolatedStoreImpl(*symbol_table_); Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; DispatcherHelper dispatcher_helper_{*dispatcher_}; diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index ba6115bedc04..26f97392430c 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -62,6 +62,7 @@ class AsyncClientImplTest : public testing::Test { } MessagePtr message_{new RequestMessageImpl()}; + Stats::MockIsolatedStatsStore stats_store_; MockAsyncClientCallbacks callbacks_; MockAsyncClientStreamCallbacks stream_callbacks_; NiceMock cm_; @@ -71,7 +72,6 @@ class AsyncClientImplTest : public testing::Test { NiceMock dispatcher_; NiceMock runtime_; NiceMock random_; - Stats::IsolatedStoreImpl stats_store_; NiceMock local_info_; Http::ContextImpl http_context_; AsyncClientImpl client_; diff --git a/test/common/http/codes_speed_test.cc b/test/common/http/codes_speed_test.cc index 15634d95fd7d..b85f8db68c73 100644 --- a/test/common/http/codes_speed_test.cc +++ b/test/common/http/codes_speed_test.cc @@ -19,6 +19,8 @@ namespace Http { class CodeUtilitySpeedTest { public: + CodeUtilitySpeedTest() : global_store_(symbol_table_), cluster_scope_(symbol_table_) {} + void addResponse(uint64_t code, bool canary, bool internal_request, const std::string& request_vhost_name = EMPTY_STRING, const std::string& request_vcluster_name = EMPTY_STRING, @@ -51,6 +53,7 @@ class CodeUtilitySpeedTest { code_stats_.chargeResponseTiming(info); } + Stats::SymbolTableImpl symbol_table_; Stats::IsolatedStoreImpl global_store_; Stats::IsolatedStoreImpl cluster_scope_; Http::CodeStatsImpl code_stats_; diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index f6a7da2851b0..5540b70e3894 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -384,6 +384,7 @@ DEFINE_PROTO_FUZZER(const test::common::http::ConnManagerImplTestCase& input) { FuzzConfig config; NiceMock drain_close; NiceMock random; + Stats::SymbolTableImpl symbol_table; Http::ContextImpl http_context; NiceMock runtime; NiceMock local_info; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 9c9e90a9de79..dc190bc8d560 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -261,12 +261,12 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan DangerousDeprecatedTestTime test_time_; RouteConfigProvider route_config_provider_; NiceMock tracer_; + Stats::IsolatedStoreImpl fake_stats_; Http::ContextImpl http_context_; NiceMock runtime_; NiceMock log_manager_; std::string access_log_path_; std::list access_logs_; - Stats::IsolatedStoreImpl fake_stats_; NiceMock filter_callbacks_; MockServerConnection* codec_; NiceMock filter_factory_; diff --git a/test/common/http/user_agent_test.cc b/test/common/http/user_agent_test.cc index 173f37ee8b81..04afdec8c207 100644 --- a/test/common/http/user_agent_test.cc +++ b/test/common/http/user_agent_test.cc @@ -19,7 +19,7 @@ namespace { TEST(UserAgentTest, All) { Stats::MockStore stat_store; - NiceMock original_histogram; + NiceMock original_histogram; //(stat_store.symbolTable()); Event::SimulatedTimeSystem time_system; Stats::Timespan span(original_histogram, time_system); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 83cad58665f4..9f405a252197 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -212,7 +212,7 @@ class RouterTestBase : public testing::Test { Event::SimulatedTimeSystem test_time_; std::string upstream_zone_{"to_az"}; envoy::api::v2::core::Locality upstream_locality_; - Stats::IsolatedStoreImpl stats_store_; + NiceMock stats_store_; NiceMock cm_; NiceMock runtime_; NiceMock random_; diff --git a/test/common/stats/heap_stat_data_test.cc b/test/common/stats/heap_stat_data_test.cc index e9ee25090a98..8f6dad629fe7 100644 --- a/test/common/stats/heap_stat_data_test.cc +++ b/test/common/stats/heap_stat_data_test.cc @@ -2,6 +2,7 @@ #include "common/stats/heap_stat_data.h" #include "common/stats/stats_options_impl.h" +#include "common/stats/symbol_table_impl.h" #include "test/test_common/logging.h" @@ -11,33 +12,59 @@ namespace Envoy { namespace Stats { namespace { +class HeapStatDataTest : public testing::Test { +protected: + HeapStatDataTest() : alloc_(symbol_table_) {} + ~HeapStatDataTest() { clearStorage(); } + + StatNameStorage makeStatStorage(absl::string_view name) { + return StatNameStorage(name, symbol_table_); + } + + StatName makeStat(absl::string_view name) { + stat_name_storage_.emplace_back(makeStatStorage(name)); + return stat_name_storage_.back().statName(); + } + + void clearStorage() { + for (auto& stat_name_storage : stat_name_storage_) { + stat_name_storage.free(symbol_table_); + } + stat_name_storage_.clear(); + EXPECT_EQ(0, symbol_table_.numSymbols()); + } + + SymbolTableImpl symbol_table_; + HeapStatDataAllocator alloc_; + std::vector stat_name_storage_; +}; + // No truncation occurs in the implementation of HeapStatData. // Note: a similar test using RawStatData* is in raw_stat_data_test.cc. -TEST(HeapStatDataTest, HeapNoTruncate) { +TEST_F(HeapStatDataTest, HeapNoTruncate) { StatsOptionsImpl stats_options; - HeapStatDataAllocator alloc; const std::string long_string(stats_options.maxNameLength() + 1, 'A'); + StatName stat_name = makeStat(long_string); HeapStatData* stat{}; - EXPECT_NO_LOGS(stat = alloc.alloc(long_string)); - EXPECT_EQ(stat->key(), long_string); - alloc.free(*stat); -} + EXPECT_NO_LOGS(stat = &alloc_.alloc(stat_name)); + EXPECT_EQ(stat->statName(), stat_name); + alloc_.free(*stat); +}; // Note: a similar test using RawStatData* is in raw_stat_data_test.cc. -TEST(HeapStatDataTest, HeapAlloc) { - HeapStatDataAllocator alloc; - HeapStatData* stat_1 = alloc.alloc("ref_name"); +TEST_F(HeapStatDataTest, HeapAlloc) { + HeapStatData* stat_1 = &alloc_.alloc(makeStat("ref_name")); ASSERT_NE(stat_1, nullptr); - HeapStatData* stat_2 = alloc.alloc("ref_name"); + HeapStatData* stat_2 = &alloc_.alloc(makeStat("ref_name")); ASSERT_NE(stat_2, nullptr); - HeapStatData* stat_3 = alloc.alloc("not_ref_name"); + HeapStatData* stat_3 = &alloc_.alloc(makeStat("not_ref_name")); ASSERT_NE(stat_3, nullptr); EXPECT_EQ(stat_1, stat_2); EXPECT_NE(stat_1, stat_3); EXPECT_NE(stat_2, stat_3); - alloc.free(*stat_1); - alloc.free(*stat_2); - alloc.free(*stat_3); + alloc_.free(*stat_1); + alloc_.free(*stat_2); + alloc_.free(*stat_3); } } // namespace diff --git a/test/common/stats/isolated_store_impl_test.cc b/test/common/stats/isolated_store_impl_test.cc index b2aaa80693fd..05e63c815761 100644 --- a/test/common/stats/isolated_store_impl_test.cc +++ b/test/common/stats/isolated_store_impl_test.cc @@ -11,11 +11,14 @@ namespace Envoy { namespace Stats { -TEST(StatsIsolatedStoreImplTest, All) { - IsolatedStoreImpl store; +class StatsIsolatedStoreImplTest : public testing::Test { +protected: + IsolatedStoreImpl store_; +}; - ScopePtr scope1 = store.createScope("scope1."); - Counter& c1 = store.counter("c1"); +TEST_F(StatsIsolatedStoreImplTest, All) { + ScopePtr scope1 = store_.createScope("scope1."); + Counter& c1 = store_.counter("c1"); Counter& c2 = scope1->counter("c2"); EXPECT_EQ("c1", c1.name()); EXPECT_EQ("scope1.c2", c2.name()); @@ -24,7 +27,7 @@ TEST(StatsIsolatedStoreImplTest, All) { EXPECT_EQ(0, c1.tags().size()); EXPECT_EQ(0, c1.tags().size()); - Gauge& g1 = store.gauge("g1"); + Gauge& g1 = store_.gauge("g1"); Gauge& g2 = scope1->gauge("g2"); EXPECT_EQ("g1", g1.name()); EXPECT_EQ("scope1.g2", g2.name()); @@ -33,7 +36,7 @@ TEST(StatsIsolatedStoreImplTest, All) { EXPECT_EQ(0, g1.tags().size()); EXPECT_EQ(0, g1.tags().size()); - Histogram& h1 = store.histogram("h1"); + Histogram& h1 = store_.histogram("h1"); Histogram& h2 = scope1->histogram("h2"); scope1->deliverHistogramToSinks(h2, 0); EXPECT_EQ("h1", h1.name()); @@ -52,16 +55,15 @@ TEST(StatsIsolatedStoreImplTest, All) { ScopePtr scope3 = scope1->createScope(std::string("foo:\0:.", 7)); EXPECT_EQ("scope1.foo___.bar", scope3->counter("bar").name()); - EXPECT_EQ(4UL, store.counters().size()); - EXPECT_EQ(2UL, store.gauges().size()); + EXPECT_EQ(4UL, store_.counters().size()); + EXPECT_EQ(2UL, store_.gauges().size()); } -TEST(StatsIsolatedStoreImplTest, LongStatName) { - IsolatedStoreImpl store; +TEST_F(StatsIsolatedStoreImplTest, LongStatName) { Stats::StatsOptionsImpl stats_options; const std::string long_string(stats_options.maxNameLength() + 1, 'A'); - ScopePtr scope = store.createScope("scope."); + ScopePtr scope = store_.createScope("scope."); Counter& counter = scope->counter(long_string); EXPECT_EQ(absl::StrCat("scope.", long_string), counter.name()); } @@ -80,11 +82,10 @@ struct TestStats { ALL_TEST_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) }; -TEST(StatsMacros, All) { - IsolatedStoreImpl stats_store; - TestStats test_stats{ALL_TEST_STATS(POOL_COUNTER_PREFIX(stats_store, "test."), - POOL_GAUGE_PREFIX(stats_store, "test."), - POOL_HISTOGRAM_PREFIX(stats_store, "test."))}; +TEST_F(StatsIsolatedStoreImplTest, StatsMacros) { + TestStats test_stats{ALL_TEST_STATS(POOL_COUNTER_PREFIX(store_, "test."), + POOL_GAUGE_PREFIX(store_, "test."), + POOL_HISTOGRAM_PREFIX(store_, "test."))}; Counter& counter = test_stats.test_counter_; EXPECT_EQ("test.test_counter", counter.name()); diff --git a/test/common/stats/raw_stat_data_test.cc b/test/common/stats/raw_stat_data_test.cc index f7bf288aacda..f06072922283 100644 --- a/test/common/stats/raw_stat_data_test.cc +++ b/test/common/stats/raw_stat_data_test.cc @@ -14,9 +14,10 @@ namespace { class RawStatDataTest : public testing::Test { public: - RawStatDataTest() : allocator_(stats_options_) {} + RawStatDataTest() : allocator_(stats_options_, symbol_table_) {} StatsOptionsImpl stats_options_; + SymbolTableImpl symbol_table_; TestAllocator allocator_; // This is RawStatDataAllocator with some size settings. }; diff --git a/test/common/stats/thread_local_store_speed_test.cc b/test/common/stats/thread_local_store_speed_test.cc index 88090b6dc44c..4bbbd6e654aa 100644 --- a/test/common/stats/thread_local_store_speed_test.cc +++ b/test/common/stats/thread_local_store_speed_test.cc @@ -22,11 +22,19 @@ namespace Envoy { class ThreadLocalStorePerf { public: ThreadLocalStorePerf() - : store_(options_, heap_alloc_), api_(Api::createApiForTest(store_, time_system_)) { + : heap_alloc_(symbol_table_), store_(options_, heap_alloc_), + api_(Api::createApiForTest(store_, time_system_)) { store_.setTagProducer(std::make_unique(stats_config_)); + + Stats::TestUtil::forEachSampleStat(1000, [this](absl::string_view name) { + stat_names_.push_back(std::make_unique(name, symbol_table_)); + }); } ~ThreadLocalStorePerf() { + for (auto& stat_name_storage : stat_names_) { + stat_name_storage->free(symbol_table_); + } store_.shutdownThreading(); if (tls_) { tls_->shutdownGlobalThreading(); @@ -34,8 +42,9 @@ class ThreadLocalStorePerf { } void accessCounters() { - Stats::TestUtil::forEachSampleStat( - 1000, [this](absl::string_view name) { store_.counter(std::string(name)); }); + for (auto& stat_name_storage : stat_names_) { + store_.counterx(stat_name_storage->statName()); + } } void initThreading() { @@ -45,6 +54,7 @@ class ThreadLocalStorePerf { } private: + Stats::SymbolTableImpl symbol_table_; Event::SimulatedTimeSystem time_system_; Stats::StatsOptionsImpl options_; Stats::HeapStatDataAllocator heap_alloc_; @@ -53,6 +63,7 @@ class ThreadLocalStorePerf { Event::DispatcherPtr dispatcher_; std::unique_ptr tls_; envoy::config::metrics::v2::StatsConfig stats_config_; + std::vector> stat_names_; }; } // namespace Envoy diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index dddfea3e6146..664b4a79e37e 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -36,7 +36,7 @@ namespace Stats { class StatsThreadLocalStoreTest : public testing::Test { public: void SetUp() override { - alloc_ = std::make_unique(options_); + alloc_ = std::make_unique(options_, symbol_table_); resetStoreWithAlloc(*alloc_); } @@ -45,6 +45,7 @@ class StatsThreadLocalStoreTest : public testing::Test { store_->addSink(sink_); } + Stats::SymbolTableImpl symbol_table_; NiceMock main_thread_dispatcher_; NiceMock tls_; StatsOptionsImpl options_; @@ -75,7 +76,7 @@ class HistogramTest : public testing::Test { public: using NameHistogramMap = std::map; - HistogramTest() : alloc_(options_) {} + HistogramTest() : alloc_(options_, symbol_table_) {} void SetUp() override { store_ = std::make_unique(options_, alloc_); @@ -168,6 +169,7 @@ class HistogramTest : public testing::Test { MOCK_METHOD1(alloc, RawStatData*(const std::string& name)); MOCK_METHOD1(free, void(RawStatData& data)); + SymbolTableImpl symbol_table_; NiceMock main_thread_dispatcher_; NiceMock tls_; StatsOptionsImpl options_; @@ -445,11 +447,13 @@ TEST_F(StatsThreadLocalStoreTest, HotRestartTruncation) { // The stats did not overflow yet. EXPECT_EQ(0UL, store_->counter("stats.overflow").value()); - // The name will be truncated, so we won't be able to find it with the entire name. - EXPECT_EQ(nullptr, TestUtility::findCounter(*store_, name_1).get()); + // Truncation occurs in the underlying representation, but the by-name lookups + // are all based on the untruncated name. + EXPECT_NE(nullptr, TestUtility::findCounter(*store_, name_1).get()); - // But we can find it based on the expected truncation. - EXPECT_NE(nullptr, TestUtility::findCounter(*store_, name_1.substr(0, max_name_length)).get()); + // Outside the stats system, no Envoy code can see the truncated view, so + // lookups for truncated names will fail. + EXPECT_EQ(nullptr, TestUtility::findCounter(*store_, name_1.substr(0, max_name_length)).get()); // The same should be true with heap allocation, which occurs when the default // allocator fails. @@ -457,11 +461,11 @@ TEST_F(StatsThreadLocalStoreTest, HotRestartTruncation) { EXPECT_CALL(*alloc_, alloc(_)).WillOnce(Return(nullptr)); store_->counter(name_2); - // Same deal: the name will be truncated, so we won't be able to find it with the entire name. - EXPECT_EQ(nullptr, TestUtility::findCounter(*store_, name_1).get()); + // Same deal: the name will be truncated, but we find it with the entire name. + EXPECT_NE(nullptr, TestUtility::findCounter(*store_, name_1).get()); - // But we can find it based on the expected truncation. - EXPECT_NE(nullptr, TestUtility::findCounter(*store_, name_1.substr(0, max_name_length)).get()); + // But we can't find it based on the truncation -- that name is not visible at the API. + EXPECT_EQ(nullptr, TestUtility::findCounter(*store_, name_1.substr(0, max_name_length)).get()); // Now the stats have overflowed. EXPECT_EQ(1UL, store_->counter("stats.overflow").value()); @@ -624,6 +628,8 @@ TEST_F(StatsMatcherTLSTest, TestExclusionRegex) { class HeapStatsThreadLocalStoreTest : public StatsThreadLocalStoreTest { public: + HeapStatsThreadLocalStoreTest() : heap_alloc_(symbol_table_) {} + void SetUp() override { resetStoreWithAlloc(heap_alloc_); // Note: we do not call StatsThreadLocalStoreTest::SetUp here as that @@ -703,7 +709,7 @@ TEST_F(HeapStatsThreadLocalStoreTest, MemoryWithoutTls) { 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); EXPECT_LT(start_mem, end_mem); - EXPECT_LT(end_mem - start_mem, 28 * million); // actual value: 27203216 as of Oct 29, 2018 + EXPECT_LT(end_mem - start_mem, 13 * million); // actual value: 12443472 as of Nov 7, 2018 } TEST_F(HeapStatsThreadLocalStoreTest, MemoryWithTls) { @@ -726,7 +732,7 @@ TEST_F(HeapStatsThreadLocalStoreTest, MemoryWithTls) { 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); EXPECT_LT(start_mem, end_mem); - EXPECT_LT(end_mem - start_mem, 31 * million); // actual value: 30482576 as of Oct 29, 2018 + EXPECT_LT(end_mem - start_mem, 16 * million); // actual value: 15722832 as of Nov 7, 2018 } TEST_F(StatsThreadLocalStoreTest, ShuttingDown) { @@ -946,13 +952,15 @@ TEST_F(HistogramTest, BasicHistogramUsed) { class TruncatingAllocTest : public HeapStatsThreadLocalStoreTest { protected: - TruncatingAllocTest() : test_alloc_(options_), long_name_(options_.maxNameLength() + 1, 'A') {} + TruncatingAllocTest() + : test_alloc_(options_, symbol_table_), long_name_(options_.maxNameLength() + 1, 'A') {} void SetUp() override { store_ = std::make_unique(options_, test_alloc_); // Do not call superclass SetUp. } + SymbolTableImpl symbol_table_; TestAllocator test_alloc_; std::string long_name_; }; diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index ce9ac4f68347..f7434905527c 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -474,9 +474,10 @@ class TcpProxyTest : public testing::Test { Event::TestTimeSystem& timeSystem() { return factory_context_.timeSystem(); } + NiceMock factory_context_; ConfigSharedPtr config_; + std::unique_ptr filter_; NiceMock filter_callbacks_; - NiceMock factory_context_; std::vector>> upstream_hosts_{}; std::vector>> upstream_connections_{}; std::vector>> @@ -485,7 +486,6 @@ class TcpProxyTest : public testing::Test { std::vector>> conn_pool_handles_; NiceMock conn_pool_; Tcp::ConnectionPool::UpstreamCallbacks* upstream_callbacks_; - std::unique_ptr filter_; StringViewSaver access_log_data_; Network::Address::InstanceConstSharedPtr upstream_local_address_; Network::Address::InstanceConstSharedPtr upstream_remote_address_; @@ -1108,10 +1108,10 @@ class TcpProxyRoutingTest : public testing::Test { Event::TestTimeSystem& timeSystem() { return factory_context_.timeSystem(); } + NiceMock factory_context_; ConfigSharedPtr config_; NiceMock connection_; NiceMock filter_callbacks_; - NiceMock factory_context_; std::unique_ptr filter_; }; diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 5fee48736d8d..e440228cede6 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -156,17 +156,15 @@ class TestClusterManagerImpl : public ClusterManagerImpl { // it with the right values at the right times. class MockedUpdatedClusterManagerImpl : public TestClusterManagerImpl { public: - MockedUpdatedClusterManagerImpl(const envoy::config::bootstrap::v2::Bootstrap& bootstrap, - ClusterManagerFactory& factory, Stats::Store& stats, - ThreadLocal::Instance& tls, Runtime::Loader& runtime, - Runtime::RandomGenerator& random, - const LocalInfo::LocalInfo& local_info, - AccessLog::AccessLogManager& log_manager, - Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, - Api::Api& api, MockLocalClusterUpdate& local_cluster_update, - MockLocalHostsRemoved& local_hosts_removed) + MockedUpdatedClusterManagerImpl( + const envoy::config::bootstrap::v2::Bootstrap& bootstrap, ClusterManagerFactory& factory, + Stats::Store& stats, ThreadLocal::Instance& tls, Runtime::Loader& runtime, + Runtime::RandomGenerator& random, const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager, Event::Dispatcher& main_thread_dispatcher, + Server::Admin& admin, Api::Api& api, MockLocalClusterUpdate& local_cluster_update, + MockLocalHostsRemoved& local_hosts_removed, Http::Context& http_context) : TestClusterManagerImpl(bootstrap, factory, stats, tls, runtime, random, local_info, - log_manager, main_thread_dispatcher, admin, api, http_context_), + log_manager, main_thread_dispatcher, admin, api, http_context), local_cluster_update_(local_cluster_update), local_hosts_removed_(local_hosts_removed) {} protected: @@ -180,7 +178,6 @@ class MockedUpdatedClusterManagerImpl : public TestClusterManagerImpl { local_hosts_removed_.post(hosts_removed); } - Http::ContextImpl http_context_; MockLocalClusterUpdate& local_cluster_update_; MockLocalHostsRemoved& local_hosts_removed_; }; @@ -235,7 +232,7 @@ class ClusterManagerImplTest : public testing::Test { cluster_manager_ = std::make_unique( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.random_, factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, *api_, - local_cluster_update_, local_hosts_removed_); + local_cluster_update_, local_hosts_removed_, http_context_); } void checkStats(uint64_t added, uint64_t modified, uint64_t removed, uint64_t active, diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index edb4f12d6e18..506842d849c5 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -67,7 +67,7 @@ template class HttpFilterTestBase : public T { Filters::Common::ExtAuthz::RequestCallbacks* request_callbacks_{}; Http::TestHeaderMapImpl request_headers_; Buffer::OwnedImpl data_; - Stats::IsolatedStoreImpl stats_store_; + NiceMock stats_store_; NiceMock runtime_; NiceMock cm_; NiceMock local_info_; @@ -82,7 +82,9 @@ template class HttpFilterTestBase : public T { } }; -class HttpFilterTest : public HttpFilterTestBase {}; +class HttpFilterTest : public HttpFilterTestBase { +public: +}; using CreateFilterConfigFunc = envoy::config::filter::http::ext_authz::v2::ExtAuthz(); @@ -153,6 +155,23 @@ TEST_F(HttpFilterTest, MergeConfig) { EXPECT_EQ("value", merged_extensions.at("key")); } +class HttpExtAuthzFilterTestBase : public HttpFilterTest { +public: + void initConfig(envoy::config::filter::http::ext_authz::v2::ExtAuthz& proto_config) { + config_ = std::make_unique(proto_config, local_info_, stats_store_, runtime_, + http_context_); + } +}; + +class HttpExtAuthzFilterTest : public HttpExtAuthzFilterTestBase { +public: + void initialize(const std::string yaml) { + envoy::config::filter::http::ext_authz::v2::ExtAuthz proto_config{}; + MessageUtil::loadFromYaml(yaml, proto_config); + initConfig(proto_config); + } +}; + // Test when failure_mode_allow is NOT set and the response from the authorization service is Error // that the request is not allowed to continue. TEST_F(HttpFilterTest, ErrorFailClose) { diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 380d93be9e82..895a79292f78 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -86,7 +86,7 @@ class HttpRateLimitFilterTest : public testing::Test { Http::TestHeaderMapImpl response_headers_; Buffer::OwnedImpl data_; Buffer::OwnedImpl response_data_; - Stats::IsolatedStoreImpl stats_store_; + NiceMock stats_store_; NiceMock runtime_; NiceMock route_rate_limit_; NiceMock vh_rate_limit_; diff --git a/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc b/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc index b4263b010e58..015cf0af4a76 100644 --- a/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc +++ b/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc @@ -179,6 +179,7 @@ TEST(UdpStatsdSinkTest, CheckActualStatsWithCustomPrefix) { } TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { + Stats::SymbolTableImpl symbol_table; NiceMock source; auto writer_ptr = std::make_shared>(); NiceMock tls_; @@ -186,6 +187,7 @@ TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { std::vector tags = {Stats::Tag{"key1", "value1"}, Stats::Tag{"key2", "value2"}}; auto counter = std::make_shared>(); + // counter->symbol_table_ = &symbol_table; counter->name_ = "test_counter"; counter->used_ = true; counter->latch_ = 1; @@ -198,6 +200,7 @@ TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { counter->used_ = false; auto gauge = std::make_shared>(); + // gauge->symbol_table_ = &symbol_table; gauge->name_ = "test_gauge"; gauge->value_ = 1; gauge->used_ = true; @@ -209,6 +212,7 @@ TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { sink.flush(source); NiceMock timer; + // timer.symbol_table_ = &symbol_table; timer.name_ = "test_timer"; timer.tags_ = tags; EXPECT_CALL(*std::dynamic_pointer_cast>(writer_ptr), diff --git a/test/extensions/stats_sinks/hystrix/hystrix_test.cc b/test/extensions/stats_sinks/hystrix/hystrix_test.cc index 26593d9b58ef..2f52df159d19 100644 --- a/test/extensions/stats_sinks/hystrix/hystrix_test.cc +++ b/test/extensions/stats_sinks/hystrix/hystrix_test.cc @@ -462,17 +462,8 @@ TEST_F(HystrixSinkTest, HistogramTest) { // Create histogram for the Hystrix sink to read. auto histogram = std::make_shared>(); histogram->name_ = "cluster." + cluster1_name_ + ".upstream_rq_time"; - const std::string tag_extracted_name = "cluster.upstream_rq_time"; - ON_CALL(*histogram, tagExtractedName()) - .WillByDefault(testing::ReturnRefOfCopy(tag_extracted_name)); - std::vector tags; - Stats::Tag tag = { - Config::TagNames::get().CLUSTER_NAME, // name_ - cluster1_name_ // value_ - }; - tags.emplace_back(tag); - ON_CALL(*histogram, tags()).WillByDefault(testing::ReturnRef(tags)); - + histogram->setTagExtractedName("cluster.upstream_rq_time"); + histogram->tags_.emplace_back(Stats::Tag{Config::TagNames::get().CLUSTER_NAME, cluster1_name_}); histogram->used_ = true; // Init with data such that the quantile value is equal to the quantile. diff --git a/test/integration/BUILD b/test/integration/BUILD index 8635220d0bc8..c350d8eef603 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -341,6 +341,7 @@ envoy_cc_test_library( "//test/config:utility_lib", "//test/mocks/buffer:buffer_mocks", "//test/mocks/server:server_mocks", + "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", diff --git a/test/integration/integration.cc b/test/integration/integration.cc index e9499226ff34..428a9095dcb3 100644 --- a/test/integration/integration.cc +++ b/test/integration/integration.cc @@ -389,6 +389,7 @@ void BaseIntegrationTest::createGeneratedApiTestServer(const std::string& bootst test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); registerTestServerPorts(port_names); } + // stats_ = std::make_unique(test_server_->stats().symbolTable()); } void BaseIntegrationTest::createApiTestServer(const ApiFilesystemConfig& api_filesystem_config, diff --git a/test/integration/integration.h b/test/integration/integration.h index d6d03e98f9a2..d4dea89f45b4 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -13,6 +13,7 @@ #include "test/integration/utility.h" #include "test/mocks/buffer/mocks.h" #include "test/mocks/server/mocks.h" +#include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" @@ -220,6 +221,7 @@ class BaseIntegrationTest : Logger::Loggable { public: Event::DispatcherPtr dispatcher_; + // Api::ApiPtr api_; /** * Open a connection to Envoy, send a series of bytes, and return the @@ -261,6 +263,8 @@ class BaseIntegrationTest : Logger::Loggable { uint32_t fake_upstreams_count_{1}; spdlog::level::level_enum default_log_level_; IntegrationTestServerPtr test_server_; + // std::unique_ptr stats_; + // A map of keys to port names. Generally the names are pulled from the v2 listener name // but if a listener is created via ADS, it will be from whatever key is used with registerPort. TestEnvironment::PortMap port_map_; diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index 2ce9f3b73b34..9f8cc10fae93 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -465,7 +465,7 @@ class StatsMatcherIntegrationTest } void makeRequest() { response_ = IntegrationUtil::makeSingleRequest(lookupPort("admin"), "GET", "/stats", "", - downstreamProtocol(), version_); + downstreamProtocol(), version_, "host", ""); ASSERT_TRUE(response_->complete()); EXPECT_STREQ("200", response_->headers().Status()->value().c_str()); } diff --git a/test/integration/server.cc b/test/integration/server.cc index 8c1f88bd323b..f7ab6bfb6a88 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -141,6 +141,22 @@ void IntegrationTestServer::serverReady() { void IntegrationTestServer::threadRoutine(const Network::Address::IpVersion version, bool deterministic) { OptionsImpl options(Server::createTestOptionsImpl(config_path_, "", version)); + + /* + // TODO(jmarantz): Sadly, we use a mock symbol table here, as plumbing the + // real symbol table through the mocking hierarchy -- which generally + // constructs hierarchies of objects with no context, is too daunting. I think + // the right thing to do is to avoid mocks in integration tests. + Test::Global symbol_table; + Server::HotRestartNopImpl restarter(*symbol_table); + Thread::MutexBasicLockable lock; + + ThreadLocal::InstanceImpl tls; + Stats::HeapStatDataAllocator stats_allocator(*symbol_table); + Stats::StatsOptionsImpl stats_options; + Stats::ThreadLocalStoreImpl stats_store(stats_options, stats_allocator); + stat_store_ = &stats_store; + */ Thread::MutexBasicLockable lock; Runtime::RandomGeneratorPtr random_generator; @@ -158,9 +174,10 @@ void IntegrationTestServerImpl::createAndRunEnvoyServer( Network::Address::InstanceConstSharedPtr local_address, TestHooks& hooks, Thread::BasicLockable& access_log_lock, Server::ComponentFactory& component_factory, Runtime::RandomGeneratorPtr&& random_generator) { - Server::HotRestartNopImpl restarter; + Stats::SymbolTableImpl symbol_table; + Server::HotRestartNopImpl restarter(symbol_table); ThreadLocal::InstanceImpl tls; - Stats::HeapStatDataAllocator stats_allocator; + Stats::HeapStatDataAllocator stats_allocator(symbol_table); Stats::ThreadLocalStoreImpl stat_store(options.statsOptions(), stats_allocator); Server::InstanceImpl server(options, time_system, local_address, hooks, restarter, stat_store, diff --git a/test/integration/server.h b/test/integration/server.h index 365211908124..d274beef5603 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -75,21 +75,36 @@ class TestScopeWrapper : public Scope { wrapped_scope_->deliverHistogramToSinks(histogram, value); } - Counter& counter(const std::string& name) override { + Counter& counterx(StatName name) override { Thread::LockGuard lock(lock_); - return wrapped_scope_->counter(name); + return wrapped_scope_->counterx(name); } - Gauge& gauge(const std::string& name) override { + Gauge& gaugex(StatName name) override { Thread::LockGuard lock(lock_); - return wrapped_scope_->gauge(name); + return wrapped_scope_->gaugex(name); } - Histogram& histogram(const std::string& name) override { + Histogram& histogramx(StatName name) override { Thread::LockGuard lock(lock_); - return wrapped_scope_->histogram(name); + return wrapped_scope_->histogramx(name); + } + + Counter& counter(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return counterx(storage.statName()); + } + Gauge& gauge(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return gaugex(storage.statName()); + } + Histogram& histogram(const std::string& name) override { + StatNameTempStorage storage(name, symbolTable()); + return histogramx(storage.statName()); } + const SymbolTable& symbolTable() const override { return wrapped_scope_->symbolTable(); } + SymbolTable& symbolTable() override { return wrapped_scope_->symbolTable(); } const StatsOptions& statsOptions() const override { return stats_options_; } private: @@ -106,6 +121,10 @@ class TestIsolatedStoreImpl : public StoreRoot { public: TestIsolatedStoreImpl() : source_(*this) {} // Stats::Scope + Counter& counterx(StatName name) override { + Thread::LockGuard lock(lock_); + return store_.counterx(name); + } Counter& counter(const std::string& name) override { Thread::LockGuard lock(lock_); return store_.counter(name); @@ -115,15 +134,25 @@ class TestIsolatedStoreImpl : public StoreRoot { return ScopePtr{new TestScopeWrapper(lock_, store_.createScope(name))}; } void deliverHistogramToSinks(const Histogram&, uint64_t) override {} + Gauge& gaugex(StatName name) override { + Thread::LockGuard lock(lock_); + return store_.gaugex(name); + } Gauge& gauge(const std::string& name) override { Thread::LockGuard lock(lock_); return store_.gauge(name); } + Histogram& histogramx(StatName name) override { + Thread::LockGuard lock(lock_); + return store_.histogramx(name); + } Histogram& histogram(const std::string& name) override { Thread::LockGuard lock(lock_); return store_.histogram(name); } const StatsOptions& statsOptions() const override { return stats_options_; } + const SymbolTable& symbolTable() const override { return store_.symbolTable(); } + SymbolTable& symbolTable() override { return store_.symbolTable(); } // Stats::Store std::vector counters() const override { diff --git a/test/integration/utility.h b/test/integration/utility.h index 58ec69b6a80e..eded6edc599b 100644 --- a/test/integration/utility.h +++ b/test/integration/utility.h @@ -99,8 +99,8 @@ class RawConnectionDriver { Network::ConnectionEvent last_connection_event_; }; - Api::ApiPtr api_; Stats::IsolatedStoreImpl stats_store_; + Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; std::unique_ptr callbacks_; Network::ClientConnectionPtr client_; diff --git a/test/mocks/server/mocks.cc b/test/mocks/server/mocks.cc index af2b5f299799..7c3e8e5bc436 100644 --- a/test/mocks/server/mocks.cc +++ b/test/mocks/server/mocks.cc @@ -71,7 +71,7 @@ MockGuardDog::MockGuardDog() : watch_dog_(new NiceMock()) { } MockGuardDog::~MockGuardDog() = default; -MockHotRestart::MockHotRestart() { +MockHotRestart::MockHotRestart() : stats_allocator_(symbol_table_.get()) { ON_CALL(*this, logLock()).WillByDefault(ReturnRef(log_lock_)); ON_CALL(*this, accessLogLock()).WillByDefault(ReturnRef(access_log_lock_)); ON_CALL(*this, statsAllocator()).WillByDefault(ReturnRef(stats_allocator_)); diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index 7c57b6e0da8e..da484c4c2bac 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -23,6 +23,7 @@ #include "common/http/context_impl.h" #include "common/secret/secret_manager_impl.h" +#include "common/stats/fake_symbol_table_impl.h" #include "extensions/transport_sockets/tls/context_manager_impl.h" @@ -206,6 +207,7 @@ class MockHotRestart : public HotRestart { MOCK_METHOD0(statsAllocator, Stats::StatDataAllocator&()); private: + Test::Global symbol_table_; Thread::MutexBasicLockable log_lock_; Thread::MutexBasicLockable access_log_lock_; Stats::HeapStatDataAllocator stats_allocator_; @@ -355,6 +357,7 @@ class MockInstance : public Instance { MOCK_METHOD0(localInfo, const LocalInfo::LocalInfo&()); MOCK_CONST_METHOD0(statsFlushInterval, std::chrono::milliseconds()); + Stats::SymbolTable& symbolTable() override { return stats_store_.symbolTable(); } TimeSource& timeSource() override { return time_system_; } std::unique_ptr secret_manager_; @@ -442,14 +445,13 @@ class MockFactoryContext : public virtual FactoryContext { testing::NiceMock local_info_; testing::NiceMock random_; testing::NiceMock runtime_loader_; - Stats::IsolatedStoreImpl scope_; + testing::NiceMock scope_; testing::NiceMock thread_local_; Singleton::ManagerPtr singleton_manager_; testing::NiceMock admin_; Stats::IsolatedStoreImpl listener_scope_; Event::GlobalTimeSystem time_system_; testing::NiceMock overload_manager_; - Tracing::HttpNullTracer null_tracer_; Http::ContextImpl http_context_; testing::NiceMock api_; }; diff --git a/test/mocks/stats/BUILD b/test/mocks/stats/BUILD index 73cda48a2b41..711c90c01fc2 100644 --- a/test/mocks/stats/BUILD +++ b/test/mocks/stats/BUILD @@ -17,9 +17,11 @@ envoy_cc_mock( "//include/envoy/stats:timespan", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/upstream:cluster_manager_interface", + "//source/common/stats:fake_symbol_table_lib", "//source/common/stats:histogram_lib", "//source/common/stats:isolated_store_lib", "//source/common/stats:stats_lib", "//test/mocks:common_lib", + "//test/test_common:global_lib", ], ) diff --git a/test/mocks/stats/mocks.cc b/test/mocks/stats/mocks.cc index 5b1dbf98df98..3c36096a6314 100644 --- a/test/mocks/stats/mocks.cc +++ b/test/mocks/stats/mocks.cc @@ -1,5 +1,9 @@ #include "mocks.h" +#include + +#include "common/stats/fake_symbol_table_impl.h" + #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -13,9 +17,27 @@ using testing::ReturnRef; namespace Envoy { namespace Stats { +MockMetric::MockMetric() : name_(*this) {} +MockMetric::~MockMetric() {} + +MockMetric::MetricName::~MetricName() { + if (stat_name_storage_ != nullptr) { + stat_name_storage_->free(*mock_metric_.symbol_table_); + } +} + +void MockMetric::setTagExtractedName(absl::string_view name) { + tag_extracted_name_ = std::string(name); + tag_extracted_stat_name_ = + std::make_unique(tagExtractedName(), *symbol_table_); +} + +void MockMetric::MetricName::MetricName::operator=(absl::string_view name) { + name_ = std::string(name); + stat_name_storage_ = std::make_unique(name, mock_metric_.symbolTable()); +} + MockCounter::MockCounter() { - ON_CALL(*this, tagExtractedName()).WillByDefault(ReturnRef(name_)); - ON_CALL(*this, tags()).WillByDefault(ReturnRef(tags_)); ON_CALL(*this, used()).WillByDefault(ReturnPointee(&used_)); ON_CALL(*this, value()).WillByDefault(ReturnPointee(&value_)); ON_CALL(*this, latch()).WillByDefault(ReturnPointee(&latch_)); @@ -23,8 +45,6 @@ MockCounter::MockCounter() { MockCounter::~MockCounter() {} MockGauge::MockGauge() { - ON_CALL(*this, tagExtractedName()).WillByDefault(ReturnRef(name_)); - ON_CALL(*this, tags()).WillByDefault(ReturnRef(tags_)); ON_CALL(*this, used()).WillByDefault(ReturnPointee(&used_)); ON_CALL(*this, value()).WillByDefault(ReturnPointee(&value_)); } @@ -36,10 +56,7 @@ MockHistogram::MockHistogram() { store_->deliverHistogramToSinks(*this, value); } })); - ON_CALL(*this, tagExtractedName()).WillByDefault(ReturnRef(name_)); - ON_CALL(*this, tags()).WillByDefault(ReturnRef(tags_)); } - MockHistogram::~MockHistogram() {} MockParentHistogram::MockParentHistogram() { @@ -48,13 +65,10 @@ MockParentHistogram::MockParentHistogram() { store_->deliverHistogramToSinks(*this, value); } })); - ON_CALL(*this, tagExtractedName()).WillByDefault(ReturnRef(name_)); - ON_CALL(*this, tags()).WillByDefault(ReturnRef(tags_)); ON_CALL(*this, intervalStatistics()).WillByDefault(ReturnRef(*histogram_stats_)); ON_CALL(*this, cumulativeStatistics()).WillByDefault(ReturnRef(*histogram_stats_)); ON_CALL(*this, used()).WillByDefault(ReturnPointee(&used_)); } - MockParentHistogram::~MockParentHistogram() {} MockSource::MockSource() { @@ -71,7 +85,7 @@ MockSink::~MockSink() {} MockStore::MockStore() { ON_CALL(*this, counter(_)).WillByDefault(ReturnRef(counter_)); ON_CALL(*this, histogram(_)).WillByDefault(Invoke([this](const std::string& name) -> Histogram& { - auto* histogram = new NiceMock; + auto* histogram = new NiceMock(); // symbol_table_); histogram->name_ = name; histogram->store_ = this; histograms_.emplace_back(histogram); @@ -81,8 +95,9 @@ MockStore::MockStore() { } MockStore::~MockStore() {} -MockIsolatedStatsStore::MockIsolatedStatsStore() {} -MockIsolatedStatsStore::~MockIsolatedStatsStore() {} +MockIsolatedStatsStore::MockIsolatedStatsStore() + : IsolatedStoreImpl(Test::Global::get()) {} +MockIsolatedStatsStore::~MockIsolatedStatsStore() { IsolatedStoreImpl::clear(); } } // namespace Stats } // namespace Envoy diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index c079e785be16..c62da6361f64 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -14,29 +14,72 @@ #include "envoy/thread_local/thread_local.h" #include "envoy/upstream/cluster_manager.h" +#include "common/stats/fake_symbol_table_impl.h" #include "common/stats/histogram_impl.h" #include "common/stats/isolated_store_impl.h" +#include "test/test_common/global.h" + #include "gmock/gmock.h" namespace Envoy { namespace Stats { -class MockCounter : public Counter { +class MockMetric : public virtual Metric { public: - MockCounter(); - ~MockCounter(); + MockMetric(); + ~MockMetric(); + + // This bit of C++ subterfuge allows us to support the wealth of tests that + // do metric->name_ = "foo" even though names are more complex now. Note + // that the statName is only populated if there is a symbol table. + class MetricName { + public: + explicit MetricName(MockMetric& mock_metric) : mock_metric_(mock_metric) {} + ~MetricName(); + + void operator=(absl::string_view str); + + std::string name() const { return name_; } + StatName statName() const { return stat_name_storage_->statName(); } + + private: + MockMetric& mock_metric_; + std::string name_; + std::unique_ptr stat_name_storage_; + }; + + SymbolTable& symbolTable() override { return symbol_table_.get(); } + const SymbolTable& symbolTable() const override { return symbol_table_.get(); } // 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_; }; - const char* nameCStr() const override { return name_.c_str(); }; + std::string name() const override { return name_.name(); } + StatName statName() const override { return name_.statName(); } + std::vector tags() const override { return tags_; } + void setTagExtractedName(absl::string_view name); + std::string tagExtractedName() const override { + return tag_extracted_name_.empty() ? name() : tag_extracted_name_; + } + StatName tagExtractedStatName() const override { return tag_extracted_stat_name_->statName(); } + + Test::Global symbol_table_; // Must outlive name_. + MetricName name_; + std::vector tags_; + +private: + std::string tag_extracted_name_; + std::unique_ptr tag_extracted_stat_name_; +}; + +class MockCounter : public Counter, public MockMetric { +public: + MockCounter(); + ~MockCounter(); MOCK_METHOD1(add, void(uint64_t amount)); MOCK_METHOD0(inc, void()); MOCK_METHOD0(latch, uint64_t()); - MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); - MOCK_CONST_METHOD0(tags, const std::vector&()); MOCK_METHOD0(reset, void()); MOCK_CONST_METHOD0(used, bool()); MOCK_CONST_METHOD0(value, uint64_t()); @@ -44,25 +87,16 @@ class MockCounter : public Counter { bool used_; uint64_t value_; uint64_t latch_; - std::string name_; - std::vector tags_; }; -class MockGauge : public Gauge { +class MockGauge : public Gauge, public MockMetric { public: MockGauge(); ~MockGauge(); - // 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_; }; - const char* nameCStr() const override { return name_.c_str(); }; - MOCK_METHOD1(add, void(uint64_t amount)); MOCK_METHOD0(dec, void()); MOCK_METHOD0(inc, void()); - 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_CONST_METHOD0(used, bool()); @@ -70,52 +104,33 @@ class MockGauge : public Gauge { bool used_; uint64_t value_; - std::string name_; - std::vector tags_; }; -class MockHistogram : public Histogram { +class MockHistogram : public Histogram, public MockMetric { public: MockHistogram(); ~MockHistogram(); - // 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_; }; - const char* nameCStr() const override { return name_.c_str(); }; - - MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); - MOCK_CONST_METHOD0(tags, const std::vector&()); MOCK_METHOD1(recordValue, void(uint64_t value)); MOCK_CONST_METHOD0(used, bool()); - std::string name_; - std::vector tags_; Store* store_; }; -class MockParentHistogram : public ParentHistogram { +class MockParentHistogram : public ParentHistogram, public MockMetric { public: MockParentHistogram(); ~MockParentHistogram(); - // 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_; }; - const char* nameCStr() const override { return name_.c_str(); }; void merge() override {} const std::string quantileSummary() const override { return ""; }; const std::string bucketSummary() const override { return ""; }; MOCK_CONST_METHOD0(used, bool()); - MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); - MOCK_CONST_METHOD0(tags, const std::vector&()); MOCK_METHOD1(recordValue, void(uint64_t value)); MOCK_CONST_METHOD0(cumulativeStatistics, const HistogramStatistics&()); MOCK_CONST_METHOD0(intervalStatistics, const HistogramStatistics&()); - std::string name_; - std::vector tags_; bool used_; Store* store_; std::shared_ptr histogram_stats_ = @@ -163,6 +178,14 @@ class MockStore : public Store { MOCK_CONST_METHOD0(histograms, std::vector()); MOCK_CONST_METHOD0(statsOptions, const StatsOptions&()); + Counter& counterx(StatName name) override { return counter(symbol_table_->toString(name)); } + Gauge& gaugex(StatName name) override { return gauge(symbol_table_->toString(name)); } + Histogram& histogramx(StatName name) override { return histogram(symbol_table_->toString(name)); } + + SymbolTable& symbolTable() override { return symbol_table_.get(); } + const SymbolTable& symbolTable() const override { return symbol_table_.get(); } + + Test::Global symbol_table_; testing::NiceMock counter_; std::vector> histograms_; StatsOptionsImpl stats_options_; @@ -172,7 +195,8 @@ class MockStore : public Store { * With IsolatedStoreImpl it's hard to test timing stats. * MockIsolatedStatsStore mocks only deliverHistogramToSinks for better testing. */ -class MockIsolatedStatsStore : public IsolatedStoreImpl { +class MockIsolatedStatsStore : private Test::Global, + public IsolatedStoreImpl { public: MockIsolatedStatsStore(); ~MockIsolatedStatsStore(); diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index e885c9c310b2..d546111bdead 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -96,7 +96,7 @@ class MockHostDescription : public HostDescription { testing::NiceMock outlier_detector_; testing::NiceMock health_checker_; testing::NiceMock cluster_; - Stats::IsolatedStoreImpl stats_store_; + testing::NiceMock stats_store_; HostStats stats_{ALL_HOST_STATS(POOL_COUNTER(stats_store_), POOL_GAUGE(stats_store_))}; }; @@ -166,7 +166,7 @@ class MockHost : public Host { testing::NiceMock cluster_; testing::NiceMock outlier_detector_; - Stats::IsolatedStoreImpl stats_store_; + NiceMock stats_store_; HostStats stats_{ALL_HOST_STATS(POOL_COUNTER(stats_store_), POOL_GAUGE(stats_store_))}; }; diff --git a/test/server/config_validation/cluster_manager_test.cc b/test/server/config_validation/cluster_manager_test.cc index a9caa4e415a3..1b86396c8991 100644 --- a/test/server/config_validation/cluster_manager_test.cc +++ b/test/server/config_validation/cluster_manager_test.cc @@ -48,6 +48,7 @@ TEST(ValidationClusterManagerTest, MockedMethods) { local_info, secret_manager, *api, http_context, log_manager, singleton_manager, time_system); const envoy::config::bootstrap::v2::Bootstrap bootstrap; + Stats::SymbolTableImpl symbol_table; ClusterManagerPtr cluster_manager = factory.clusterManagerFromProto(bootstrap); EXPECT_EQ(nullptr, cluster_manager->httpConnPoolForCluster("cluster", ResourcePriority::Default, Http::Protocol::Http11, nullptr)); diff --git a/test/server/hot_restart_impl_test.cc b/test/server/hot_restart_impl_test.cc index cc77baf4f517..46f68cb85953 100644 --- a/test/server/hot_restart_impl_test.cc +++ b/test/server/hot_restart_impl_test.cc @@ -41,10 +41,11 @@ class HotRestartImplTest : public testing::Test { EXPECT_CALL(options_, statsOptions()).WillRepeatedly(ReturnRef(stats_options_)); // Test we match the correct stat with empty-slots before, after, or both. - hot_restart_ = std::make_unique(options_); + hot_restart_ = std::make_unique(options_, symbol_table_); hot_restart_->drainParentListeners(); } + Stats::SymbolTableImpl symbol_table_; Api::MockOsSysCalls os_sys_calls_; TestThreadsafeSingletonInjector os_calls{&os_sys_calls_}; NiceMock options_; @@ -131,7 +132,7 @@ TEST_F(HotRestartImplTest, crossAlloc) { EXPECT_CALL(os_sys_calls_, mmap(_, _, _, _, _, _)) .WillOnce(Return(Api::SysCallPtrResult{buffer_.data(), 0})); EXPECT_CALL(os_sys_calls_, bind(_, _, _)); - HotRestartImpl hot_restart2(options_); + HotRestartImpl hot_restart2(options_, symbol_table_); Stats::RawStatData* stat1_prime = hot_restart2.statsAllocator().alloc("stat1"); Stats::RawStatData* stat3_prime = hot_restart2.statsAllocator().alloc("stat3"); Stats::RawStatData* stat5_prime = hot_restart2.statsAllocator().alloc("stat5"); diff --git a/test/server/http/admin_test.cc b/test/server/http/admin_test.cc index ea442401bec5..3cc55aa02759 100644 --- a/test/server/http/admin_test.cc +++ b/test/server/http/admin_test.cc @@ -50,7 +50,7 @@ namespace Server { class AdminStatsTest : public testing::TestWithParam { public: - AdminStatsTest() : alloc_(options_) { + AdminStatsTest() : alloc_(options_, symbol_table_) { store_ = std::make_unique(options_, alloc_); store_->addSink(sink_); } @@ -63,6 +63,7 @@ class AdminStatsTest : public testing::TestWithParam main_thread_dispatcher_; NiceMock tls_; Stats::StatsOptionsImpl options_; @@ -1225,20 +1226,23 @@ class HistogramWrapper { class PrometheusStatsFormatterTest : public testing::Test { protected: + PrometheusStatsFormatterTest() : alloc_(symbol_table_) {} + void addCounter(const std::string& name, std::vector cluster_tags) { - std::string tname = std::string(name); - counters_.push_back(alloc_.makeCounter(name, std::move(tname), std::move(cluster_tags))); + Stats::StatNameTempStorage storage(name, symbol_table_); + counters_.push_back(alloc_.makeCounter(storage.statName(), name, cluster_tags)); } void addGauge(const std::string& name, std::vector cluster_tags) { - std::string tname = std::string(name); - gauges_.push_back(alloc_.makeGauge(name, std::move(tname), std::move(cluster_tags))); + Stats::StatNameTempStorage storage(name, symbol_table_); + gauges_.push_back(alloc_.makeGauge(storage.statName(), name, cluster_tags)); } void addHistogram(const Stats::ParentHistogramSharedPtr histogram) { histograms_.push_back(histogram); } + Stats::SymbolTableImpl symbol_table_; Stats::StatsOptionsImpl stats_options_; Stats::HeapStatDataAllocator alloc_; std::vector counters_; diff --git a/test/test_common/BUILD b/test/test_common/BUILD index b6be6366c7ee..b81416a700cc 100644 --- a/test/test_common/BUILD +++ b/test/test_common/BUILD @@ -107,6 +107,7 @@ envoy_cc_test_library( "//source/common/network:address_lib", "//source/common/network:utility_lib", "//source/common/protobuf:utility_lib", + "//source/common/stats:fake_symbol_table_lib", "//source/common/stats:stats_lib", "//source/common/stats:stats_options_lib", "//test/mocks/stats:stats_mocks", diff --git a/test/test_common/global.h b/test/test_common/global.h index fc788a0830d8..3628180bd6fc 100644 --- a/test/test_common/global.h +++ b/test/test_common/global.h @@ -117,6 +117,7 @@ template class Global { public: Global() : singleton_(Globals::get()) {} Type& get() { return singleton_->ref(); } + const Type& get() const { return singleton_->ref(); } Type* operator->() { return singleton_->ptr(); } Type& operator*() { return singleton_->ref(); } diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index c23d06530329..44ebfb7fcf23 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -141,6 +141,10 @@ Stats::GaugeSharedPtr TestUtility::findGauge(Stats::Store& store, const std::str return findByName(store.gauges(), name); } +Stats::HistogramSharedPtr TestUtility::findHistogram(Stats::Store& store, const std::string& name) { + return findByName(store.histograms(), name); +} + std::list TestUtility::makeDnsResponse(const std::list& addresses) { std::list ret; @@ -362,8 +366,9 @@ bool TestHeaderMapImpl::has(const LowerCaseString& key) { return get(key) != nul namespace Stats { -MockedTestAllocator::MockedTestAllocator(const StatsOptions& stats_options) - : TestAllocator(stats_options) { +MockedTestAllocator::MockedTestAllocator(const StatsOptions& stats_options, + SymbolTable& symbol_table) + : TestAllocator(stats_options, symbol_table) { ON_CALL(*this, alloc(_)).WillByDefault(Invoke([this](absl::string_view name) -> RawStatData* { return TestAllocator::alloc(name); })); diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 43b7ed834801..dbd0055c7de1 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -21,6 +21,7 @@ #include "common/common/thread.h" #include "common/http/header_map_impl.h" #include "common/protobuf/utility.h" +#include "common/stats/fake_symbol_table_impl.h" #include "common/stats/raw_stat_data.h" #include "test/test_common/printers.h" @@ -164,6 +165,14 @@ class TestUtility { */ static Stats::GaugeSharedPtr findGauge(Stats::Store& store, const std::string& name); + /** + * Find a histogram in a stats store. + * @param store supplies the stats store. + * @param name supplies the name to search for. + * @return Stats::HistogramSharedPtr the gauge or nullptr if there is none. + */ + static Stats::HistogramSharedPtr findHistogram(Stats::Store& store, const std::string& name); + /** * Convert a string list of IP addresses into a list of network addresses usable for DNS * response testing. @@ -474,14 +483,15 @@ class TestAllocator : public RawStatDataAllocator { } }; - explicit TestAllocator(const StatsOptions& stats_options) - : RawStatDataAllocator(mutex_, hash_set_, stats_options), + TestAllocator(const StatsOptions& stats_options, SymbolTable& symbol_table) + : RawStatDataAllocator(mutex_, hash_set_, stats_options, symbol_table), block_memory_(std::make_unique( RawStatDataSet::numBytes(block_hash_options_, stats_options))), hash_set_(block_hash_options_, true /* init */, block_memory_.get(), stats_options) {} ~TestAllocator() { EXPECT_EQ(0, hash_set_.size()); } private: + FakeSymbolTableImpl symbol_table_; Thread::MutexBasicLockable mutex_; TestBlockMemoryHashSetOptions block_hash_options_; std::unique_ptr block_memory_; @@ -490,7 +500,7 @@ class TestAllocator : public RawStatDataAllocator { class MockedTestAllocator : public TestAllocator { public: - MockedTestAllocator(const StatsOptions& stats_options); + MockedTestAllocator(const StatsOptions& stats_options, SymbolTable& symbol_table); virtual ~MockedTestAllocator(); MOCK_METHOD1(alloc, RawStatData*(absl::string_view name)); From 9e47dfe2ed9b07d19f76f3fa0a4f5967ed706c87 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 4 Mar 2019 15:56:37 -0500 Subject: [PATCH 02/39] Rename counterx to counterFromStatName; ditto for gaugex, histogramx. Signed-off-by: Joshua Marantz --- include/envoy/stats/scope.h | 6 ++-- source/common/stats/isolated_store_impl.h | 12 ++++---- source/common/stats/scope_prefixer.cc | 12 ++++---- source/common/stats/scope_prefixer.h | 12 ++++---- source/common/stats/thread_local_store.cc | 18 +++++------ source/common/stats/thread_local_store.h | 24 +++++++++------ .../stats/thread_local_store_speed_test.cc | 2 +- test/integration/server.h | 30 +++++++++---------- test/mocks/stats/mocks.h | 10 +++++-- 9 files changed, 68 insertions(+), 58 deletions(-) diff --git a/include/envoy/stats/scope.h b/include/envoy/stats/scope.h index 70be7f9222a0..824f40e029fd 100644 --- a/include/envoy/stats/scope.h +++ b/include/envoy/stats/scope.h @@ -44,19 +44,19 @@ class Scope { /** * @return a counter within the scope's namespace. */ - virtual Counter& counterx(StatName name) PURE; + virtual Counter& counterFromStatName(StatName name) PURE; virtual Counter& counter(const std::string& name) PURE; /** * @return a gauge within the scope's namespace. */ - virtual Gauge& gaugex(StatName name) PURE; + virtual Gauge& gaugeFromStatName(StatName name) PURE; virtual Gauge& gauge(const std::string& name) PURE; /** * @return a histogram within the scope's namespace with a particular value type. */ - virtual Histogram& histogramx(StatName name) PURE; + virtual Histogram& histogramFromStatName(StatName name) PURE; virtual Histogram& histogram(const std::string& name) PURE; /** diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index 459cd76dcdcf..41f87ca66c0d 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -64,11 +64,11 @@ class IsolatedStoreImpl : public Store { explicit IsolatedStoreImpl(SymbolTable& symbol_table); // Stats::Scope - Counter& counterx(StatName name) override { return counters_.get(name); } + Counter& counterFromStatName(StatName name) override { return counters_.get(name); } ScopePtr createScope(const std::string& name) override; void deliverHistogramToSinks(const Histogram&, uint64_t) override {} - Gauge& gaugex(StatName name) override { return gauges_.get(name); } - Histogram& histogramx(StatName name) override { + Gauge& gaugeFromStatName(StatName name) override { return gauges_.get(name); } + Histogram& histogramFromStatName(StatName name) override { Histogram& histogram = histograms_.get(name); return histogram; } @@ -85,15 +85,15 @@ class IsolatedStoreImpl : public Store { Counter& counter(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return counterx(storage.statName()); + return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return gaugex(storage.statName()); + return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return histogramx(storage.statName()); + return histogramFromStatName(storage.statName()); } void clear(); diff --git a/source/common/stats/scope_prefixer.cc b/source/common/stats/scope_prefixer.cc index ddb82340cee9..aa00a3a67d0e 100644 --- a/source/common/stats/scope_prefixer.cc +++ b/source/common/stats/scope_prefixer.cc @@ -34,22 +34,22 @@ ScopePtr ScopePrefixer::createScope(const std::string& name) { *scope_); } -Counter& ScopePrefixer::counterx(StatName name) { +Counter& ScopePrefixer::counterFromStatName(StatName name) { Stats::SymbolTable::StoragePtr stat_name_storage = scope_->symbolTable().join({prefix_.statName(), name}); - return scope_->counterx(StatName(stat_name_storage.get())); + return scope_->counterFromStatName(StatName(stat_name_storage.get())); } -Gauge& ScopePrefixer::gaugex(StatName name) { +Gauge& ScopePrefixer::gaugeFromStatName(StatName name) { Stats::SymbolTable::StoragePtr stat_name_storage = scope_->symbolTable().join({prefix_.statName(), name}); - return scope_->gaugex(StatName(stat_name_storage.get())); + return scope_->gaugeFromStatName(StatName(stat_name_storage.get())); } -Histogram& ScopePrefixer::histogramx(StatName name) { +Histogram& ScopePrefixer::histogramFromStatName(StatName name) { Stats::SymbolTable::StoragePtr stat_name_storage = scope_->symbolTable().join({prefix_.statName(), name}); - return scope_->histogramx(StatName(stat_name_storage.get())); + return scope_->histogramFromStatName(StatName(stat_name_storage.get())); } void ScopePrefixer::deliverHistogramToSinks(const Histogram& histograms, uint64_t val) { diff --git a/source/common/stats/scope_prefixer.h b/source/common/stats/scope_prefixer.h index c8abcc548176..1758a1f6e769 100644 --- a/source/common/stats/scope_prefixer.h +++ b/source/common/stats/scope_prefixer.h @@ -21,22 +21,22 @@ class ScopePrefixer : public Scope { // Scope ScopePtr createScope(const std::string& name) override; - Counter& counterx(StatName name) override; - Gauge& gaugex(StatName name) override; - Histogram& histogramx(StatName name) override; + Counter& counterFromStatName(StatName name) override; + Gauge& gaugeFromStatName(StatName name) override; + Histogram& histogramFromStatName(StatName name) override; void deliverHistogramToSinks(const Histogram& histograms, uint64_t val) override; Counter& counter(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return counterx(storage.statName()); + return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return gaugex(storage.statName()); + return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return histogramx(storage.statName()); + return histogramFromStatName(storage.statName()); } const Stats::StatsOptions& statsOptions() const override { return scope_->statsOptions(); } diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 41fdf0d93fc1..8fb32a1bd547 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -28,7 +28,7 @@ ThreadLocalStoreImpl::ThreadLocalStoreImpl(const StatsOptions& stats_options, tag_producer_(std::make_unique()), stats_matcher_(std::make_unique()), stats_overflow_("stats.overflow", alloc.symbolTable()), - num_last_resort_stats_(default_scope_->counterx(stats_overflow_.statName())), + num_last_resort_stats_(default_scope_->counterFromStatName(stats_overflow_.statName())), heap_allocator_(alloc.symbolTable()), source_(*this), null_counter_(alloc.symbolTable()), null_gauge_(alloc.symbolTable()), null_histogram_(alloc.symbolTable()) {} @@ -330,7 +330,7 @@ StatType& ThreadLocalStoreImpl::ScopeImpl::safeMakeStat( return **central_ref; } -Counter& ThreadLocalStoreImpl::ScopeImpl::counterx(StatName name) { +Counter& ThreadLocalStoreImpl::ScopeImpl::counterFromStatName(StatName name) { // Determine the final name based on the prefix and the passed name. // // Note that we can do map.find(final_name.c_str()), but we cannot do @@ -379,9 +379,9 @@ void ThreadLocalStoreImpl::ScopeImpl::deliverHistogramToSinks(const Histogram& h } } -Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugex(StatName name) { - // See comments in counterx(). There is no super clean way (via templates or otherwise) to - // share this code so I'm leaving it largely duplicated for now. +Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugeFromStatName(StatName name) { + // See comments in counterFromStatName(). There is no super clean way (via templates or otherwise) + // to share this code so I'm leaving it largely duplicated for now. // // Note that we can do map.find(final_name.c_str()), but we cannot do // map[final_name.c_str()] as the char*-keyed maps would then save the pointer to @@ -409,9 +409,9 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugex(StatName name) { tls_cache); } -Histogram& ThreadLocalStoreImpl::ScopeImpl::histogramx(StatName name) { - // See comments in counterx(). There is no super clean way (via templates or otherwise) to - // share this code so I'm leaving it largely duplicated for now. +Histogram& ThreadLocalStoreImpl::ScopeImpl::histogramFromStatName(StatName name) { + // See comments in counterFromStatName(). There is no super clean way (via templates or otherwise) + // to share this code so I'm leaving it largely duplicated for now. // // Note that we can do map.find(final_name.c_str()), but we cannot do // map[final_name.c_str()] as the char*-keyed maps would then save the pointer to @@ -460,7 +460,7 @@ Histogram& ThreadLocalStoreImpl::ScopeImpl::tlsHistogram(StatName name, return parent_.null_histogram_; } - // See comments in counterx() which explains the logic here. + // See comments in counterFromStatName() which explains the logic here. StatMap* tls_cache = nullptr; if (!parent_.shutting_down_ && parent_.tls_) { diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 01b727cc6d61..7af9757ac587 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -143,15 +143,21 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo ~ThreadLocalStoreImpl() override; // Stats::Scope - Counter& counterx(StatName name) override { return default_scope_->counterx(name); } + Counter& counterFromStatName(StatName name) override { + return default_scope_->counterFromStatName(name); + } Counter& counter(const std::string& name) override { return default_scope_->counter(name); } ScopePtr createScope(const std::string& name) override; void deliverHistogramToSinks(const Histogram& histogram, uint64_t value) override { return default_scope_->deliverHistogramToSinks(histogram, value); } - Gauge& gaugex(StatName name) override { return default_scope_->gaugex(name); } + Gauge& gaugeFromStatName(StatName name) override { + return default_scope_->gaugeFromStatName(name); + } Gauge& gauge(const std::string& name) override { return default_scope_->gauge(name); } - Histogram& histogramx(StatName name) override { return default_scope_->histogramx(name); } + Histogram& histogramFromStatName(StatName name) override { + return default_scope_->histogramFromStatName(name); + } Histogram& histogram(const std::string& name) override { return default_scope_->histogram(name); } const SymbolTable& symbolTable() const override { return alloc_.symbolTable(); } SymbolTable& symbolTable() override { return alloc_.symbolTable(); } @@ -200,10 +206,10 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo ~ScopeImpl() override; // Stats::Scope - Counter& counterx(StatName name) override; + Counter& counterFromStatName(StatName name) override; void deliverHistogramToSinks(const Histogram& histogram, uint64_t value) override; - Gauge& gaugex(StatName name) override; - Histogram& histogramx(StatName name) override; + Gauge& gaugeFromStatName(StatName name) override; + Histogram& histogramFromStatName(StatName name) override; Histogram& tlsHistogram(StatName name, ParentHistogramImpl& parent) override; const Stats::StatsOptions& statsOptions() const override { return parent_.statsOptions(); } ScopePtr createScope(const std::string& name) override { @@ -215,15 +221,15 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo Counter& counter(const std::string& name) override { // std::cerr << "counter(" << name << ")" << std::endl; StatNameTempStorage storage(name, symbolTable()); - return counterx(storage.statName()); + return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return gaugex(storage.statName()); + return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return histogramx(storage.statName()); + return histogramFromStatName(storage.statName()); } template diff --git a/test/common/stats/thread_local_store_speed_test.cc b/test/common/stats/thread_local_store_speed_test.cc index 4bbbd6e654aa..5717026c6026 100644 --- a/test/common/stats/thread_local_store_speed_test.cc +++ b/test/common/stats/thread_local_store_speed_test.cc @@ -43,7 +43,7 @@ class ThreadLocalStorePerf { void accessCounters() { for (auto& stat_name_storage : stat_names_) { - store_.counterx(stat_name_storage->statName()); + store_.counterFromStatName(stat_name_storage->statName()); } } diff --git a/test/integration/server.h b/test/integration/server.h index d274beef5603..93cc37b636a0 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -75,32 +75,32 @@ class TestScopeWrapper : public Scope { wrapped_scope_->deliverHistogramToSinks(histogram, value); } - Counter& counterx(StatName name) override { + Counter& counterFromStatName(StatName name) override { Thread::LockGuard lock(lock_); - return wrapped_scope_->counterx(name); + return wrapped_scope_->counterFromStatName(name); } - Gauge& gaugex(StatName name) override { + Gauge& gaugeFromStatName(StatName name) override { Thread::LockGuard lock(lock_); - return wrapped_scope_->gaugex(name); + return wrapped_scope_->gaugeFromStatName(name); } - Histogram& histogramx(StatName name) override { + Histogram& histogramFromStatName(StatName name) override { Thread::LockGuard lock(lock_); - return wrapped_scope_->histogramx(name); + return wrapped_scope_->histogramFromStatName(name); } Counter& counter(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return counterx(storage.statName()); + return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return gaugex(storage.statName()); + return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { StatNameTempStorage storage(name, symbolTable()); - return histogramx(storage.statName()); + return histogramFromStatName(storage.statName()); } const SymbolTable& symbolTable() const override { return wrapped_scope_->symbolTable(); } @@ -121,9 +121,9 @@ class TestIsolatedStoreImpl : public StoreRoot { public: TestIsolatedStoreImpl() : source_(*this) {} // Stats::Scope - Counter& counterx(StatName name) override { + Counter& counterFromStatName(StatName name) override { Thread::LockGuard lock(lock_); - return store_.counterx(name); + return store_.counterFromStatName(name); } Counter& counter(const std::string& name) override { Thread::LockGuard lock(lock_); @@ -134,17 +134,17 @@ class TestIsolatedStoreImpl : public StoreRoot { return ScopePtr{new TestScopeWrapper(lock_, store_.createScope(name))}; } void deliverHistogramToSinks(const Histogram&, uint64_t) override {} - Gauge& gaugex(StatName name) override { + Gauge& gaugeFromStatName(StatName name) override { Thread::LockGuard lock(lock_); - return store_.gaugex(name); + return store_.gaugeFromStatName(name); } Gauge& gauge(const std::string& name) override { Thread::LockGuard lock(lock_); return store_.gauge(name); } - Histogram& histogramx(StatName name) override { + Histogram& histogramFromStatName(StatName name) override { Thread::LockGuard lock(lock_); - return store_.histogramx(name); + return store_.histogramFromStatName(name); } Histogram& histogram(const std::string& name) override { Thread::LockGuard lock(lock_); diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index c62da6361f64..59db3d4467cf 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -178,9 +178,13 @@ class MockStore : public Store { MOCK_CONST_METHOD0(histograms, std::vector()); MOCK_CONST_METHOD0(statsOptions, const StatsOptions&()); - Counter& counterx(StatName name) override { return counter(symbol_table_->toString(name)); } - Gauge& gaugex(StatName name) override { return gauge(symbol_table_->toString(name)); } - Histogram& histogramx(StatName name) override { return histogram(symbol_table_->toString(name)); } + Counter& counterFromStatName(StatName name) override { + return counter(symbol_table_->toString(name)); + } + Gauge& gaugeFromStatName(StatName name) override { return gauge(symbol_table_->toString(name)); } + Histogram& histogramFromStatName(StatName name) override { + return histogram(symbol_table_->toString(name)); + } SymbolTable& symbolTable() override { return symbol_table_.get(); } const SymbolTable& symbolTable() const override { return symbol_table_.get(); } From f384eeb0807ce8a1bf6c428f0768872ef7db0d72 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Tue, 5 Mar 2019 10:30:35 -0500 Subject: [PATCH 03/39] avoid extra copy when encoding strings into FakeSymbolTableImpl. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 2 ++ source/common/stats/fake_symbol_table_impl.h | 17 +++++++++++++++++ source/common/stats/symbol_table_impl.cc | 12 ++++++++---- source/common/stats/symbol_table_impl.h | 2 ++ test/common/http/codes_speed_test.cc | 3 ++- .../stats/thread_local_store_speed_test.cc | 2 +- 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 7b88c6d4665a..8cc2b298da64 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -159,6 +159,8 @@ class SymbolTable { * @param stat_name the stat name. */ virtual void incRefCount(const StatName& stat_name) PURE; + + virtual StoragePtr copyToBytes(absl::string_view name) PURE; }; using SharedSymbolTable = std::shared_ptr; diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index 6c7e2f37bdf8..c53a4096db3c 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -75,7 +75,24 @@ class FakeSymbolTableImpl : public SymbolTable { void debugPrint() const override {} #endif + StoragePtr copyToBytes(absl::string_view name) override { + auto bytes = std::make_unique(name.size() + StatNameSizeEncodingBytes); + uint8_t* buffer = saveLengthToBytesReturningNext(name.size(), bytes.get()); + memcpy(buffer, name.data(), name.size()); + return bytes; + } + private: + // Saves the specified length into the byte array, returning the next byte. + // There is no guarantee that bytes will be aligned, so we can't cast to a + // uint16_t* and assign, but must individually copy the bytes. + static uint8_t* saveLengthToBytesReturningNext(uint64_t length, uint8_t* bytes) { + ASSERT(length < StatNameMaxSize); + *bytes++ = length & 0xff; + *bytes++ = length >> 8; + return bytes; + } + SymbolEncoding encodeHelper(absl::string_view name) const { SymbolEncoding encoding; encoding.addStringForFakeSymbolTable(name); diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index 4608886511fb..85624e89c827 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -296,12 +296,16 @@ void SymbolTableImpl::debugPrint() const { } #endif -StatNameStorage::StatNameStorage(absl::string_view name, SymbolTable& table) { - SymbolEncoding encoding = table.encode(name); - bytes_ = std::make_unique(encoding.bytesRequired()); - encoding.moveToStorage(bytes_.get()); +SymbolTable::StoragePtr SymbolTableImpl::copyToBytes(absl::string_view name) { + SymbolEncoding encoding = encode(name); + auto bytes = std::make_unique(encoding.bytesRequired()); + encoding.moveToStorage(bytes.get()); + return bytes; } +StatNameStorage::StatNameStorage(absl::string_view name, SymbolTable& table) + : bytes_(table.copyToBytes(name)) {} + StatNameStorage::StatNameStorage(StatName src, SymbolTable& table) { uint64_t size = src.size(); bytes_ = std::make_unique(size); diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index c65c41c033df..5388eb570eaa 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -146,6 +146,8 @@ class SymbolTableImpl : public SymbolTable { void debugPrint() const override; #endif + StoragePtr copyToBytes(absl::string_view name) override; + private: friend class StatName; friend class StatNameTest; diff --git a/test/common/http/codes_speed_test.cc b/test/common/http/codes_speed_test.cc index b85f8db68c73..8ec80973600f 100644 --- a/test/common/http/codes_speed_test.cc +++ b/test/common/http/codes_speed_test.cc @@ -10,6 +10,7 @@ #include "common/common/empty_string.h" #include "common/http/codes.h" +#include "common/stats/fake_symbol_table_impl.h" #include "common/stats/isolated_store_impl.h" #include "benchmark/benchmark.h" @@ -53,7 +54,7 @@ class CodeUtilitySpeedTest { code_stats_.chargeResponseTiming(info); } - Stats::SymbolTableImpl symbol_table_; + Stats::FakeSymbolTableImpl symbol_table_; Stats::IsolatedStoreImpl global_store_; Stats::IsolatedStoreImpl cluster_scope_; Http::CodeStatsImpl code_stats_; diff --git a/test/common/stats/thread_local_store_speed_test.cc b/test/common/stats/thread_local_store_speed_test.cc index 5717026c6026..36a05466c643 100644 --- a/test/common/stats/thread_local_store_speed_test.cc +++ b/test/common/stats/thread_local_store_speed_test.cc @@ -54,7 +54,7 @@ class ThreadLocalStorePerf { } private: - Stats::SymbolTableImpl symbol_table_; + Stats::FakeSymbolTableImpl symbol_table_; Event::SimulatedTimeSystem time_system_; Stats::StatsOptionsImpl options_; Stats::HeapStatDataAllocator heap_alloc_; From f13821211b7879d0b48b46a7d5a86304dc0e1d84 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 11 Mar 2019 12:57:33 -0400 Subject: [PATCH 04/39] Use FakeSymbolTable more consistently. Start updating stats.md for symbol tables. Signed-off-by: Joshua Marantz --- source/docs/stats.md | 32 ++++++++++++++++++- .../http/conn_manager_impl_fuzz_test.cc | 2 +- test/common/stats/BUILD | 1 + test/common/stats/heap_stat_data_test.cc | 5 +-- test/common/stats/raw_stat_data_test.cc | 2 +- test/common/stats/thread_local_store_test.cc | 6 ++-- .../common/statsd/udp_statsd_test.cc | 4 --- test/integration/server.cc | 2 +- .../config_validation/cluster_manager_test.cc | 2 +- test/server/hot_restart_impl_test.cc | 2 +- test/server/http/admin_test.cc | 4 +-- 11 files changed, 45 insertions(+), 17 deletions(-) diff --git a/source/docs/stats.md b/source/docs/stats.md index 357efec2c6e0..abe953c9f895 100644 --- a/source/docs/stats.md +++ b/source/docs/stats.md @@ -101,7 +101,7 @@ followed. Stat names are replicated in several places in various forms. - * Held fully elaborated next to the values, in `RawStatData` and `HeapStatData` + * Held with the stat values, in `RawStatData` and `HeapStatData` * In [MetricImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/metric_impl.h) in a transformed state, with tags extracted into vectors of name/value strings. * In static strings across the codebase where stats are referenced @@ -121,6 +121,36 @@ the key. If the `.find` fails, the actual stat must be constructed first, and then inserted into the map using its key storage. This strategy saves duplication of the keys, but costs an extra map lookup on each miss. +### Naming Representation + +When stored as flat strings, stat names can dominate Envoy memory usage when +there are a large number of clusters. Stat names typically combine a small +number of keywords, cluster names, host names, and response codes, separated by +".". For example "CLUSTER.upstream_cx_connect_attempts_exceeded". There may be +thousands of clusters, and ~100 stats per cluster. Thus, the number of +combinations can be large. It is significantly more efficient to symbolize each +"."-delimited token and represent stats as arrays of symbols. + +The transformation between flattened string and symbolized form is CPU-intensive +at scale. It requires parsing, encoding, and lookups in a shared map, which must +be mutex-protected. To avoid adding latency and CPU overhead while serving +requests, the tokens can be symbolized and saved in data structures. This can +occur on startup or when new hosts or clusters are configured dynamically. Thus +users of stats that are allocated dynamically per cluster, host, etc, must +explicitly store partial stat-names their class instances, which can be composed +dynamically at runtime in order to fully elaborate counters, gauges, etc, +without taking symbol-table locks. + +### Implementation + +The SymbolTable +[SymbolTable](https://github.com/envoyproxy/envoy/blob/master/include/envoy/stats/symbol.h) +abstraction has, temporarily, two implementions, +[FakeSymbolTableImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/fake_symbol_table_impl.h) +and +[SymbolTableImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/symbol_table_impl.h). +The fake will eventually be deleted. + ## Tags and Tag Extraction TBD diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 5540b70e3894..79308034c383 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -384,7 +384,7 @@ DEFINE_PROTO_FUZZER(const test::common::http::ConnManagerImplTestCase& input) { FuzzConfig config; NiceMock drain_close; NiceMock random; - Stats::SymbolTableImpl symbol_table; + Stats::FakeSymbolTableImpl symbol_table; Http::ContextImpl http_context; NiceMock runtime; NiceMock local_info; diff --git a/test/common/stats/BUILD b/test/common/stats/BUILD index 4ceea90e1b3c..3e8184772c91 100644 --- a/test/common/stats/BUILD +++ b/test/common/stats/BUILD @@ -14,6 +14,7 @@ envoy_cc_test( name = "heap_stat_data_test", srcs = ["heap_stat_data_test.cc"], deps = [ + "//source/common/stats:fake_symbol_table_lib", "//source/common/stats:heap_stat_data_lib", "//source/common/stats:stats_options_lib", "//test/test_common:logging_lib", diff --git a/test/common/stats/heap_stat_data_test.cc b/test/common/stats/heap_stat_data_test.cc index 8f6dad629fe7..3c56edfcbe35 100644 --- a/test/common/stats/heap_stat_data_test.cc +++ b/test/common/stats/heap_stat_data_test.cc @@ -1,8 +1,9 @@ #include #include "common/stats/heap_stat_data.h" + +#include "common/stats/fake_symbol_table_impl.h" #include "common/stats/stats_options_impl.h" -#include "common/stats/symbol_table_impl.h" #include "test/test_common/logging.h" @@ -34,7 +35,7 @@ class HeapStatDataTest : public testing::Test { EXPECT_EQ(0, symbol_table_.numSymbols()); } - SymbolTableImpl symbol_table_; + FakeSymbolTableImpl symbol_table_; HeapStatDataAllocator alloc_; std::vector stat_name_storage_; }; diff --git a/test/common/stats/raw_stat_data_test.cc b/test/common/stats/raw_stat_data_test.cc index f06072922283..e1e0f1365b4a 100644 --- a/test/common/stats/raw_stat_data_test.cc +++ b/test/common/stats/raw_stat_data_test.cc @@ -17,7 +17,7 @@ class RawStatDataTest : public testing::Test { RawStatDataTest() : allocator_(stats_options_, symbol_table_) {} StatsOptionsImpl stats_options_; - SymbolTableImpl symbol_table_; + FakeSymbolTableImpl symbol_table_; TestAllocator allocator_; // This is RawStatDataAllocator with some size settings. }; diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index 664b4a79e37e..d7bdd1426bac 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -45,7 +45,7 @@ class StatsThreadLocalStoreTest : public testing::Test { store_->addSink(sink_); } - Stats::SymbolTableImpl symbol_table_; + Stats::FakeSymbolTableImpl symbol_table_; NiceMock main_thread_dispatcher_; NiceMock tls_; StatsOptionsImpl options_; @@ -169,7 +169,7 @@ class HistogramTest : public testing::Test { MOCK_METHOD1(alloc, RawStatData*(const std::string& name)); MOCK_METHOD1(free, void(RawStatData& data)); - SymbolTableImpl symbol_table_; + FakeSymbolTableImpl symbol_table_; NiceMock main_thread_dispatcher_; NiceMock tls_; StatsOptionsImpl options_; @@ -960,7 +960,7 @@ class TruncatingAllocTest : public HeapStatsThreadLocalStoreTest { // Do not call superclass SetUp. } - SymbolTableImpl symbol_table_; + FakeSymbolTableImpl symbol_table_; TestAllocator test_alloc_; std::string long_name_; }; diff --git a/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc b/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc index 015cf0af4a76..b4263b010e58 100644 --- a/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc +++ b/test/extensions/stats_sinks/common/statsd/udp_statsd_test.cc @@ -179,7 +179,6 @@ TEST(UdpStatsdSinkTest, CheckActualStatsWithCustomPrefix) { } TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { - Stats::SymbolTableImpl symbol_table; NiceMock source; auto writer_ptr = std::make_shared>(); NiceMock tls_; @@ -187,7 +186,6 @@ TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { std::vector tags = {Stats::Tag{"key1", "value1"}, Stats::Tag{"key2", "value2"}}; auto counter = std::make_shared>(); - // counter->symbol_table_ = &symbol_table; counter->name_ = "test_counter"; counter->used_ = true; counter->latch_ = 1; @@ -200,7 +198,6 @@ TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { counter->used_ = false; auto gauge = std::make_shared>(); - // gauge->symbol_table_ = &symbol_table; gauge->name_ = "test_gauge"; gauge->value_ = 1; gauge->used_ = true; @@ -212,7 +209,6 @@ TEST(UdpStatsdSinkWithTagsTest, CheckActualStats) { sink.flush(source); NiceMock timer; - // timer.symbol_table_ = &symbol_table; timer.name_ = "test_timer"; timer.tags_ = tags; EXPECT_CALL(*std::dynamic_pointer_cast>(writer_ptr), diff --git a/test/integration/server.cc b/test/integration/server.cc index f7ab6bfb6a88..3ff5bf9ccbed 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -174,7 +174,7 @@ void IntegrationTestServerImpl::createAndRunEnvoyServer( Network::Address::InstanceConstSharedPtr local_address, TestHooks& hooks, Thread::BasicLockable& access_log_lock, Server::ComponentFactory& component_factory, Runtime::RandomGeneratorPtr&& random_generator) { - Stats::SymbolTableImpl symbol_table; + Stats::FakeSymbolTableImpl symbol_table; Server::HotRestartNopImpl restarter(symbol_table); ThreadLocal::InstanceImpl tls; Stats::HeapStatDataAllocator stats_allocator(symbol_table); diff --git a/test/server/config_validation/cluster_manager_test.cc b/test/server/config_validation/cluster_manager_test.cc index 1b86396c8991..b64e43b0b83d 100644 --- a/test/server/config_validation/cluster_manager_test.cc +++ b/test/server/config_validation/cluster_manager_test.cc @@ -48,7 +48,7 @@ TEST(ValidationClusterManagerTest, MockedMethods) { local_info, secret_manager, *api, http_context, log_manager, singleton_manager, time_system); const envoy::config::bootstrap::v2::Bootstrap bootstrap; - Stats::SymbolTableImpl symbol_table; + Stats::FakeSymbolTableImpl symbol_table; ClusterManagerPtr cluster_manager = factory.clusterManagerFromProto(bootstrap); EXPECT_EQ(nullptr, cluster_manager->httpConnPoolForCluster("cluster", ResourcePriority::Default, Http::Protocol::Http11, nullptr)); diff --git a/test/server/hot_restart_impl_test.cc b/test/server/hot_restart_impl_test.cc index 46f68cb85953..035c44d01301 100644 --- a/test/server/hot_restart_impl_test.cc +++ b/test/server/hot_restart_impl_test.cc @@ -45,7 +45,7 @@ class HotRestartImplTest : public testing::Test { hot_restart_->drainParentListeners(); } - Stats::SymbolTableImpl symbol_table_; + Stats::FakeSymbolTableImpl symbol_table_; Api::MockOsSysCalls os_sys_calls_; TestThreadsafeSingletonInjector os_calls{&os_sys_calls_}; NiceMock options_; diff --git a/test/server/http/admin_test.cc b/test/server/http/admin_test.cc index 3cc55aa02759..6cb68358b249 100644 --- a/test/server/http/admin_test.cc +++ b/test/server/http/admin_test.cc @@ -63,7 +63,7 @@ class AdminStatsTest : public testing::TestWithParam main_thread_dispatcher_; NiceMock tls_; Stats::StatsOptionsImpl options_; @@ -1242,7 +1242,7 @@ class PrometheusStatsFormatterTest : public testing::Test { histograms_.push_back(histogram); } - Stats::SymbolTableImpl symbol_table_; + Stats::FakeSymbolTableImpl symbol_table_; Stats::StatsOptionsImpl stats_options_; Stats::HeapStatDataAllocator alloc_; std::vector counters_; From 52e07df8b275387aa2bb7c658cd304c51ac28e34 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 10:08:46 -0400 Subject: [PATCH 05/39] checkpoint with impl-specific Encoding class to remove a copy that was impacting microbenchmarks. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 22 +-- source/common/stats/BUILD | 1 + source/common/stats/fake_symbol_table_impl.h | 65 ++++++-- source/common/stats/metric_impl.cc | 2 +- source/common/stats/symbol_table_impl.cc | 47 +++--- source/common/stats/symbol_table_impl.h | 156 +++++++++++-------- test/common/stats/symbol_table_impl_test.cc | 17 +- 7 files changed, 192 insertions(+), 118 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 8cc2b298da64..f15d20326f60 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -19,6 +19,8 @@ namespace Stats { */ class StatName; +class StatNameList; + /** * Intermediate representation for a stat-name. This helps store multiple names * in a single packed allocation. First we encode each desired name, then sum @@ -27,7 +29,7 @@ class StatName; * a vptr overhead per object, and the representation is shared between the * SymbolTable implementations, so this is just a pre-declare. */ -class SymbolEncoding; +//class SymbolEncoding; /** * SymbolTable manages a namespace optimized for stat names, exploiting their @@ -59,22 +61,6 @@ class SymbolTable { virtual ~SymbolTable() = default; - /** - * Encodes a stat name using the symbol table, returning a SymbolEncoding. The - * SymbolEncoding is not intended for long-term storage, but is used to help - * allocate a StatName with the correct amount of storage. - * - * When a name is encoded, it bumps reference counts held in the table for - * each symbol. The caller is responsible for creating a StatName using this - * SymbolEncoding and ultimately disposing of it by calling - * SymbolTable::free(). Users are protected from leaking symbols into the pool - * by ASSERTions in the SymbolTable destructor. - * - * @param name The name to encode. - * @return SymbolEncoding the encoded symbols. - */ - virtual SymbolEncoding encode(absl::string_view name) PURE; - /** * @return uint64_t the number of symbols in the symbol table. */ @@ -130,6 +116,8 @@ class SymbolTable { */ virtual StoragePtr join(const std::vector& stat_names) const PURE; + virtual void populateList(const std::vector& names, StatNameList& list) PURE; + #ifndef ENVOY_CONFIG_COVERAGE virtual void debugPrint() const PURE; #endif diff --git a/source/common/stats/BUILD b/source/common/stats/BUILD index b01c94da80e3..37a6b2b12848 100644 --- a/source/common/stats/BUILD +++ b/source/common/stats/BUILD @@ -146,6 +146,7 @@ envoy_cc_library( "//include/envoy/stats:symbol_table_interface", "//source/common/common:assert_lib", "//source/common/common:logger_lib", + "//source/common/common:stack_array", "//source/common/common:thread_lib", "//source/common/common:utility_lib", ], diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index c53a4096db3c..141c67852a64 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -49,7 +49,60 @@ namespace Stats { */ class FakeSymbolTableImpl : public SymbolTable { public: - SymbolEncoding encode(absl::string_view name) override { return encodeHelper(name); } + /** + * Represents an 8-bit encoding of a vector of symbols, used as a transient + * representation during encoding and prior to retained allocation. + */ + class Encoding { + public: + Encoding() : storage_(nullptr) {} + + explicit Encoding(absl::string_view str) + : storage_(new uint8_t[str.size() + 2]) { + uint8_t* p = saveLengthToBytesReturningNext(str.size(), storage_); + memcpy(p, str.data(), str.size()); + } + + void swap(Encoding& rhs) { + std::swap(rhs.storage_, storage_); + } + + /** + * Before destructing SymbolEncoding, you must call moveToStorage. This + * transfers ownership, and in particular, the responsibility to call + * SymbolTable::clear() on all referenced symbols. If we ever wanted + * to be able to destruct a SymbolEncoding without transferring it + * we could add a clear(SymbolTable&) method. + */ + ~Encoding() { ASSERT(storage_ == nullptr); } + + uint64_t bytesRequired() const { return StatName(storage_).size(); } + + /** + * Moves the contents of the vector into an allocated array. The array + * must have been allocated with bytesRequired() bytes. + * + * @param array destination memory to receive the encoded bytes. + * @return uint64_t the number of bytes transferred. + */ + uint64_t moveToStorage(SymbolTable::Storage array) { + uint64_t bytes_required = bytesRequired(); + memcpy(array, storage_, bytes_required); + delete [] storage_; + storage_ = nullptr; + return bytes_required; + } + + private: + uint8_t* storage_; + }; + + + Encoding encode(absl::string_view name) { return Encoding(name); } + + void populateList(const std::vector& names, StatNameList& list) override { + list.populate(names, *this); + } std::string toString(const StatName& stat_name) const override { return std::string(toStringView(stat_name)); @@ -93,18 +146,12 @@ class FakeSymbolTableImpl : public SymbolTable { return bytes; } - SymbolEncoding encodeHelper(absl::string_view name) const { - SymbolEncoding encoding; - encoding.addStringForFakeSymbolTable(name); - return encoding; - } - absl::string_view toStringView(const StatName& stat_name) const { return {reinterpret_cast(stat_name.data()), stat_name.dataSize()}; } - SymbolTable::StoragePtr stringToStorage(absl::string_view name) const { - SymbolEncoding encoding = encodeHelper(name); + StoragePtr stringToStorage(absl::string_view name) const { + Encoding encoding(name); auto bytes = std::make_unique(encoding.bytesRequired()); encoding.moveToStorage(bytes.get()); return bytes; diff --git a/source/common/stats/metric_impl.cc b/source/common/stats/metric_impl.cc index 3eed2af53931..e27a5e98cb62 100644 --- a/source/common/stats/metric_impl.cc +++ b/source/common/stats/metric_impl.cc @@ -26,7 +26,7 @@ MetricImpl::MetricImpl(absl::string_view tag_extracted_name, const std::vector(data()[i])); } - SymbolVec encoding = SymbolEncoding::decodeSymbols(data(), dataSize()); + SymbolVec encoding = SymbolTableImpl::Encoding::decodeSymbols(data(), dataSize()); absl::StrAppend(&msg, ", numSymbols=", encoding.size(), ":"); for (Symbol symbol : encoding) { absl::StrAppend(&msg, " ", symbol); @@ -40,9 +40,9 @@ void StatName::debugPrint() { } #endif -SymbolEncoding::~SymbolEncoding() { ASSERT(vec_.empty()); } +SymbolTableImpl::Encoding::~Encoding() { ASSERT(vec_.empty()); } -void SymbolEncoding::addSymbol(Symbol symbol) { +void SymbolTableImpl::Encoding::addSymbol(Symbol symbol) { // UTF-8-like encoding where a value 127 or less gets written as a single // byte. For higher values we write the low-order 7 bits with a 1 in // the high-order bit. Then we right-shift 7 bits and keep adding more bytes @@ -60,14 +60,7 @@ void SymbolEncoding::addSymbol(Symbol symbol) { } while (symbol != 0); } -void SymbolEncoding::addStringForFakeSymbolTable(absl::string_view str) { - if (!str.empty()) { - vec_.resize(str.size()); - memcpy(&vec_[0], str.data(), str.size()); - } -} - -SymbolVec SymbolEncoding::decodeSymbols(const SymbolTable::Storage array, uint64_t size) { +SymbolVec SymbolTableImpl::Encoding::decodeSymbols(const SymbolTable::Storage array, uint64_t size) { SymbolVec symbol_vec; Symbol symbol = 0; for (uint32_t shift = 0; size > 0; --size, ++array) { @@ -98,7 +91,7 @@ static inline uint8_t* saveLengthToBytesReturningNext(uint64_t length, uint8_t* return bytes; } -uint64_t SymbolEncoding::moveToStorage(SymbolTable::Storage symbol_array) { +uint64_t SymbolTableImpl::Encoding::moveToStorage(SymbolTable::Storage symbol_array) { uint64_t sz = size(); symbol_array = saveLengthToBytesReturningNext(sz, symbol_array); if (sz != 0) { @@ -123,8 +116,8 @@ SymbolTableImpl::~SymbolTableImpl() { // TODO(ambuc): There is a possible performance optimization here for avoiding // the encoding of IPs / numbers if they appear in stat names. We don't want to // waste time symbolizing an integer as an integer, if we can help it. -SymbolEncoding SymbolTableImpl::encode(const absl::string_view name) { - SymbolEncoding encoding; +SymbolTableImpl::Encoding SymbolTableImpl::encode(const absl::string_view name) { + Encoding encoding; if (name.empty()) { return encoding; @@ -159,7 +152,7 @@ uint64_t SymbolTableImpl::numSymbols() const { } std::string SymbolTableImpl::toString(const StatName& stat_name) const { - return decodeSymbolVec(SymbolEncoding::decodeSymbols(stat_name.data(), stat_name.dataSize())); + return decodeSymbolVec(SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize())); } std::string SymbolTableImpl::decodeSymbolVec(const SymbolVec& symbols) const { @@ -177,7 +170,7 @@ std::string SymbolTableImpl::decodeSymbolVec(const SymbolVec& symbols) const { void SymbolTableImpl::incRefCount(const StatName& stat_name) { // Before taking the lock, decode the array of symbols from the SymbolTable::Storage. - SymbolVec symbols = SymbolEncoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); + SymbolVec symbols = SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); Thread::LockGuard lock(lock_); for (Symbol symbol : symbols) { @@ -193,7 +186,7 @@ void SymbolTableImpl::incRefCount(const StatName& stat_name) { void SymbolTableImpl::free(const StatName& stat_name) { // Before taking the lock, decode the array of symbols from the SymbolTable::Storage. - SymbolVec symbols = SymbolEncoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); + SymbolVec symbols = SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); Thread::LockGuard lock(lock_); for (Symbol symbol : symbols) { @@ -265,8 +258,8 @@ bool SymbolTableImpl::lessThan(const StatName& a, const StatName& b) const { // If this becomes a performance bottleneck (e.g. during sorting), we could // provide an iterator-like interface for incrementally decoding the symbols // without allocating memory. - SymbolVec av = SymbolEncoding::decodeSymbols(a.data(), a.dataSize()); - SymbolVec bv = SymbolEncoding::decodeSymbols(b.data(), b.dataSize()); + SymbolVec av = SymbolTableImpl::Encoding::decodeSymbols(a.data(), a.dataSize()); + SymbolVec bv = SymbolTableImpl::Encoding::decodeSymbols(b.data(), b.dataSize()); // Calling fromSymbol requires holding the lock, as it needs read-access to // the maps that are written when adding new symbols. @@ -297,7 +290,7 @@ void SymbolTableImpl::debugPrint() const { #endif SymbolTable::StoragePtr SymbolTableImpl::copyToBytes(absl::string_view name) { - SymbolEncoding encoding = encode(name); + Encoding encoding = encode(name); auto bytes = std::make_unique(encoding.bytesRequired()); encoding.moveToStorage(bytes.get()); return bytes; @@ -341,19 +334,23 @@ SymbolTable::StoragePtr SymbolTableImpl::join(const std::vector& stat_ return bytes; } +void SymbolTableImpl::populateList(const std::vector& names, StatNameList& list) { + list.populate(names, *this); +} + StatNameList::~StatNameList() { ASSERT(!populated()); } -void StatNameList::populate(const std::vector& names, - SymbolTable& symbol_table) { +/*void StatNameList::populate(const std::vector& names, + SymbolTableType& symbol_table) { RELEASE_ASSERT(names.size() < 256, "Maximum number elements in a StatNameList exceeded"); // First encode all the names. - size_t total_size_bytes = 1; /* one byte for holding the number of names */ + size_t total_size_bytes = 1; // one byte for holding the number of names std::vector encodings; encodings.resize(names.size()); int index = 0; for (auto& name : names) { - SymbolEncoding encoding = symbol_table.encode(name); + SymbolEncoding encoding = symbol_table.fooEncode(name); total_size_bytes += encoding.bytesRequired(); encodings[index++].swap(encoding); } @@ -367,7 +364,7 @@ void StatNameList::populate(const std::vector& names, p += encoding.moveToStorage(p); } ASSERT(p == &storage_[0] + total_size_bytes); -} +}*/ void StatNameList::iterate(const std::function& f) const { uint8_t* p = &storage_[0]; diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 5388eb570eaa..6274a2ec7731 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -15,6 +15,7 @@ #include "common/common/hash.h" #include "common/common/lock_guard.h" #include "common/common/non_copyable.h" +#include "common/common/stack_array.h" #include "common/common/thread.h" #include "common/common/utility.h" @@ -37,67 +38,6 @@ constexpr uint64_t StatNameMaxSize = 1 << (8 * StatNameSizeEncodingBytes); // 65 /** Transient representations of a vector of 32-bit symbols */ using SymbolVec = std::vector; -/** - * Represents an 8-bit encoding of a vector of symbols, used as a transient - * representation during encoding and prior to retained allocation. - */ -class SymbolEncoding { -public: - /** - * Before destructing SymbolEncoding, you must call moveToStorage. This - * transfers ownership, and in particular, the responsibility to call - * SymbolTable::clear() on all referenced symbols. If we ever wanted - * to be able to destruct a SymbolEncoding without transferring it - * we could add a clear(SymbolTable&) method. - */ - ~SymbolEncoding(); - - /** - * Encodes a token into the vec. - * - * @param symbol the symbol to encode. - */ - void addSymbol(Symbol symbol); - - /** - * Encodes an entire string into the vec, on behalf of FakeSymbolTableImpl. - * TODO(jmarantz): delete this method when FakeSymbolTableImpl is deleted. - * - * @param str The string to encode. - */ - void addStringForFakeSymbolTable(absl::string_view str); - - /** - * Decodes a uint8_t array into a SymbolVec. - */ - static SymbolVec decodeSymbols(const SymbolTable::Storage array, uint64_t size); - - /** - * Returns the number of bytes required to represent StatName as a uint8_t - * array, including the encoded size. - */ - uint64_t bytesRequired() const { return size() + StatNameSizeEncodingBytes; } - - /** - * Returns the number of uint8_t entries we collected while adding symbols. - */ - uint64_t size() const { return vec_.size(); } - - /** - * Moves the contents of the vector into an allocated array. The array - * must have been allocated with bytesRequired() bytes. - * - * @param array destination memory to receive the encoded bytes. - * @return uint64_t the number of bytes transferred. - */ - uint64_t moveToStorage(SymbolTable::Storage array); - - void swap(SymbolEncoding& src) { vec_.swap(src.vec_); } - -private: - std::vector vec_; -}; - /** * SymbolTableImpl manages a namespace optimized for stats, which are typically * composed of arrays of "."-separated tokens, with a significant overlap @@ -130,18 +70,84 @@ class SymbolEncoding { */ class SymbolTableImpl : public SymbolTable { public: + /** + * Represents an 8-bit encoding of a vector of symbols, used as a transient + * representation during encoding and prior to retained allocation. + */ + class Encoding { + public: + Encoding() = default; + + /** + * Before destructing SymbolEncoding, you must call moveToStorage. This + * transfers ownership, and in particular, the responsibility to call + * SymbolTable::clear() on all referenced symbols. If we ever wanted + * to be able to destruct a SymbolEncoding without transferring it + * we could add a clear(SymbolTable&) method. + */ + ~Encoding(); + + /** + * Encodes a token into the vec. + * + * @param symbol the symbol to encode. + */ + void addSymbol(Symbol symbol); + + /** + * Encodes an entire string into the vec, on behalf of FakeSymbolTableImpl. + * TODO(jmarantz): delete this method when FakeSymbolTableImpl is deleted. + * + * @param str The string to encode. + */ + void addStringForFakeSymbolTable(absl::string_view str); + + /** + * Decodes a uint8_t array into a SymbolVec. + */ + static SymbolVec decodeSymbols(const SymbolTable::Storage array, uint64_t size); + + /** + * Returns the number of bytes required to represent StatName as a uint8_t + * array, including the encoded size. + */ + uint64_t bytesRequired() const { return size() + StatNameSizeEncodingBytes; } + + /** + * Returns the number of uint8_t entries we collected while adding symbols. + */ + uint64_t size() const { return vec_.size(); } + + /** + * Moves the contents of the vector into an allocated array. The array + * must have been allocated with bytesRequired() bytes. + * + * @param array destination memory to receive the encoded bytes. + * @return uint64_t the number of bytes transferred. + */ + uint64_t moveToStorage(SymbolTable::Storage array); + + void swap(Encoding& src) { vec_.swap(src.vec_); } + + private: + std::vector vec_; + }; + + SymbolTableImpl(); ~SymbolTableImpl() override; // SymbolTable std::string toString(const StatName& stat_name) const override; - SymbolEncoding encode(absl::string_view name) override; uint64_t numSymbols() const override; bool lessThan(const StatName& a, const StatName& b) const override; void free(const StatName& stat_name) override; void incRefCount(const StatName& stat_name) override; SymbolTable::StoragePtr join(const std::vector& stat_names) const override; + Encoding encode(absl::string_view name); + void populateList(const std::vector& names, StatNameList& list) override; + #ifndef ENVOY_CONFIG_COVERAGE void debugPrint() const override; #endif @@ -386,7 +392,31 @@ class StatNameList { * @param encodings The list names to encode. * @param symbol_table The symbol table in which to encode the names. */ - void populate(const std::vector& encodings, SymbolTable& symbol_table); + template void populate(const std::vector& names, + SymbolTableType& symbol_table) { + RELEASE_ASSERT(names.size() < 256, "Maximum number elements in a StatNameList exceeded"); + + // First encode all the names. + size_t total_size_bytes = 1; /* one byte for holding the number of names */ + + STACK_ARRAY(encodings, typename SymbolTableType::Encoding, names.size()); + size_t i = 0; + for (auto& name : names) { + typename SymbolTableType::Encoding encoding = symbol_table.encode(name); + total_size_bytes += encoding.bytesRequired(); + encodings[i++].swap(encoding); + } + + // Now allocate the exact number of bytes required and move the encodings + // into storage. + storage_ = std::make_unique(total_size_bytes); + uint8_t* p = &storage_[0]; + *p++ = names.size(); + for (auto& encoding : encodings) { + p += encoding.moveToStorage(p); + } + ASSERT(p == &storage_[0] + total_size_bytes); + } /** * @return true if populate() has been called on this list. diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index 59680fd38ea0..9188cbade2b5 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -39,7 +39,9 @@ class StatNameTest : public testing::TestWithParam { break; } case SymbolTableType::Fake: - table_ = std::make_unique(); + auto table = std::make_unique(); + fake_symbol_table_ = table.get(); + table_ = std::move(table); break; } } @@ -54,7 +56,7 @@ class StatNameTest : public testing::TestWithParam { } SymbolVec getSymbols(StatName stat_name) { - return SymbolEncoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); + return SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); } std::string decodeSymbolVec(const SymbolVec& symbol_vec) { return real_symbol_table_->decodeSymbolVec(symbol_vec); @@ -71,6 +73,7 @@ class StatNameTest : public testing::TestWithParam { return stat_name_storage_.back().statName(); } + FakeSymbolTableImpl* fake_symbol_table_{nullptr}; SymbolTableImpl* real_symbol_table_{nullptr}; std::unique_ptr table_; @@ -271,7 +274,15 @@ TEST_P(StatNameTest, List) { std::vector names{"hello.world", "goodbye.world"}; StatNameList name_list; EXPECT_FALSE(name_list.populated()); - name_list.populate(names, *table_); + switch (GetParam()) { + case SymbolTableType::Real: + name_list.populate(names, *real_symbol_table_); + break; + case SymbolTableType::Fake: + name_list.populate(names, *fake_symbol_table_); + break; + } + EXPECT_TRUE(name_list.populated()); // First, decode only the first name. From 08c35f5079c56733f074c81b9fd781a7961a3856 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 13:18:27 -0400 Subject: [PATCH 06/39] hacks to use stack allocated arrays for temps to reduce new/delete calls. Helped but not too much. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 2 +- source/common/stats/fake_symbol_table_impl.h | 44 +++++++++++--------- source/common/stats/metric_impl.cc | 6 +-- source/common/stats/symbol_table_impl.cc | 14 +++---- source/common/stats/symbol_table_impl.h | 19 ++++----- test/common/stats/symbol_table_impl_test.cc | 7 ++-- 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index f15d20326f60..3a836ecdbdff 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -116,7 +116,7 @@ class SymbolTable { */ virtual StoragePtr join(const std::vector& stat_names) const PURE; - virtual void populateList(const std::vector& names, StatNameList& list) PURE; + virtual void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) PURE; #ifndef ENVOY_CONFIG_COVERAGE virtual void debugPrint() const PURE; diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index 141c67852a64..a9d97fe471ca 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -55,17 +55,22 @@ class FakeSymbolTableImpl : public SymbolTable { */ class Encoding { public: - Encoding() : storage_(nullptr) {} + //Encoding() : storage_(nullptr) {} - explicit Encoding(absl::string_view str) - : storage_(new uint8_t[str.size() + 2]) { - uint8_t* p = saveLengthToBytesReturningNext(str.size(), storage_); + void fromString(absl::string_view str) { + storage_ = std::make_unique(str.size() + 2); + uint8_t* p = saveLengthToBytesReturningNext(str.size(), storage_.get()); memcpy(p, str.data(), str.size()); } - void swap(Encoding& rhs) { + /*Encoding& operator=(Encoding&& src) { + storage_ = std::move(src.storage_); + return *this; + }*/ + + /*void swap(Encoding& rhs) { std::swap(rhs.storage_, storage_); - } + }*/ /** * Before destructing SymbolEncoding, you must call moveToStorage. This @@ -76,7 +81,7 @@ class FakeSymbolTableImpl : public SymbolTable { */ ~Encoding() { ASSERT(storage_ == nullptr); } - uint64_t bytesRequired() const { return StatName(storage_).size(); } + uint64_t bytesRequired() const { return StatName(storage_.get()).size(); } /** * Moves the contents of the vector into an allocated array. The array @@ -87,21 +92,23 @@ class FakeSymbolTableImpl : public SymbolTable { */ uint64_t moveToStorage(SymbolTable::Storage array) { uint64_t bytes_required = bytesRequired(); - memcpy(array, storage_, bytes_required); - delete [] storage_; - storage_ = nullptr; + memcpy(array, storage_.get(), bytes_required); + storage_.reset(); return bytes_required; } + StoragePtr transferStorage() { + return std::move(storage_); + } + private: - uint8_t* storage_; + StoragePtr storage_; }; + void encode(absl::string_view name, Encoding& encoding) { encoding.fromString(name); } - Encoding encode(absl::string_view name) { return Encoding(name); } - - void populateList(const std::vector& names, StatNameList& list) override { - list.populate(names, *this); + void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) override { + list.populate(names, num_names, *this); } std::string toString(const StatName& stat_name) const override { @@ -151,10 +158,9 @@ class FakeSymbolTableImpl : public SymbolTable { } StoragePtr stringToStorage(absl::string_view name) const { - Encoding encoding(name); - auto bytes = std::make_unique(encoding.bytesRequired()); - encoding.moveToStorage(bytes.get()); - return bytes; + Encoding encoding; + encoding.fromString(name); + return encoding.transferStorage(); } }; diff --git a/source/common/stats/metric_impl.cc b/source/common/stats/metric_impl.cc index e27a5e98cb62..6851d2abd3ee 100644 --- a/source/common/stats/metric_impl.cc +++ b/source/common/stats/metric_impl.cc @@ -18,15 +18,15 @@ MetricImpl::MetricImpl(absl::string_view tag_extracted_name, const std::vector names; - names.resize(1 /* tag_extracted_name */ + 2 * tags.size()); + uint32_t num_names = 1 /* tag_extracted_name */ + 2 * tags.size(); + STACK_ARRAY(names, absl::string_view, num_names); names[0] = tag_extracted_name; int index = 0; for (auto& tag : tags) { names[++index] = tag.name_; names[++index] = tag.value_; } - symbol_table.populateList(names, stat_names_); + symbol_table.populateList(names.begin(), num_names, stat_names_); } void MetricImpl::clear() { stat_names_.clear(symbolTable()); } diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index 6b01c37e1b66..cbbc6eab40ef 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -116,11 +116,9 @@ SymbolTableImpl::~SymbolTableImpl() { // TODO(ambuc): There is a possible performance optimization here for avoiding // the encoding of IPs / numbers if they appear in stat names. We don't want to // waste time symbolizing an integer as an integer, if we can help it. -SymbolTableImpl::Encoding SymbolTableImpl::encode(const absl::string_view name) { - Encoding encoding; - +void SymbolTableImpl::encode(const absl::string_view name, Encoding& encoding) { if (name.empty()) { - return encoding; + return; } // We want to hold the lock for the minimum amount of time, so we do the @@ -142,7 +140,6 @@ SymbolTableImpl::Encoding SymbolTableImpl::encode(const absl::string_view name) for (Symbol symbol : symbols) { encoding.addSymbol(symbol); } - return encoding; } uint64_t SymbolTableImpl::numSymbols() const { @@ -290,7 +287,8 @@ void SymbolTableImpl::debugPrint() const { #endif SymbolTable::StoragePtr SymbolTableImpl::copyToBytes(absl::string_view name) { - Encoding encoding = encode(name); + Encoding encoding; + encode(name, encoding); auto bytes = std::make_unique(encoding.bytesRequired()); encoding.moveToStorage(bytes.get()); return bytes; @@ -334,8 +332,8 @@ SymbolTable::StoragePtr SymbolTableImpl::join(const std::vector& stat_ return bytes; } -void SymbolTableImpl::populateList(const std::vector& names, StatNameList& list) { - list.populate(names, *this); +void SymbolTableImpl::populateList(absl::string_view* names, int32_t num_names, StatNameList& list) { + list.populate(names, num_names, *this); } StatNameList::~StatNameList() { ASSERT(!populated()); } diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 6274a2ec7731..83e7a0e484ff 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -145,8 +145,8 @@ class SymbolTableImpl : public SymbolTable { void incRefCount(const StatName& stat_name) override; SymbolTable::StoragePtr join(const std::vector& stat_names) const override; - Encoding encode(absl::string_view name); - void populateList(const std::vector& names, StatNameList& list) override; + void encode(absl::string_view name, Encoding& encoding); + void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) override; #ifndef ENVOY_CONFIG_COVERAGE void debugPrint() const override; @@ -392,26 +392,25 @@ class StatNameList { * @param encodings The list names to encode. * @param symbol_table The symbol table in which to encode the names. */ - template void populate(const std::vector& names, + template void populate(absl::string_view* names, int32_t num_names, SymbolTableType& symbol_table) { - RELEASE_ASSERT(names.size() < 256, "Maximum number elements in a StatNameList exceeded"); + RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); // First encode all the names. size_t total_size_bytes = 1; /* one byte for holding the number of names */ - STACK_ARRAY(encodings, typename SymbolTableType::Encoding, names.size()); - size_t i = 0; - for (auto& name : names) { - typename SymbolTableType::Encoding encoding = symbol_table.encode(name); + STACK_ARRAY(encodings, typename SymbolTableType::Encoding, num_names); + for (int32_t i = 0; i < num_names; ++i) { + typename SymbolTableType::Encoding& encoding = encodings[i]; + symbol_table.encode(names[i], encoding); total_size_bytes += encoding.bytesRequired(); - encodings[i++].swap(encoding); } // Now allocate the exact number of bytes required and move the encodings // into storage. storage_ = std::make_unique(total_size_bytes); uint8_t* p = &storage_[0]; - *p++ = names.size(); + *p++ = num_names; for (auto& encoding : encodings) { p += encoding.moveToStorage(p); } diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index 9188cbade2b5..e76204c94963 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -1,5 +1,6 @@ #include +#include "common/common/macros.h" #include "common/common/mutex_tracer_impl.h" #include "common/memory/stats.h" #include "common/stats/fake_symbol_table_impl.h" @@ -271,15 +272,15 @@ TEST_P(StatNameTest, TestShrinkingExpectation) { // you don't free all the StatNames you've allocated bytes for. StatNameList // provides this capability. TEST_P(StatNameTest, List) { - std::vector names{"hello.world", "goodbye.world"}; + absl::string_view names[] = {"hello.world", "goodbye.world"}; StatNameList name_list; EXPECT_FALSE(name_list.populated()); switch (GetParam()) { case SymbolTableType::Real: - name_list.populate(names, *real_symbol_table_); + name_list.populate(names, ARRAY_SIZE(names), *real_symbol_table_); break; case SymbolTableType::Fake: - name_list.populate(names, *fake_symbol_table_); + name_list.populate(names, ARRAY_SIZE(names), *fake_symbol_table_); break; } From 94fba4fd11308a85ae51dcce9a136496eb80a21b Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 15:48:37 -0400 Subject: [PATCH 07/39] Make a separate populateList for Fake table that doesn't bother encoding each element. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 8 +++++ source/common/stats/fake_symbol_table_impl.h | 26 ++++++++++++++- source/common/stats/symbol_table_impl.cc | 23 ++++++++++++- source/common/stats/symbol_table_impl.h | 35 ++------------------ test/common/stats/symbol_table_impl_test.cc | 3 ++ 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 3a836ecdbdff..5d56d9157d5f 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -116,6 +116,14 @@ class SymbolTable { */ virtual StoragePtr join(const std::vector& stat_names) const PURE; + /** + * Populates a StatNameList from a list of encodings. This is not done at + * construction time to enable StatNameList to be instantiated directly in + * a class that doesn't have a live SymbolTable when it is constructed. + * + * @param encodings The list names to encode. + * @param symbol_table The symbol table in which to encode the names. + */ virtual void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) PURE; #ifndef ENVOY_CONFIG_COVERAGE diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index a9d97fe471ca..671c2c912da7 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -108,7 +108,31 @@ class FakeSymbolTableImpl : public SymbolTable { void encode(absl::string_view name, Encoding& encoding) { encoding.fromString(name); } void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) override { - list.populate(names, num_names, *this); + RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); + + // First encode all the names. + size_t total_size_bytes = 1 + num_names * StatNameSizeEncodingBytes; + + for (int32_t i = 0; i < num_names; ++i) { + total_size_bytes += names[i].size(); + } + + // Now allocate the exact number of bytes required and move the encodings + // into storage. + auto storage = std::make_unique(total_size_bytes); + uint8_t* p = &storage[0]; + *p++ = num_names; + for (int32_t i = 0; i < num_names; ++i) { + auto& name = names[i]; + size_t sz = name.size(); + p = saveLengthToBytesReturningNext(sz, p); + if (!name.empty()) { + memcpy(p, name.data(), sz * sizeof(uint8_t)); + p += sz; + } + } + ASSERT(p == &storage[0] + total_size_bytes); + list.moveStorageIntoList(std::move(storage)); } std::string toString(const StatName& stat_name) const override { diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index cbbc6eab40ef..4bc2f793906f 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -333,7 +333,28 @@ SymbolTable::StoragePtr SymbolTableImpl::join(const std::vector& stat_ } void SymbolTableImpl::populateList(absl::string_view* names, int32_t num_names, StatNameList& list) { - list.populate(names, num_names, *this); + RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); + + // First encode all the names. + size_t total_size_bytes = 1; /* one byte for holding the number of names */ + + STACK_ARRAY(encodings, Encoding, num_names); + for (int32_t i = 0; i < num_names; ++i) { + Encoding& encoding = encodings[i]; + encode(names[i], encoding); + total_size_bytes += encoding.bytesRequired(); + } + + // Now allocate the exact number of bytes required and move the encodings + // into storage. + auto storage = std::make_unique(total_size_bytes); + uint8_t* p = &storage[0]; + *p++ = num_names; + for (auto& encoding : encodings) { + p += encoding.moveToStorage(p); + } + ASSERT(p == &storage[0] + total_size_bytes); + list.moveStorageIntoList(std::move(storage)); } StatNameList::~StatNameList() { ASSERT(!populated()); } diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 83e7a0e484ff..497e92ec2f62 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -384,37 +384,8 @@ class StatNameList { public: ~StatNameList(); - /** - * Populates the StatNameList from a list of encodings. This is not done at - * construction time to enable StatNameList to be instantiated directly in - * a class that doesn't have a live SymbolTable when it is constructed. - * - * @param encodings The list names to encode. - * @param symbol_table The symbol table in which to encode the names. - */ - template void populate(absl::string_view* names, int32_t num_names, - SymbolTableType& symbol_table) { - RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); - - // First encode all the names. - size_t total_size_bytes = 1; /* one byte for holding the number of names */ - - STACK_ARRAY(encodings, typename SymbolTableType::Encoding, num_names); - for (int32_t i = 0; i < num_names; ++i) { - typename SymbolTableType::Encoding& encoding = encodings[i]; - symbol_table.encode(names[i], encoding); - total_size_bytes += encoding.bytesRequired(); - } - - // Now allocate the exact number of bytes required and move the encodings - // into storage. - storage_ = std::make_unique(total_size_bytes); - uint8_t* p = &storage_[0]; - *p++ = num_names; - for (auto& encoding : encodings) { - p += encoding.moveToStorage(p); - } - ASSERT(p == &storage_[0] + total_size_bytes); + void moveStorageIntoList(SymbolTable::StoragePtr&& storage) { + storage_ = std::move(storage); } /** @@ -442,7 +413,7 @@ class StatNameList { void clear(SymbolTable& symbol_table); private: - std::unique_ptr storage_; + SymbolTable::StoragePtr storage_; }; // Helper class for constructing hash-tables with StatName keys. diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index e76204c94963..9ad0f4e1912e 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -275,6 +275,8 @@ TEST_P(StatNameTest, List) { absl::string_view names[] = {"hello.world", "goodbye.world"}; StatNameList name_list; EXPECT_FALSE(name_list.populated()); + table_->populateList(names, ARRAY_SIZE(names), name_list); + /* switch (GetParam()) { case SymbolTableType::Real: name_list.populate(names, ARRAY_SIZE(names), *real_symbol_table_); @@ -283,6 +285,7 @@ TEST_P(StatNameTest, List) { name_list.populate(names, ARRAY_SIZE(names), *fake_symbol_table_); break; } + */ EXPECT_TRUE(name_list.populated()); From c103f22cb617cd7cbf1444c9135cf8bd66d206c6 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 16:27:11 -0400 Subject: [PATCH 08/39] Use string_view through to the regexes to avoid an extra std::string temp creation. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 4 ++++ include/envoy/stats/tag_extractor.h | 2 +- include/envoy/stats/tag_producer.h | 4 +++- source/common/stats/fake_symbol_table_impl.h | 5 +++++ source/common/stats/symbol_table_impl.cc | 5 +++++ source/common/stats/symbol_table_impl.h | 3 +++ source/common/stats/tag_extractor_impl.cc | 12 ++++++----- source/common/stats/tag_extractor_impl.h | 4 ++-- source/common/stats/tag_producer_impl.cc | 6 +++--- source/common/stats/tag_producer_impl.h | 4 ++-- source/common/stats/thread_local_store.cc | 22 +++----------------- 11 files changed, 38 insertions(+), 33 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 5d56d9157d5f..8bc2d9dbed3c 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -130,6 +131,9 @@ class SymbolTable { virtual void debugPrint() const PURE; #endif + virtual void callWithStringView(StatName stat_name, + const std::function& fn) const PURE; + private: friend struct HeapStatData; friend class StatNameStorage; diff --git a/include/envoy/stats/tag_extractor.h b/include/envoy/stats/tag_extractor.h index 270b78238633..361cda3e2266 100644 --- a/include/envoy/stats/tag_extractor.h +++ b/include/envoy/stats/tag_extractor.h @@ -40,7 +40,7 @@ class TagExtractor { * @param remove_characters set of intervals of character-indices to be removed from name. * @return bool indicates whether a tag was found in the name. */ - virtual bool extractTag(const std::string& stat_name, std::vector& tags, + virtual bool extractTag(absl::string_view stat_name, std::vector& tags, IntervalSet& remove_characters) const PURE; /** diff --git a/include/envoy/stats/tag_producer.h b/include/envoy/stats/tag_producer.h index 3e7986cd69d5..3cf2b27da128 100644 --- a/include/envoy/stats/tag_producer.h +++ b/include/envoy/stats/tag_producer.h @@ -7,6 +7,8 @@ #include "envoy/common/pure.h" #include "envoy/stats/tag.h" +#include "absl/strings/string_view.h" + namespace Envoy { namespace Stats { @@ -20,7 +22,7 @@ class TagProducer { * @param metric_name std::string a name of Stats::Metric (Counter, Gauge, Histogram). * @param tags std::vector a set of Stats::Tag. */ - virtual std::string produceTags(const std::string& metric_name, + virtual std::string produceTags(absl::string_view metric_name, std::vector& tags) const PURE; }; diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index 671c2c912da7..3c62eba638ee 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -166,6 +166,11 @@ class FakeSymbolTableImpl : public SymbolTable { return bytes; } + void callWithStringView(StatName stat_name, + const std::function& fn) const override { + fn(toStringView(stat_name)); + } + private: // Saves the specified length into the byte array, returning the next byte. // There is no guarantee that bytes will be aligned, so we can't cast to a diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index 4bc2f793906f..d609b8697fb2 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -152,6 +152,11 @@ std::string SymbolTableImpl::toString(const StatName& stat_name) const { return decodeSymbolVec(SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize())); } +void SymbolTableImpl::callWithStringView(StatName stat_name, + const std::function& fn) const { + fn(toString(stat_name)); +} + std::string SymbolTableImpl::decodeSymbolVec(const SymbolVec& symbols) const { std::vector name_tokens; name_tokens.reserve(symbols.size()); diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 497e92ec2f62..8c947490385d 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -154,6 +154,9 @@ class SymbolTableImpl : public SymbolTable { StoragePtr copyToBytes(absl::string_view name) override; + void callWithStringView(StatName stat_name, + const std::function& fn) const override; + private: friend class StatName; friend class StatNameTest; diff --git a/source/common/stats/tag_extractor_impl.cc b/source/common/stats/tag_extractor_impl.cc index 092e66f0edd8..010932deb0be 100644 --- a/source/common/stats/tag_extractor_impl.cc +++ b/source/common/stats/tag_extractor_impl.cc @@ -62,11 +62,11 @@ TagExtractorPtr TagExtractorImpl::createTagExtractor(const std::string& name, return TagExtractorPtr{new TagExtractorImpl(name, regex, substr)}; } -bool TagExtractorImpl::substrMismatch(const std::string& stat_name) const { - return !substr_.empty() && stat_name.find(substr_) == std::string::npos; +bool TagExtractorImpl::substrMismatch(absl::string_view stat_name) const { + return !substr_.empty() && stat_name.find(substr_) == absl::string_view::npos; } -bool TagExtractorImpl::extractTag(const std::string& stat_name, std::vector& tags, +bool TagExtractorImpl::extractTag(absl::string_view stat_name, std::vector& tags, IntervalSet& remove_characters) const { PERF_OPERATION(perf); @@ -75,9 +75,11 @@ bool TagExtractorImpl::extractTag(const std::string& stat_name, std::vector return false; } - std::smatch match; + std::match_results match; // The regex must match and contain one or more subexpressions (all after the first are ignored). - if (std::regex_search(stat_name, match, regex_) && match.size() > 1) { + //std::string stat_name_str = std::string(stat_name); + if (std::regex_search( + stat_name.begin(), stat_name.end(), match, regex_) && match.size() > 1) { // remove_subexpr is the first submatch. It represents the portion of the string to be removed. const auto& remove_subexpr = match[1]; diff --git a/source/common/stats/tag_extractor_impl.h b/source/common/stats/tag_extractor_impl.h index c72765a75234..138f25b6af7c 100644 --- a/source/common/stats/tag_extractor_impl.h +++ b/source/common/stats/tag_extractor_impl.h @@ -28,7 +28,7 @@ class TagExtractorImpl : public TagExtractor { TagExtractorImpl(const std::string& name, const std::string& regex, const std::string& substr = ""); std::string name() const override { return name_; } - bool extractTag(const std::string& tag_extracted_name, std::vector& tags, + bool extractTag(absl::string_view tag_extracted_name, std::vector& tags, IntervalSet& remove_characters) const override; absl::string_view prefixToken() const override { return prefix_; } @@ -37,7 +37,7 @@ class TagExtractorImpl : public TagExtractor { * @return bool indicates whether tag extraction should be skipped for this stat_name due * to a substring mismatch. */ - bool substrMismatch(const std::string& stat_name) const; + bool substrMismatch(absl::string_view stat_name) const; private: /** diff --git a/source/common/stats/tag_producer_impl.cc b/source/common/stats/tag_producer_impl.cc index d23d609c8a44..f03a4b6553e3 100644 --- a/source/common/stats/tag_producer_impl.cc +++ b/source/common/stats/tag_producer_impl.cc @@ -63,12 +63,12 @@ void TagProducerImpl::addExtractor(TagExtractorPtr extractor) { } void TagProducerImpl::forEachExtractorMatching( - const std::string& stat_name, std::function f) const { + absl::string_view stat_name, std::function f) const { IntervalSetImpl remove_characters; for (const TagExtractorPtr& tag_extractor : tag_extractors_without_prefix_) { f(tag_extractor); } - const std::string::size_type dot = stat_name.find('.'); + const absl::string_view::size_type dot = stat_name.find('.'); if (dot != std::string::npos) { const absl::string_view token = absl::string_view(stat_name.data(), dot); const auto iter = tag_extractor_prefix_map_.find(token); @@ -80,7 +80,7 @@ void TagProducerImpl::forEachExtractorMatching( } } -std::string TagProducerImpl::produceTags(const std::string& metric_name, +std::string TagProducerImpl::produceTags(absl::string_view metric_name, std::vector& tags) const { tags.insert(tags.end(), default_tags_.begin(), default_tags_.end()); IntervalSetImpl remove_characters; diff --git a/source/common/stats/tag_producer_impl.h b/source/common/stats/tag_producer_impl.h index 1614bcb28299..505cb71929aa 100644 --- a/source/common/stats/tag_producer_impl.h +++ b/source/common/stats/tag_producer_impl.h @@ -37,7 +37,7 @@ class TagProducerImpl : public TagProducer { * @param metric_name std::string a name of Stats::Metric (Counter, Gauge, Histogram). * @param tags std::vector a set of Stats::Tag. */ - std::string produceTags(const std::string& metric_name, std::vector& tags) const override; + std::string produceTags(absl::string_view metric_name, std::vector& tags) const override; private: friend class DefaultTagRegexTester; @@ -89,7 +89,7 @@ class TagProducerImpl : public TagProducer { * @param stat_name const std::string& the stat name. * @param f std::function function to call for each extractor. */ - void forEachExtractorMatching(const std::string& stat_name, + void forEachExtractorMatching(absl::string_view stat_name, std::function f) const; std::vector tag_extractors_without_prefix_; diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 8fb32a1bd547..7c047fca25bf 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -250,31 +250,15 @@ ThreadLocalStoreImpl::ScopeImpl::~ScopeImpl() { prefix_.free(symbolTable()); } -/* -void ThreadLocalStoreImpl::ScopeImpl::extractTagsAndTruncate( - StatName& name, std::unique_ptr& truncated_name_storage, - std::vector& tags, - std::string& tag_extracted_name) { - - // Tag extraction occurs on the original, untruncated name so the extraction - // can complete properly, even if the tag values are partially truncated. - std::string name_str = name.toString(parent_.symbolTable()); - tag_extracted_name = parent_.getTagsForName(name_str, tags); - absl::string_view truncated_name = parent_.truncateStatNameIfNeeded(name_str); - if (truncated_name.size() < name_str.size()) { - truncated_name_storage = std::make_unique(truncated_name, symbolTable()); - name = truncated_name_storage->statName(); - } - }*/ - // Manages the truncation and tag-extration of stat names. Tag extraction occurs // on the original, untruncated name so the extraction can complete properly, // even if the tag values are partially truncated. class TagExtraction { public: TagExtraction(ThreadLocalStoreImpl& tls, StatName name) { - std::string name_str = tls.symbolTable().toString(name); - tag_extracted_name_ = tls.tagProducer().produceTags(name_str, tags_); + tls.symbolTable().callWithStringView(name, [this, &tls](absl::string_view name_str) { + tag_extracted_name_ = tls.tagProducer().produceTags(name_str, tags_); + }); } const std::vector& tags() { return tags_; } From 91ec93ce2b1a76e5b57de5b8689232b629952548 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 16:28:05 -0400 Subject: [PATCH 09/39] format Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 2 +- include/envoy/stats/tag_producer.h | 3 +-- source/common/stats/fake_symbol_table_impl.h | 10 ++++------ source/common/stats/symbol_table_impl.cc | 17 +++++++++++------ source/common/stats/symbol_table_impl.h | 9 +++------ source/common/stats/tag_extractor_impl.cc | 7 ++++--- test/common/stats/heap_stat_data_test.cc | 3 +-- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 8bc2d9dbed3c..1d37e9b759da 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -30,7 +30,7 @@ class StatNameList; * a vptr overhead per object, and the representation is shared between the * SymbolTable implementations, so this is just a pre-declare. */ -//class SymbolEncoding; +// class SymbolEncoding; /** * SymbolTable manages a namespace optimized for stat names, exploiting their diff --git a/include/envoy/stats/tag_producer.h b/include/envoy/stats/tag_producer.h index 3cf2b27da128..9da9ee106037 100644 --- a/include/envoy/stats/tag_producer.h +++ b/include/envoy/stats/tag_producer.h @@ -22,8 +22,7 @@ class TagProducer { * @param metric_name std::string a name of Stats::Metric (Counter, Gauge, Histogram). * @param tags std::vector a set of Stats::Tag. */ - virtual std::string produceTags(absl::string_view metric_name, - std::vector& tags) const PURE; + virtual std::string produceTags(absl::string_view metric_name, std::vector& tags) const PURE; }; typedef std::unique_ptr TagProducerPtr; diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index 3c62eba638ee..d08ac14cefca 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -54,8 +54,8 @@ class FakeSymbolTableImpl : public SymbolTable { * representation during encoding and prior to retained allocation. */ class Encoding { - public: - //Encoding() : storage_(nullptr) {} + public: + // Encoding() : storage_(nullptr) {} void fromString(absl::string_view str) { storage_ = std::make_unique(str.size() + 2); @@ -97,11 +97,9 @@ class FakeSymbolTableImpl : public SymbolTable { return bytes_required; } - StoragePtr transferStorage() { - return std::move(storage_); - } + StoragePtr transferStorage() { return std::move(storage_); } - private: + private: StoragePtr storage_; }; diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index d609b8697fb2..34798b0bf020 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -60,7 +60,8 @@ void SymbolTableImpl::Encoding::addSymbol(Symbol symbol) { } while (symbol != 0); } -SymbolVec SymbolTableImpl::Encoding::decodeSymbols(const SymbolTable::Storage array, uint64_t size) { +SymbolVec SymbolTableImpl::Encoding::decodeSymbols(const SymbolTable::Storage array, + uint64_t size) { SymbolVec symbol_vec; Symbol symbol = 0; for (uint32_t shift = 0; size > 0; --size, ++array) { @@ -149,11 +150,12 @@ uint64_t SymbolTableImpl::numSymbols() const { } std::string SymbolTableImpl::toString(const StatName& stat_name) const { - return decodeSymbolVec(SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize())); + return decodeSymbolVec( + SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize())); } void SymbolTableImpl::callWithStringView(StatName stat_name, - const std::function& fn) const { + const std::function& fn) const { fn(toString(stat_name)); } @@ -172,7 +174,8 @@ std::string SymbolTableImpl::decodeSymbolVec(const SymbolVec& symbols) const { void SymbolTableImpl::incRefCount(const StatName& stat_name) { // Before taking the lock, decode the array of symbols from the SymbolTable::Storage. - SymbolVec symbols = SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); + SymbolVec symbols = + SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); Thread::LockGuard lock(lock_); for (Symbol symbol : symbols) { @@ -188,7 +191,8 @@ void SymbolTableImpl::incRefCount(const StatName& stat_name) { void SymbolTableImpl::free(const StatName& stat_name) { // Before taking the lock, decode the array of symbols from the SymbolTable::Storage. - SymbolVec symbols = SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); + SymbolVec symbols = + SymbolTableImpl::Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()); Thread::LockGuard lock(lock_); for (Symbol symbol : symbols) { @@ -337,7 +341,8 @@ SymbolTable::StoragePtr SymbolTableImpl::join(const std::vector& stat_ return bytes; } -void SymbolTableImpl::populateList(absl::string_view* names, int32_t num_names, StatNameList& list) { +void SymbolTableImpl::populateList(absl::string_view* names, int32_t num_names, + StatNameList& list) { RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); // First encode all the names. diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 8c947490385d..518f72a6bd4e 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -75,7 +75,7 @@ class SymbolTableImpl : public SymbolTable { * representation during encoding and prior to retained allocation. */ class Encoding { - public: + public: Encoding() = default; /** @@ -129,11 +129,10 @@ class SymbolTableImpl : public SymbolTable { void swap(Encoding& src) { vec_.swap(src.vec_); } - private: + private: std::vector vec_; }; - SymbolTableImpl(); ~SymbolTableImpl() override; @@ -387,9 +386,7 @@ class StatNameList { public: ~StatNameList(); - void moveStorageIntoList(SymbolTable::StoragePtr&& storage) { - storage_ = std::move(storage); - } + void moveStorageIntoList(SymbolTable::StoragePtr&& storage) { storage_ = std::move(storage); } /** * @return true if populate() has been called on this list. diff --git a/source/common/stats/tag_extractor_impl.cc b/source/common/stats/tag_extractor_impl.cc index 010932deb0be..761e82caa7b2 100644 --- a/source/common/stats/tag_extractor_impl.cc +++ b/source/common/stats/tag_extractor_impl.cc @@ -77,9 +77,10 @@ bool TagExtractorImpl::extractTag(absl::string_view stat_name, std::vector& std::match_results match; // The regex must match and contain one or more subexpressions (all after the first are ignored). - //std::string stat_name_str = std::string(stat_name); - if (std::regex_search( - stat_name.begin(), stat_name.end(), match, regex_) && match.size() > 1) { + // std::string stat_name_str = std::string(stat_name); + if (std::regex_search(stat_name.begin(), stat_name.end(), match, + regex_) && + match.size() > 1) { // remove_subexpr is the first submatch. It represents the portion of the string to be removed. const auto& remove_subexpr = match[1]; diff --git a/test/common/stats/heap_stat_data_test.cc b/test/common/stats/heap_stat_data_test.cc index 3c56edfcbe35..faab893e4e50 100644 --- a/test/common/stats/heap_stat_data_test.cc +++ b/test/common/stats/heap_stat_data_test.cc @@ -1,8 +1,7 @@ #include -#include "common/stats/heap_stat_data.h" - #include "common/stats/fake_symbol_table_impl.h" +#include "common/stats/heap_stat_data.h" #include "common/stats/stats_options_impl.h" #include "test/test_common/logging.h" From c02132c4dd1590252e7945ca7d08cb0ba9978286 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 17:29:24 -0400 Subject: [PATCH 10/39] cleanups. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 16 +++++++++++++++- source/common/stats/fake_symbol_table_impl.h | 18 +++++------------- source/common/stats/symbol_table_impl.h | 2 -- test/common/stats/thread_local_store_test.cc | 4 ++-- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 1d37e9b759da..d518fd2e099d 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -122,7 +122,8 @@ class SymbolTable { * construction time to enable StatNameList to be instantiated directly in * a class that doesn't have a live SymbolTable when it is constructed. * - * @param encodings The list names to encode. + * @param names A pointer to the first name in an array. + * @param num_names The number of names. * @param symbol_table The symbol table in which to encode the names. */ virtual void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) PURE; @@ -131,6 +132,19 @@ class SymbolTable { virtual void debugPrint() const PURE; #endif + /** + * Calls the provided function with a string-view representation of the + * elaborated name. This is useful during the interim period when we + * are using FakeSymbolTableImpl, to avoid an extra allocation. Once + * we migrate to using SymbolTableImpl, this interface will no longer + * be helpful and can be removed. The reason it's useful now is that + * it makes up, in part, for some extra runtime overhead that is spent + * on the SymbolTable abstraction and API, without getting any benefit + * from the improved representation. + * + * @param stat_name The stat name. + * @param fn The function to call with the elaborated stat name as a string_view. + */ virtual void callWithStringView(StatName stat_name, const std::function& fn) const PURE; diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index d08ac14cefca..e5f83883c7d5 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -55,23 +55,12 @@ class FakeSymbolTableImpl : public SymbolTable { */ class Encoding { public: - // Encoding() : storage_(nullptr) {} - void fromString(absl::string_view str) { storage_ = std::make_unique(str.size() + 2); uint8_t* p = saveLengthToBytesReturningNext(str.size(), storage_.get()); memcpy(p, str.data(), str.size()); } - /*Encoding& operator=(Encoding&& src) { - storage_ = std::move(src.storage_); - return *this; - }*/ - - /*void swap(Encoding& rhs) { - std::swap(rhs.storage_, storage_); - }*/ - /** * Before destructing SymbolEncoding, you must call moveToStorage. This * transfers ownership, and in particular, the responsibility to call @@ -97,14 +86,17 @@ class FakeSymbolTableImpl : public SymbolTable { return bytes_required; } + /** + * Removes the storage from this, and returns it to the caller. + * @return the allocated storage. + */ StoragePtr transferStorage() { return std::move(storage_); } private: StoragePtr storage_; }; - void encode(absl::string_view name, Encoding& encoding) { encoding.fromString(name); } - + // SymbolTable void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) override { RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 518f72a6bd4e..be3b0011e0fe 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -76,8 +76,6 @@ class SymbolTableImpl : public SymbolTable { */ class Encoding { public: - Encoding() = default; - /** * Before destructing SymbolEncoding, you must call moveToStorage. This * transfers ownership, and in particular, the responsibility to call diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index d7bdd1426bac..989355a2581e 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -709,7 +709,7 @@ TEST_F(HeapStatsThreadLocalStoreTest, MemoryWithoutTls) { 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); EXPECT_LT(start_mem, end_mem); - EXPECT_LT(end_mem - start_mem, 13 * million); // actual value: 12443472 as of Nov 7, 2018 + EXPECT_LT(end_mem - start_mem, 20 * million); // actual value: 19601552 as of March 14, 2019 } TEST_F(HeapStatsThreadLocalStoreTest, MemoryWithTls) { @@ -732,7 +732,7 @@ TEST_F(HeapStatsThreadLocalStoreTest, MemoryWithTls) { 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); EXPECT_LT(start_mem, end_mem); - EXPECT_LT(end_mem - start_mem, 16 * million); // actual value: 15722832 as of Nov 7, 2018 + EXPECT_LT(end_mem - start_mem, 23 * million); // actual value: 22880912 as of March 14, 2019 } TEST_F(StatsThreadLocalStoreTest, ShuttingDown) { From 31c8a61a487e6d93161e151bf0cb7a83617adbe5 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 18:16:34 -0400 Subject: [PATCH 11/39] remove commented out reference to std::cerr, caught by spelling :) Signed-off-by: Joshua Marantz --- source/common/stats/thread_local_store.h | 1 - 1 file changed, 1 deletion(-) diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 7af9757ac587..f988118bf18f 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -219,7 +219,6 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo SymbolTable& symbolTable() override { return parent_.symbolTable(); } Counter& counter(const std::string& name) override { - // std::cerr << "counter(" << name << ")" << std::endl; StatNameTempStorage storage(name, symbolTable()); return counterFromStatName(storage.statName()); } From cb9e6f29e1f32967191fa405c8fc5af8f4c76773 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 18:43:46 -0400 Subject: [PATCH 12/39] Removed no-longer-needed class FakeSymbolTableImpl::Encoding. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 10 ---- source/common/stats/fake_symbol_table_impl.h | 54 ++------------------ source/common/stats/symbol_table_impl.h | 8 ++- 3 files changed, 10 insertions(+), 62 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index d518fd2e099d..4ac177bfd895 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -22,16 +22,6 @@ class StatName; class StatNameList; -/** - * Intermediate representation for a stat-name. This helps store multiple names - * in a single packed allocation. First we encode each desired name, then sum - * their sizes for the single packed allocation. This is used to store - * MetricImpl's tags and tagExtractedName. Like StatName, we don't want to pay - * a vptr overhead per object, and the representation is shared between the - * SymbolTable implementations, so this is just a pre-declare. - */ -// class SymbolEncoding; - /** * SymbolTable manages a namespace optimized for stat names, exploiting their * typical composition from "."-separated tokens, with a significant overlap diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index e5f83883c7d5..fc9971a1c68d 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -49,53 +49,6 @@ namespace Stats { */ class FakeSymbolTableImpl : public SymbolTable { public: - /** - * Represents an 8-bit encoding of a vector of symbols, used as a transient - * representation during encoding and prior to retained allocation. - */ - class Encoding { - public: - void fromString(absl::string_view str) { - storage_ = std::make_unique(str.size() + 2); - uint8_t* p = saveLengthToBytesReturningNext(str.size(), storage_.get()); - memcpy(p, str.data(), str.size()); - } - - /** - * Before destructing SymbolEncoding, you must call moveToStorage. This - * transfers ownership, and in particular, the responsibility to call - * SymbolTable::clear() on all referenced symbols. If we ever wanted - * to be able to destruct a SymbolEncoding without transferring it - * we could add a clear(SymbolTable&) method. - */ - ~Encoding() { ASSERT(storage_ == nullptr); } - - uint64_t bytesRequired() const { return StatName(storage_.get()).size(); } - - /** - * Moves the contents of the vector into an allocated array. The array - * must have been allocated with bytesRequired() bytes. - * - * @param array destination memory to receive the encoded bytes. - * @return uint64_t the number of bytes transferred. - */ - uint64_t moveToStorage(SymbolTable::Storage array) { - uint64_t bytes_required = bytesRequired(); - memcpy(array, storage_.get(), bytes_required); - storage_.reset(); - return bytes_required; - } - - /** - * Removes the storage from this, and returns it to the caller. - * @return the allocated storage. - */ - StoragePtr transferStorage() { return std::move(storage_); } - - private: - StoragePtr storage_; - }; - // SymbolTable void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) override { RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); @@ -177,9 +130,10 @@ class FakeSymbolTableImpl : public SymbolTable { } StoragePtr stringToStorage(absl::string_view name) const { - Encoding encoding; - encoding.fromString(name); - return encoding.transferStorage(); + auto storage = std::make_unique(name.size() + 2); + uint8_t* p = saveLengthToBytesReturningNext(name.size(), storage.get()); + memcpy(p, name.data(), name.size()); + return storage; } }; diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index be3b0011e0fe..d30044845bb4 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -71,8 +71,12 @@ using SymbolVec = std::vector; class SymbolTableImpl : public SymbolTable { public: /** - * Represents an 8-bit encoding of a vector of symbols, used as a transient - * representation during encoding and prior to retained allocation. + * Intermediate representation for a stat-name. This helps store multiple names + * in a single packed allocation. First we encode each desired name, then sum + * their sizes for the single packed allocation. This is used to store + * MetricImpl's tags and tagExtractedName. Like StatName, we don't want to pay + * a vptr overhead per object, and the representation is shared between the + * SymbolTable implementations, so this is just a pre-declare. */ class Encoding { public: From 944b81153f9cd1b12ac6c10e62912f553314d774 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 19:47:47 -0400 Subject: [PATCH 13/39] Remove commented out blocks, add 'override' attribute to virtual dtor, etc. Signed-off-by: Joshua Marantz --- source/common/stats/fake_symbol_table_impl.h | 8 ++++++ .../common/stats/stat_data_allocator_impl.h | 17 +++--------- source/common/stats/symbol_table_impl.cc | 26 ------------------- source/server/config_validation/server.h | 1 - test/common/stats/symbol_table_impl_test.cc | 11 -------- test/integration/server.cc | 16 ------------ 6 files changed, 12 insertions(+), 67 deletions(-) diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index fc9971a1c68d..40a329ddcde6 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -51,6 +51,14 @@ class FakeSymbolTableImpl : public SymbolTable { public: // SymbolTable void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) override { + // This implementation of populateList is similar to + // SymboLableImpl::populateList. This variant is more efficient for + // FakeSymbolTableImpl, because it avoid "encoding" each name in names. The + // strings are laid out abutting each other with 2-byte length prefixes, so + // encoding isn't needed, and doing a dummy encoding step would cost one + // memory allocation per element, adding significant overhead as measured by + // thread_local_store_speed_test. + RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded"); // First encode all the names. diff --git a/source/common/stats/stat_data_allocator_impl.h b/source/common/stats/stat_data_allocator_impl.h index 3582daabea6f..e3ae24ca5af3 100644 --- a/source/common/stats/stat_data_allocator_impl.h +++ b/source/common/stats/stat_data_allocator_impl.h @@ -31,15 +31,6 @@ template class StatDataAllocatorImpl : public StatDataAllocator public: explicit StatDataAllocatorImpl(SymbolTable& symbol_table) : symbol_table_(symbol_table) {} - /** - * @param name the full name of the stat. - * @return StatData* a data block for a given stat name or nullptr if there is no more memory - * available for stats. The allocator should return a reference counted data location - * by name if one already exists with the same name. This is used for intra-process - * scope swapping as well as inter-process hot restart. - */ - // virtual StatData* alloc(StatName name) PURE; - /** * Free a raw stat data block. The allocator should handle reference counting and only truly * free the block if it is no longer needed. @@ -69,7 +60,7 @@ template class CounterImpl : public Counter, public MetricImpl CounterImpl(StatData& data, StatDataAllocatorImpl& alloc, absl::string_view tag_extracted_name, const std::vector& tags) : MetricImpl(tag_extracted_name, tags, alloc.symbolTable()), data_(data), alloc_(alloc) {} - ~CounterImpl() { + ~CounterImpl() override { alloc_.free(data_); MetricImpl::clear(); } @@ -102,7 +93,7 @@ template class CounterImpl : public Counter, public MetricImpl class NullCounterImpl : public Counter, NullMetricImpl { public: explicit NullCounterImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} - ~NullCounterImpl() { MetricImpl::clear(); } + ~NullCounterImpl() override { MetricImpl::clear(); } void add(uint64_t) override {} void inc() override {} @@ -119,7 +110,7 @@ template class GaugeImpl : public Gauge, public MetricImpl { GaugeImpl(StatData& data, StatDataAllocatorImpl& alloc, absl::string_view tag_extracted_name, const std::vector& tags) : MetricImpl(tag_extracted_name, tags, alloc.symbolTable()), data_(data), alloc_(alloc) {} - ~GaugeImpl() { + ~GaugeImpl() override { alloc_.free(data_); MetricImpl::clear(); } @@ -158,7 +149,7 @@ template class GaugeImpl : public Gauge, public MetricImpl { class NullGaugeImpl : public Gauge, NullMetricImpl { public: explicit NullGaugeImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} - ~NullGaugeImpl() { MetricImpl::clear(); } + ~NullGaugeImpl() override { MetricImpl::clear(); } void add(uint64_t) override {} void inc() override {} diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index 34798b0bf020..b2cfb5b7374a 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -369,32 +369,6 @@ void SymbolTableImpl::populateList(absl::string_view* names, int32_t num_names, StatNameList::~StatNameList() { ASSERT(!populated()); } -/*void StatNameList::populate(const std::vector& names, - SymbolTableType& symbol_table) { - RELEASE_ASSERT(names.size() < 256, "Maximum number elements in a StatNameList exceeded"); - - // First encode all the names. - size_t total_size_bytes = 1; // one byte for holding the number of names - std::vector encodings; - encodings.resize(names.size()); - int index = 0; - for (auto& name : names) { - SymbolEncoding encoding = symbol_table.fooEncode(name); - total_size_bytes += encoding.bytesRequired(); - encodings[index++].swap(encoding); - } - - // Now allocate the exact number of bytes required and move the encodings - // into storage. - storage_ = std::make_unique(total_size_bytes); - uint8_t* p = &storage_[0]; - *p++ = encodings.size(); - for (auto& encoding : encodings) { - p += encoding.moveToStorage(p); - } - ASSERT(p == &storage_[0] + total_size_bytes); -}*/ - void StatNameList::iterate(const std::function& f) const { uint8_t* p = &storage_[0]; uint32_t num_elements = *p++; diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index c5a4790e2f8c..135d7ddced60 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -95,7 +95,6 @@ class ValidationInstance : Logger::Loggable, Stats::SymbolTable& symbolTable() override { return stats_store_.symbolTable(); } TimeSource& timeSource() override { return api_->timeSource(); } Envoy::MutexTracer* mutexTracer() override { return mutex_tracer_; } - // Http::CodeStats& codeStats() override { return code_stats_; } std::chrono::milliseconds statsFlushInterval() const override { return config_.statsFlushInterval(); diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index 9ad0f4e1912e..36c86aaf4365 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -276,17 +276,6 @@ TEST_P(StatNameTest, List) { StatNameList name_list; EXPECT_FALSE(name_list.populated()); table_->populateList(names, ARRAY_SIZE(names), name_list); - /* - switch (GetParam()) { - case SymbolTableType::Real: - name_list.populate(names, ARRAY_SIZE(names), *real_symbol_table_); - break; - case SymbolTableType::Fake: - name_list.populate(names, ARRAY_SIZE(names), *fake_symbol_table_); - break; - } - */ - EXPECT_TRUE(name_list.populated()); // First, decode only the first name. diff --git a/test/integration/server.cc b/test/integration/server.cc index 3ff5bf9ccbed..8d2bd72403bf 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -141,22 +141,6 @@ void IntegrationTestServer::serverReady() { void IntegrationTestServer::threadRoutine(const Network::Address::IpVersion version, bool deterministic) { OptionsImpl options(Server::createTestOptionsImpl(config_path_, "", version)); - - /* - // TODO(jmarantz): Sadly, we use a mock symbol table here, as plumbing the - // real symbol table through the mocking hierarchy -- which generally - // constructs hierarchies of objects with no context, is too daunting. I think - // the right thing to do is to avoid mocks in integration tests. - Test::Global symbol_table; - Server::HotRestartNopImpl restarter(*symbol_table); - Thread::MutexBasicLockable lock; - - ThreadLocal::InstanceImpl tls; - Stats::HeapStatDataAllocator stats_allocator(*symbol_table); - Stats::StatsOptionsImpl stats_options; - Stats::ThreadLocalStoreImpl stats_store(stats_options, stats_allocator); - stat_store_ = &stats_store; - */ Thread::MutexBasicLockable lock; Runtime::RandomGeneratorPtr random_generator; From e3a9eae546d593911fc8fec3d646cd5dee8b0918 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 23:11:17 -0400 Subject: [PATCH 14/39] Remove commented-out code. Signed-off-by: Joshua Marantz --- source/common/stats/heap_stat_data.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/source/common/stats/heap_stat_data.cc b/source/common/stats/heap_stat_data.cc index 9ccca38aafd8..af9b19b71073 100644 --- a/source/common/stats/heap_stat_data.cc +++ b/source/common/stats/heap_stat_data.cc @@ -34,7 +34,6 @@ HeapStatData& HeapStatDataAllocator::alloc(StatName name) { lock.release(); if (ret.second) { - // symbolTable().incRefCount(existing_data->statName()); return *data_ptr.release(); } ++existing_data->ref_count_; From 8c72c2d5b404121c7a657aea297fb1a3dcf921dd Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 14 Mar 2019 23:19:06 -0400 Subject: [PATCH 15/39] tweaks pulled over from #6293. Signed-off-by: Joshua Marantz --- include/envoy/stats/symbol_table.h | 2 +- source/common/stats/fake_symbol_table_impl.h | 6 ++--- source/common/stats/symbol_table_impl.cc | 8 +++---- source/common/stats/symbol_table_impl.h | 23 ++++++++++++++++++-- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 4ac177bfd895..76fcc388361a 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -129,7 +129,7 @@ class SymbolTable { * we migrate to using SymbolTableImpl, this interface will no longer * be helpful and can be removed. The reason it's useful now is that * it makes up, in part, for some extra runtime overhead that is spent - * on the SymbolTable abstraction and API, without getting any benefit + * on the SymbolTable abstraction and API, without getting full benefit * from the improved representation. * * @param stat_name The stat name. diff --git a/source/common/stats/fake_symbol_table_impl.h b/source/common/stats/fake_symbol_table_impl.h index 40a329ddcde6..28296a853421 100644 --- a/source/common/stats/fake_symbol_table_impl.h +++ b/source/common/stats/fake_symbol_table_impl.h @@ -52,7 +52,7 @@ class FakeSymbolTableImpl : public SymbolTable { // SymbolTable void populateList(absl::string_view* names, int32_t num_names, StatNameList& list) override { // This implementation of populateList is similar to - // SymboLableImpl::populateList. This variant is more efficient for + // SymbolTableImpl::populateList. This variant is more efficient for // FakeSymbolTableImpl, because it avoid "encoding" each name in names. The // strings are laid out abutting each other with 2-byte length prefixes, so // encoding isn't needed, and doing a dummy encoding step would cost one @@ -70,7 +70,7 @@ class FakeSymbolTableImpl : public SymbolTable { // Now allocate the exact number of bytes required and move the encodings // into storage. - auto storage = std::make_unique(total_size_bytes); + auto storage = std::make_unique(total_size_bytes); uint8_t* p = &storage[0]; *p++ = num_names; for (int32_t i = 0; i < num_names; ++i) { @@ -111,7 +111,7 @@ class FakeSymbolTableImpl : public SymbolTable { #endif StoragePtr copyToBytes(absl::string_view name) override { - auto bytes = std::make_unique(name.size() + StatNameSizeEncodingBytes); + auto bytes = std::make_unique(name.size() + StatNameSizeEncodingBytes); uint8_t* buffer = saveLengthToBytesReturningNext(name.size(), bytes.get()); memcpy(buffer, name.data(), name.size()); return bytes; diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index b2cfb5b7374a..d87a3691263a 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -298,7 +298,7 @@ void SymbolTableImpl::debugPrint() const { SymbolTable::StoragePtr SymbolTableImpl::copyToBytes(absl::string_view name) { Encoding encoding; encode(name, encoding); - auto bytes = std::make_unique(encoding.bytesRequired()); + auto bytes = std::make_unique(encoding.bytesRequired()); encoding.moveToStorage(bytes.get()); return bytes; } @@ -308,7 +308,7 @@ StatNameStorage::StatNameStorage(absl::string_view name, SymbolTable& table) StatNameStorage::StatNameStorage(StatName src, SymbolTable& table) { uint64_t size = src.size(); - bytes_ = std::make_unique(size); + bytes_ = std::make_unique(size); src.copyToStorage(bytes_.get()); table.incRefCount(statName()); } @@ -331,7 +331,7 @@ SymbolTable::StoragePtr SymbolTableImpl::join(const std::vector& stat_ for (StatName stat_name : stat_names) { num_bytes += stat_name.dataSize(); } - auto bytes = std::make_unique(num_bytes + StatNameSizeEncodingBytes); + auto bytes = std::make_unique(num_bytes + StatNameSizeEncodingBytes); uint8_t* p = saveLengthToBytesReturningNext(num_bytes, bytes.get()); for (StatName stat_name : stat_names) { num_bytes = stat_name.dataSize(); @@ -357,7 +357,7 @@ void SymbolTableImpl::populateList(absl::string_view* names, int32_t num_names, // Now allocate the exact number of bytes required and move the encodings // into storage. - auto storage = std::make_unique(total_size_bytes); + auto storage = std::make_unique(total_size_bytes); uint8_t* p = &storage[0]; *p++ = num_names; for (auto& encoding : encodings) { diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index d30044845bb4..66d80dc9ed59 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -388,8 +388,6 @@ class StatNameList { public: ~StatNameList(); - void moveStorageIntoList(SymbolTable::StoragePtr&& storage) { storage_ = std::move(storage); } - /** * @return true if populate() has been called on this list. */ @@ -415,6 +413,27 @@ class StatNameList { void clear(SymbolTable& symbol_table); private: + friend class FakeSymbolTableImpl; + friend class SymbolTableImpl; + + /** + * Moves the specified storage into the list. The storage format is an + * array of bytes, organized like this: + * + * [0] The number of elements in the list (must be < 256). + * [1] low order 8 bits of the number of symbols in the first element. + * [2] high order 8 bits of the number of symbols in the first element. + * [3...] the symbols in the first element. + * ... + * + * + * For FakeSymbolTableImpl, each symbol is a single char, casted into a + * uint8_t. For SymbolTableImpl, each symbol is 1 or more bytes, in a + * variable-length encoding. See SymbolTableImpl::Encoding::addSymbol for + * details. + */ + void moveStorageIntoList(SymbolTable::StoragePtr&& storage) { storage_ = std::move(storage); } + SymbolTable::StoragePtr storage_; }; From 26700c826921e065bb891e130749b65d41b86e3b Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Fri, 15 Mar 2019 14:38:46 -0400 Subject: [PATCH 16/39] back out some changes that are not needed. Signed-off-by: Joshua Marantz --- source/common/http/user_agent.cc | 3 +-- source/common/stats/heap_stat_data.h | 2 +- source/common/upstream/cluster_manager_impl.h | 1 - test/common/http/user_agent_test.cc | 2 +- test/integration/integration.cc | 1 - test/integration/integration.h | 4 ---- test/integration/integration_admin_test.cc | 2 +- test/test_common/utility.cc | 4 ---- test/test_common/utility.h | 8 -------- 9 files changed, 4 insertions(+), 23 deletions(-) diff --git a/source/common/http/user_agent.cc b/source/common/http/user_agent.cc index 75c4befe294c..57cb82e57103 100644 --- a/source/common/http/user_agent.cc +++ b/source/common/http/user_agent.cc @@ -21,8 +21,7 @@ void UserAgent::completeConnectionLength(Stats::Timespan& span) { return; } - Stats::Histogram& histogram = scope_->histogram(prefix_ + "downstream_cx_length_ms"); - histogram.recordValue(span.getRawDuration().count()); + scope_->histogram(prefix_ + "downstream_cx_length_ms").recordValue(span.getRawDuration().count()); } void UserAgent::initializeFromHeaders(const HeaderMap& headers, const std::string& prefix, diff --git a/source/common/stats/heap_stat_data.h b/source/common/stats/heap_stat_data.h index b87fe813be3c..5f770c58ecf7 100644 --- a/source/common/stats/heap_stat_data.h +++ b/source/common/stats/heap_stat_data.h @@ -67,7 +67,7 @@ template class HeapStat : public Stat { class HeapStatDataAllocator : public StatDataAllocatorImpl { public: HeapStatDataAllocator(SymbolTable& symbol_table) : StatDataAllocatorImpl(symbol_table) {} - virtual ~HeapStatDataAllocator(); + ~HeapStatDataAllocator() override; HeapStatData& alloc(StatName name); void free(HeapStatData& data) override; diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 23d23610121c..0e9e8eb52667 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -451,7 +451,6 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable original_histogram; //(stat_store.symbolTable()); + NiceMock original_histogram; Event::SimulatedTimeSystem time_system; Stats::Timespan span(original_histogram, time_system); diff --git a/test/integration/integration.cc b/test/integration/integration.cc index 25a0a5415c81..e38b97c209ee 100644 --- a/test/integration/integration.cc +++ b/test/integration/integration.cc @@ -389,7 +389,6 @@ void BaseIntegrationTest::createGeneratedApiTestServer(const std::string& bootst test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); registerTestServerPorts(port_names); } - // stats_ = std::make_unique(test_server_->stats().symbolTable()); } void BaseIntegrationTest::createApiTestServer(const ApiFilesystemConfig& api_filesystem_config, diff --git a/test/integration/integration.h b/test/integration/integration.h index 9e451fd7da16..cb7e8c937b7f 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -13,7 +13,6 @@ #include "test/integration/utility.h" #include "test/mocks/buffer/mocks.h" #include "test/mocks/server/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" @@ -245,7 +244,6 @@ class BaseIntegrationTest : Logger::Loggable { public: Event::DispatcherPtr dispatcher_; - // Api::ApiPtr api_; /** * Open a connection to Envoy, send a series of bytes, and return the @@ -287,8 +285,6 @@ class BaseIntegrationTest : Logger::Loggable { uint32_t fake_upstreams_count_{1}; spdlog::level::level_enum default_log_level_; IntegrationTestServerPtr test_server_; - // std::unique_ptr stats_; - // A map of keys to port names. Generally the names are pulled from the v2 listener name // but if a listener is created via ADS, it will be from whatever key is used with registerPort. TestEnvironment::PortMap port_map_; diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index 9f8cc10fae93..2ce9f3b73b34 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -465,7 +465,7 @@ class StatsMatcherIntegrationTest } void makeRequest() { response_ = IntegrationUtil::makeSingleRequest(lookupPort("admin"), "GET", "/stats", "", - downstreamProtocol(), version_, "host", ""); + downstreamProtocol(), version_); ASSERT_TRUE(response_->complete()); EXPECT_STREQ("200", response_->headers().Status()->value().c_str()); } diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index b72e542011f6..e9bbcd3e06ae 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -148,10 +148,6 @@ Stats::GaugeSharedPtr TestUtility::findGauge(Stats::Store& store, const std::str return findByName(store.gauges(), name); } -Stats::HistogramSharedPtr TestUtility::findHistogram(Stats::Store& store, const std::string& name) { - return findByName(store.histograms(), name); -} - std::list TestUtility::makeDnsResponse(const std::list& addresses) { std::list ret; diff --git a/test/test_common/utility.h b/test/test_common/utility.h index ff472dd7d38a..d30c0f9a7569 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -166,14 +166,6 @@ class TestUtility { */ static Stats::GaugeSharedPtr findGauge(Stats::Store& store, const std::string& name); - /** - * Find a histogram in a stats store. - * @param store supplies the stats store. - * @param name supplies the name to search for. - * @return Stats::HistogramSharedPtr the gauge or nullptr if there is none. - */ - static Stats::HistogramSharedPtr findHistogram(Stats::Store& store, const std::string& name); - /** * Convert a string list of IP addresses into a list of network addresses usable for DNS * response testing. From 323b005a81f4e64bb315d7cb5da449fbeab485e0 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Fri, 15 Mar 2019 15:23:52 -0400 Subject: [PATCH 17/39] backout router changes which are not needed. Signed-off-by: Joshua Marantz --- source/common/router/router.cc | 5 ----- source/common/router/router.h | 17 ++++++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 74bf467204c9..f15c6aedc3a4 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -179,11 +179,6 @@ FilterUtility::finalTimeout(const RouteEntry& route, Http::HeaderMap& request_he return timeout; } -Filter::Filter(FilterConfig& config) - : config_(config), downstream_response_started_(false), downstream_end_stream_(false), - do_shadowing_(false), is_retry_(false), - attempting_internal_redirect_with_complete_stream_(false) {} - Filter::~Filter() { // Upstream resources should already have been cleaned. ASSERT(!upstream_request_); diff --git a/source/common/router/router.h b/source/common/router/router.h index 4ec7b0997918..8bbd312974e9 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -103,8 +103,8 @@ class FilterConfig { : scope_(scope), local_info_(local_info), cm_(cm), runtime_(runtime), random_(random), stats_{ALL_ROUTER_STATS(POOL_COUNTER_PREFIX(scope, stat_prefix))}, emit_dynamic_stats_(emit_dynamic_stats), start_child_span_(start_child_span), - suppress_envoy_headers_(suppress_envoy_headers), shadow_writer_(std::move(shadow_writer)), - time_source_(time_source), http_context_(http_context) {} + suppress_envoy_headers_(suppress_envoy_headers), http_context_(http_context), + shadow_writer_(std::move(shadow_writer)), time_source_(time_source) {} FilterConfig(const std::string& stat_prefix, Server::Configuration::FactoryContext& context, ShadowWriterPtr&& shadow_writer, @@ -121,7 +121,6 @@ class FilterConfig { ShadowWriter& shadowWriter() { return *shadow_writer_; } TimeSource& timeSource() { return time_source_; } - Http::Context& httpContext() { return http_context_; } Stats::Scope& scope_; const LocalInfo::LocalInfo& local_info_; @@ -133,11 +132,11 @@ class FilterConfig { const bool start_child_span_; const bool suppress_envoy_headers_; std::list upstream_logs_; + Http::Context& http_context_; private: ShadowWriterPtr shadow_writer_; TimeSource& time_source_; - Http::Context& http_context_; }; typedef std::shared_ptr FilterConfigSharedPtr; @@ -149,8 +148,12 @@ class Filter : Logger::Loggable, public Http::StreamDecoderFilter, public Upstream::LoadBalancerContextBase { public: - explicit Filter(FilterConfig& config); - ~Filter() override; + Filter(FilterConfig& config) + : config_(config), downstream_response_started_(false), downstream_end_stream_(false), + do_shadowing_(false), is_retry_(false), + attempting_internal_redirect_with_complete_stream_(false) {} + + ~Filter(); // Http::StreamFilterBase void onDestroy() override; @@ -395,7 +398,7 @@ class Filter : Logger::Loggable, // and handle difference between gRPC and non-gRPC requests. void handleNon5xxResponseHeaders(const Http::HeaderMap& headers, bool end_stream); TimeSource& timeSource() { return config_.timeSource(); } - Http::Context& httpContext() { return config_.httpContext(); } + Http::Context& httpContext() { return config_.http_context_; } FilterConfig& config_; Http::StreamDecoderFilterCallbacks* callbacks_{}; From 94495965ce1d309ee84d6c7ca679a8e16506ee0e Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Fri, 15 Mar 2019 17:17:43 -0400 Subject: [PATCH 18/39] Remove more changes that are not clearly needed yet in this PR. Signed-off-by: Joshua Marantz --- .../filters/http/ext_authz/ext_authz_test.cc | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 506842d849c5..edb4f12d6e18 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -67,7 +67,7 @@ template class HttpFilterTestBase : public T { Filters::Common::ExtAuthz::RequestCallbacks* request_callbacks_{}; Http::TestHeaderMapImpl request_headers_; Buffer::OwnedImpl data_; - NiceMock stats_store_; + Stats::IsolatedStoreImpl stats_store_; NiceMock runtime_; NiceMock cm_; NiceMock local_info_; @@ -82,9 +82,7 @@ template class HttpFilterTestBase : public T { } }; -class HttpFilterTest : public HttpFilterTestBase { -public: -}; +class HttpFilterTest : public HttpFilterTestBase {}; using CreateFilterConfigFunc = envoy::config::filter::http::ext_authz::v2::ExtAuthz(); @@ -155,23 +153,6 @@ TEST_F(HttpFilterTest, MergeConfig) { EXPECT_EQ("value", merged_extensions.at("key")); } -class HttpExtAuthzFilterTestBase : public HttpFilterTest { -public: - void initConfig(envoy::config::filter::http::ext_authz::v2::ExtAuthz& proto_config) { - config_ = std::make_unique(proto_config, local_info_, stats_store_, runtime_, - http_context_); - } -}; - -class HttpExtAuthzFilterTest : public HttpExtAuthzFilterTestBase { -public: - void initialize(const std::string yaml) { - envoy::config::filter::http::ext_authz::v2::ExtAuthz proto_config{}; - MessageUtil::loadFromYaml(yaml, proto_config); - initConfig(proto_config); - } -}; - // Test when failure_mode_allow is NOT set and the response from the authorization service is Error // that the request is not allowed to continue. TEST_F(HttpFilterTest, ErrorFailClose) { From 61ea4e201bdf0dd61aa0b628d0343f61885e7276 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Tue, 19 Mar 2019 12:37:36 -0400 Subject: [PATCH 19/39] patches from #6299 Signed-off-by: Joshua Marantz --- include/envoy/server/instance.h | 5 ----- source/common/stats/isolated_store_impl.cc | 2 +- source/common/stats/isolated_store_impl.h | 3 ++- source/extensions/stat_sinks/hystrix/hystrix.cc | 4 ++-- source/server/config_validation/server.h | 1 - source/server/server.h | 1 - test/mocks/server/mocks.h | 1 - 7 files changed, 5 insertions(+), 12 deletions(-) diff --git a/include/envoy/server/instance.h b/include/envoy/server/instance.h index 9a09d81b7d14..a3d548eb3c77 100644 --- a/include/envoy/server/instance.h +++ b/include/envoy/server/instance.h @@ -203,11 +203,6 @@ class Instance { */ virtual TimeSource& timeSource() PURE; - /** - * @return the statistics symbol table. - */ - virtual Stats::SymbolTable& symbolTable() PURE; - /** * @return the flush interval of stats sinks. */ diff --git a/source/common/stats/isolated_store_impl.cc b/source/common/stats/isolated_store_impl.cc index bbee9bc1f87e..c7247028f031 100644 --- a/source/common/stats/isolated_store_impl.cc +++ b/source/common/stats/isolated_store_impl.cc @@ -17,7 +17,7 @@ namespace Stats { IsolatedStoreImpl::IsolatedStoreImpl() : IsolatedStoreImpl(std::make_unique()) {} -IsolatedStoreImpl::IsolatedStoreImpl(std::unique_ptr symbol_table) +IsolatedStoreImpl::IsolatedStoreImpl(std::unique_ptr&& symbol_table) : IsolatedStoreImpl(*symbol_table) { symbol_table_storage_ = std::move(symbol_table); } diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index 41f87ca66c0d..e09b0e59179a 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -60,7 +60,6 @@ template class IsolatedStatsCache { class IsolatedStoreImpl : public Store { public: IsolatedStoreImpl(); - explicit IsolatedStoreImpl(std::unique_ptr symbol_table); explicit IsolatedStoreImpl(SymbolTable& symbol_table); // Stats::Scope @@ -99,6 +98,8 @@ class IsolatedStoreImpl : public Store { void clear(); private: + IsolatedStoreImpl(std::unique_ptr&& symbol_table); + std::unique_ptr symbol_table_storage_; SymbolTable& symbol_table_; HeapStatDataAllocator alloc_; diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc index 2725e75f5687..2f4ad6155a06 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.cc +++ b/source/extensions/stat_sinks/hystrix/hystrix.cc @@ -268,7 +268,7 @@ const std::string HystrixSink::printRollingWindows() { HystrixSink::HystrixSink(Server::Instance& server, const uint64_t num_buckets) : server_(server), current_index_(num_buckets > 0 ? num_buckets : DEFAULT_NUM_BUCKETS), window_size_(current_index_ + 1), - cluster_upstream_rq_time_("cluster.upstream_rq_time", server.symbolTable()) { + cluster_upstream_rq_time_("cluster.upstream_rq_time", server.stats().symbolTable()) { Server::Admin& admin = server_.admin(); ENVOY_LOG(debug, "adding hystrix_event_stream endpoint to enable connection to hystrix dashboard"); @@ -276,7 +276,7 @@ HystrixSink::HystrixSink(Server::Instance& server, const uint64_t num_buckets) MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false); } -HystrixSink::~HystrixSink() { cluster_upstream_rq_time_.free(server_.symbolTable()); } +HystrixSink::~HystrixSink() { cluster_upstream_rq_time_.free(server_.stats().symbolTable()); } Http::Code HystrixSink::handlerHystrixEventStream(absl::string_view, Http::HeaderMap& response_headers, diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index 135d7ddced60..2369cda8777c 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -92,7 +92,6 @@ class ValidationInstance : Logger::Loggable, Http::Context& httpContext() override { return http_context_; } ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } - Stats::SymbolTable& symbolTable() override { return stats_store_.symbolTable(); } TimeSource& timeSource() override { return api_->timeSource(); } Envoy::MutexTracer* mutexTracer() override { return mutex_tracer_; } diff --git a/source/server/server.h b/source/server/server.h index bdd2eaea1d27..27b02a0722f4 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -184,7 +184,6 @@ class InstanceImpl : Logger::Loggable, public Instance { Http::Context& httpContext() override { return http_context_; } ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } - Stats::SymbolTable& symbolTable() override { return stats_store_.symbolTable(); } TimeSource& timeSource() override { return time_source_; } std::chrono::milliseconds statsFlushInterval() const override { diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index 9804af4797e4..0d1af3e4c14e 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -360,7 +360,6 @@ class MockInstance : public Instance { MOCK_METHOD0(localInfo, const LocalInfo::LocalInfo&()); MOCK_CONST_METHOD0(statsFlushInterval, std::chrono::milliseconds()); - Stats::SymbolTable& symbolTable() override { return stats_store_.symbolTable(); } TimeSource& timeSource() override { return time_system_; } std::unique_ptr secret_manager_; From 1abc30c0e601ff9443c79d4b87011c35fb9bd078 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Wed, 20 Mar 2019 09:14:15 -0400 Subject: [PATCH 20/39] Go back to making SharedStatNameStorageSet a class to assert we've called free(symbol_table) prior to destruction. Signed-off-by: Joshua Marantz --- source/common/stats/symbol_table_impl.cc | 30 +++++++++++++++++++++++ source/common/stats/symbol_table_impl.h | 17 ++++++++++--- source/common/stats/thread_local_store.cc | 27 +------------------- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index d87a3691263a..d5c566c230b7 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -326,6 +326,36 @@ void StatNameStorage::free(SymbolTable& table) { bytes_.reset(); } +SharedStatNameStorageSet::~SharedStatNameStorageSet() { + // free() must be called before destructing SharedStatNameStorageSet to + // decrement references to all symbols. + ASSERT(empty()); +} + +void SharedStatNameStorageSet::free(SymbolTable& symbol_table) { + // We must free() all symbols referenced in the set, otherwise the symbols + // will leak when the flat_hash_map superclass is destructed. They cannot + // self-destruct without an explicit free() as each individual StatNameStorage + // object does not have a reference to the symbol table, which would waste 8 + // bytes per stat-name. So we must iterate over the set and free it. But we + // don't want to mutate objects while they are in a set, so we just copy them, + // which is easy because they are shared_ptr. + + size_t sz = size(); + STACK_ARRAY(storage, SharedStatNameStorage, sz); + size_t i = 0; + for (const SharedStatNameStorage& name : *this) { + storage[i++] = name; + } + clear(); + + // Now that the associative container is clear, we can free all the referenced + // symbols. + for (i = 0; i < sz; ++i) { + storage[i]->free(symbol_table); + } +} + SymbolTable::StoragePtr SymbolTableImpl::join(const std::vector& stat_names) const { uint64_t num_bytes = 0; for (StatName stat_name : stat_names) { diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 6f1e640cbc75..c1922c02032c 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -492,9 +492,20 @@ struct HeterogeneousStatNameEqual { size_t operator()(const SharedStatNameStorage& a, StatName b) const { return a->statName() == b; } }; -using SharedStatNameStorageSet = - absl::flat_hash_set; +// Encapsulates a set of shared_ptr. We use a subclass here +// rather than a 'using' alias because we need to ensure that when the set is +// destructed, StatNameStorage::free(symbol_table) is called on each entry. It +// is a little easier at the call-site in thread_local_store.cc to implement +// this an explicit free() method, analogous to StatNameStorage::free(), +// compared to storing a SymbolTable reference in the class and doing the free +// in the destructor, like StatNameTempStorage. +class SharedStatNameStorageSet + : public absl::flat_hash_set { +public: + ~SharedStatNameStorageSet(); + void free(SymbolTable& symbol_table); +}; } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 618d8a27a7c1..6783c60ce504 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -192,31 +192,6 @@ void ThreadLocalStoreImpl::mergeInternal(PostMergeCb merge_complete_cb) { } } -static void freeSharedStatNameStorageSet(SymbolTable& symbol_table, - SharedStatNameStorageSet& storage_set) { - // We must free() all symbols referenced in the set, otherwise the symbols - // will leak when the flat_hash_map superclass is destructed. They cannot - // self-destruct without an explicit free() as each individual StatNameStorage - // object does not have a reference to the symbol table, which would waste 8 - // bytes per stat-name. So we must iterate over the set and free it. But we - // don't want to mutate objects while they are in a set, so we just copy them, - // which is easy because they are shared_ptr. - - size_t sz = storage_set.size(); - STACK_ARRAY(storage, SharedStatNameStorage, sz); - size_t i = 0; - for (const SharedStatNameStorage& name : storage_set) { - storage[i++] = name; - } - storage_set.clear(); - - // Now that the associative container is clear, we can free all the referenced - // symbols. - for (i = 0; i < sz; ++i) { - storage[i]->free(symbol_table); - } -} - void ThreadLocalStoreImpl::releaseScopeCrossThread(ScopeImpl* scope) { Thread::LockGuard lock(lock_); ASSERT(scopes_.count(scope) == 1); @@ -232,7 +207,7 @@ void ThreadLocalStoreImpl::releaseScopeCrossThread(ScopeImpl* scope) { rejected_stats->swap(scope->central_cache_.rejected_stats_); const uint64_t scope_id = scope->scope_id_; auto clean_central_cache = [this, rejected_stats]() { - freeSharedStatNameStorageSet(symbolTable(), *rejected_stats); + rejected_stats->free(symbolTable()); delete rejected_stats; }; From 4c2eeb2404a78c87f134fb5db49cedc6d8f612d4 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 28 Mar 2019 09:30:27 -0400 Subject: [PATCH 21/39] Update to more aggressive expected memory usage. Signed-off-by: Joshua Marantz --- test/integration/stats_integration_test.cc | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index 737bde2a29e6..eaac77cfa5a8 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -195,12 +195,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { EXPECT_LT(start_mem, m1); EXPECT_LT(start_mem, m1001); -// As of 2019/03/13, m_per_cluster = 56404 (libstdc++), 52249 (libc++). -#ifdef _LIBCPP_VERSION - EXPECT_LT(m_per_cluster, 53000); -#else - EXPECT_LT(m_per_cluster, 57000); -#endif + EXPECT_LT(m_per_cluster, 48000); // As of 2019/03/20, m_per_cluster = 47629 (libstdc++) } } // namespace From c16f5c1d1fba86c50c5f7c834dac90b56ba5b2a0 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 28 Mar 2019 13:58:35 -0400 Subject: [PATCH 22/39] fix memory-use limit, remove commented-out block. Signed-off-by: Joshua Marantz --- source/common/stats/isolated_store_impl.cc | 26 ---------------------- test/integration/stats_integration_test.cc | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/source/common/stats/isolated_store_impl.cc b/source/common/stats/isolated_store_impl.cc index 2695df6a8e04..cf0419395de4 100644 --- a/source/common/stats/isolated_store_impl.cc +++ b/source/common/stats/isolated_store_impl.cc @@ -36,32 +36,6 @@ IsolatedStoreImpl::IsolatedStoreImpl(SymbolTable& symbol_table) }), null_gauge_(symbol_table) {} -/* -struct IsolatedScopeImpl : public Scope { - IsolatedScopeImpl(IsolatedStoreImpl& parent, const std::string& prefix) - : parent_(parent), prefix_(Utility::sanitizeStatsName(prefix)) {} - - // Stats::Scope - ScopePtr createScope(const std::string& name) override { - return ScopePtr{new IsolatedScopeImpl(parent_, prefix_ + name)}; - } - void deliverHistogramToSinks(const Histogram&, uint64_t) 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); } - NullGaugeImpl& nullGauge(const std::string&) override { return null_gauge_; } - Histogram& histogram(const std::string& name) override { - return parent_.histogram(prefix_ + name); - } - const Stats::StatsOptions& statsOptions() const override { return parent_.statsOptions(); } - const SymbolTable& symbolTable() const override { return parent_.symbolTable(); } - SymbolTable& symbolTable() override { return parent_.symbolTable(); } - - IsolatedStoreImpl& parent_; - NullGaugeImpl null_gauge_; - const std::string prefix_; -}; -*/ - ScopePtr IsolatedStoreImpl::createScope(const std::string& name) { return std::make_unique(name, *this); } diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index c59585af3ebb..dce396dd4813 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -195,7 +195,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { EXPECT_LT(start_mem, m1); EXPECT_LT(start_mem, m1001); - EXPECT_LT(m_per_cluster, 48000); // As of 2019/03/29, m_per_cluster = 47629 (libstdc++) + EXPECT_LT(m_per_cluster, 49000); // As of 2019/03/29, m_per_cluster = 48839 (libstdc++) } } // namespace From 885ee774123c7b281cba831a4775a7611ec1e465 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 1 Apr 2019 22:15:41 -0400 Subject: [PATCH 23/39] Describe the strategy for converting to symbol tables. Signed-off-by: Joshua Marantz --- source/docs/stats.md | 64 ++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/source/docs/stats.md b/source/docs/stats.md index abe953c9f895..064ba0f56948 100644 --- a/source/docs/stats.md +++ b/source/docs/stats.md @@ -126,30 +126,54 @@ duplication of the keys, but costs an extra map lookup on each miss. When stored as flat strings, stat names can dominate Envoy memory usage when there are a large number of clusters. Stat names typically combine a small number of keywords, cluster names, host names, and response codes, separated by -".". For example "CLUSTER.upstream_cx_connect_attempts_exceeded". There may be -thousands of clusters, and ~100 stats per cluster. Thus, the number of -combinations can be large. It is significantly more efficient to symbolize each -"."-delimited token and represent stats as arrays of symbols. +`.`. For example `CLUSTER.upstream_cx_connect_attempts_exceeded`. There may be +thousands of clusters, and roughly 100 stats per cluster. Thus, the number +of combinations can be large. It is significantly more efficient to symbolize +each `.`-delimited token and represent stats as arrays of symbols. The transformation between flattened string and symbolized form is CPU-intensive at scale. It requires parsing, encoding, and lookups in a shared map, which must be mutex-protected. To avoid adding latency and CPU overhead while serving -requests, the tokens can be symbolized and saved in data structures. This can -occur on startup or when new hosts or clusters are configured dynamically. Thus -users of stats that are allocated dynamically per cluster, host, etc, must -explicitly store partial stat-names their class instances, which can be composed -dynamically at runtime in order to fully elaborate counters, gauges, etc, -without taking symbol-table locks. - -### Implementation - -The SymbolTable -[SymbolTable](https://github.com/envoyproxy/envoy/blob/master/include/envoy/stats/symbol.h) -abstraction has, temporarily, two implementions, -[FakeSymbolTableImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/fake_symbol_table_impl.h) -and -[SymbolTableImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/symbol_table_impl.h). -The fake will eventually be deleted. +requests, the tokens can be symbolized and saved in context classes, such as +(Http::CodeStatsImpl)[https://github.com/envoyproxy/envoy/blob/master/source/common/http/code_stats_impl.h]. +Symbolization can occur on startup or when new hosts or clusters are configured +dynamically. Users of stats that are allocated dynamically per cluster, host, +etc, must explicitly store partial stat-names their class instances, which later +can be composed dynamically at runtime in order to fully elaborate counters, +gauges, etc, without taking symbol-table locks, via `SymbolTable::join()`. + +###Current State and Strategy To Deploy Symbol Tables + +As of April 1, 2019, there are a fairly large number of files that directly +lookup stats by name, e.g. via `Stats::Scope::counter(const std::string&)` in +the request path. In most cases, this runtime lookup concatenates the scope name +with a string literal or other request-dependent token to form the stat name, so +it is not possible to fully memoize the stats at startup; there must be a +runtime name lookup. + +If a PR is issued that changes the underlying representation of a stat name to +be a symbol table entry then each stat-name will need to be transformed at +runtime, which would add CPU overhead and lock contention in the request-path, +violating one of the principles of Envoy's (threading +model)[https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310]. Before +issuing such a PR we need to first iterate through the codebase memoizing the +symbols that are used to form stat-names. + +To resolve this chicken-and-egg challenge to switch to symbol-table stat-name +representation without suffering a temporary loss of performance, we employ a +("fake" symbol table +implementation)[https://github.com/envoyproxy/envoy/blob/master/source/common/stats/fake_symbol_table_impl.h]. +This implemenation uses elaborated strings as an underlying representation, but +contains an identical API to the ("real" +implemention)[https://github.com/envoyproxy/envoy/blob/master/source/common/stats/symbol_table_impl.h].] +. The underlying string representation means that there is minimal runtime +overhead compared to the current state. But once all stat-allocation call-sites +have been converted to use the abstract (SymbolTable +API)[https://github.com/envoyproxy/envoy/blob/master/include/envoy/stats/symbol_table.h], + +Once all the call-sites that allocate stat-names have been converted to the +SymbolTable API, the real implementation can be swapped in, the space savings +realized, and the fake implementation deleted. ## Tags and Tag Extraction From 292f925018f9b8beecdda806337b49c1a9b39e2c Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Tue, 2 Apr 2019 10:15:30 -0400 Subject: [PATCH 24/39] fix links in doc, add deprecation comments to Scope interface, and remove iostream. Signed-off-by: Joshua Marantz --- include/envoy/stats/scope.h | 21 +++++++++++++++++++++ source/common/stats/heap_stat_data.cc | 6 ++---- source/docs/stats.md | 20 ++++++++++---------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/include/envoy/stats/scope.h b/include/envoy/stats/scope.h index 63f41190f136..eb3dbf0fa896 100644 --- a/include/envoy/stats/scope.h +++ b/include/envoy/stats/scope.h @@ -43,15 +43,29 @@ class Scope { virtual void deliverHistogramToSinks(const Histogram& histogram, uint64_t value) PURE; /** + * @param name The name of the stat, obtained from the SymbolTable. * @return a counter within the scope's namespace. */ virtual Counter& counterFromStatName(StatName name) PURE; + + /** + * TODO(jmarantz): this variant is deprecated: use counterFromStatName. + * @param name The name, expressed as a string. + * @return a counter within the scope's namespace. + */ virtual Counter& counter(const std::string& name) PURE; /** + * @param name The name of the stat, obtained from the SymbolTable. * @return a gauge within the scope's namespace. */ virtual Gauge& gaugeFromStatName(StatName name) PURE; + + /** + * TODO(jmarantz): this variant is deprecated: use gaugeFromStatName. + * @param name The name, expressed as a string. + * @return a gauge within the scope's namespace. + */ virtual Gauge& gauge(const std::string& name) PURE; /** @@ -60,9 +74,16 @@ class Scope { virtual NullGaugeImpl& nullGauge(const std::string& name) PURE; /** + * @param name The name of the stat, obtained from the SymbolTable. * @return a histogram within the scope's namespace with a particular value type. */ virtual Histogram& histogramFromStatName(StatName name) PURE; + + /** + * TODO(jmarantz): this variant is deprecated: use histogramFromStatName. + * @param name The name, expressed as a string. + * @return a histogram within the scope's namespace with a particular value type. + */ virtual Histogram& histogram(const std::string& name) PURE; /** diff --git a/source/common/stats/heap_stat_data.cc b/source/common/stats/heap_stat_data.cc index af9b19b71073..c13fec4f6265 100644 --- a/source/common/stats/heap_stat_data.cc +++ b/source/common/stats/heap_stat_data.cc @@ -1,8 +1,7 @@ #include "common/stats/heap_stat_data.h" -#include - #include "common/common/lock_guard.h" +#include "common/common/logger.h" #include "common/common/thread.h" #include "common/common/utility.h" @@ -59,9 +58,8 @@ void HeapStatDataAllocator::free(HeapStatData& data) { void HeapStatDataAllocator::debugPrint() { Thread::LockGuard lock(mutex_); for (HeapStatData* heap_stat_data : stats_) { - std::cout << symbolTable().toString(heap_stat_data->statName()) << std::endl; + ENVOY_LOG_MISC(info, "{}", symbolTable().toString(heap_stat_data->statName())); } - std::cout << std::flush; } #endif diff --git a/source/docs/stats.md b/source/docs/stats.md index 064ba0f56948..f9e0f9bd303c 100644 --- a/source/docs/stats.md +++ b/source/docs/stats.md @@ -135,7 +135,7 @@ The transformation between flattened string and symbolized form is CPU-intensive at scale. It requires parsing, encoding, and lookups in a shared map, which must be mutex-protected. To avoid adding latency and CPU overhead while serving requests, the tokens can be symbolized and saved in context classes, such as -(Http::CodeStatsImpl)[https://github.com/envoyproxy/envoy/blob/master/source/common/http/code_stats_impl.h]. +[Http::CodeStatsImpl](https://github.com/envoyproxy/envoy/blob/master/source/common/http/codes.h). Symbolization can occur on startup or when new hosts or clusters are configured dynamically. Users of stats that are allocated dynamically per cluster, host, etc, must explicitly store partial stat-names their class instances, which later @@ -154,22 +154,22 @@ runtime name lookup. If a PR is issued that changes the underlying representation of a stat name to be a symbol table entry then each stat-name will need to be transformed at runtime, which would add CPU overhead and lock contention in the request-path, -violating one of the principles of Envoy's (threading -model)[https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310]. Before +violating one of the principles of Envoy's [threading +model](https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310). Before issuing such a PR we need to first iterate through the codebase memoizing the symbols that are used to form stat-names. -To resolve this chicken-and-egg challenge to switch to symbol-table stat-name +To resolve this chicken-and-egg challenge of switching to symbol-table stat-name representation without suffering a temporary loss of performance, we employ a -("fake" symbol table -implementation)[https://github.com/envoyproxy/envoy/blob/master/source/common/stats/fake_symbol_table_impl.h]. +["fake" symbol table +implementation](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/fake_symbol_table_impl.h). This implemenation uses elaborated strings as an underlying representation, but -contains an identical API to the ("real" -implemention)[https://github.com/envoyproxy/envoy/blob/master/source/common/stats/symbol_table_impl.h].] +contains an identical API to the ["real" +implemention](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/symbol_table_impl.h). . The underlying string representation means that there is minimal runtime overhead compared to the current state. But once all stat-allocation call-sites -have been converted to use the abstract (SymbolTable -API)[https://github.com/envoyproxy/envoy/blob/master/include/envoy/stats/symbol_table.h], +have been converted to use the abstract [SymbolTable +API](https://github.com/envoyproxy/envoy/blob/master/include/envoy/stats/symbol_table.h), Once all the call-sites that allocate stat-names have been converted to the SymbolTable API, the real implementation can be swapped in, the space savings From 330b3e78f3144322687e0c9a5672f81733edcc4a Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Tue, 2 Apr 2019 14:58:49 -0400 Subject: [PATCH 25/39] fix section syntax Signed-off-by: Joshua Marantz --- source/docs/stats.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/docs/stats.md b/source/docs/stats.md index f9e0f9bd303c..831b85be43ea 100644 --- a/source/docs/stats.md +++ b/source/docs/stats.md @@ -142,7 +142,7 @@ etc, must explicitly store partial stat-names their class instances, which later can be composed dynamically at runtime in order to fully elaborate counters, gauges, etc, without taking symbol-table locks, via `SymbolTable::join()`. -###Current State and Strategy To Deploy Symbol Tables +### Current State and Strategy To Deploy Symbol Tables As of April 1, 2019, there are a fairly large number of files that directly lookup stats by name, e.g. via `Stats::Scope::counter(const std::string&)` in @@ -164,7 +164,7 @@ representation without suffering a temporary loss of performance, we employ a ["fake" symbol table implementation](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/fake_symbol_table_impl.h). This implemenation uses elaborated strings as an underlying representation, but -contains an identical API to the ["real" +implements the same API as the ["real" implemention](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/symbol_table_impl.h). . The underlying string representation means that there is minimal runtime overhead compared to the current state. But once all stat-allocation call-sites From 06427a2756715dd1c7b5528d6c28be62bf90aeed Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Tue, 2 Apr 2019 16:50:10 -0400 Subject: [PATCH 26/39] fix grammar & make other minor edits. Signed-off-by: Joshua Marantz --- source/docs/stats.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/source/docs/stats.md b/source/docs/stats.md index 831b85be43ea..ad088720cf35 100644 --- a/source/docs/stats.md +++ b/source/docs/stats.md @@ -152,9 +152,9 @@ it is not possible to fully memoize the stats at startup; there must be a runtime name lookup. If a PR is issued that changes the underlying representation of a stat name to -be a symbol table entry then each stat-name will need to be transformed at -runtime, which would add CPU overhead and lock contention in the request-path, -violating one of the principles of Envoy's [threading +be a symbol table entry then each stat-name will need to be transformed +whenever names are looked up, which would add CPU overhead and lock contention +in the request-path, violating one of the principles of Envoy's [threading model](https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310). Before issuing such a PR we need to first iterate through the codebase memoizing the symbols that are used to form stat-names. @@ -166,14 +166,12 @@ implementation](https://github.com/envoyproxy/envoy/blob/master/source/common/st This implemenation uses elaborated strings as an underlying representation, but implements the same API as the ["real" implemention](https://github.com/envoyproxy/envoy/blob/master/source/common/stats/symbol_table_impl.h). -. The underlying string representation means that there is minimal runtime +The underlying string representation means that there is minimal runtime overhead compared to the current state. But once all stat-allocation call-sites have been converted to use the abstract [SymbolTable API](https://github.com/envoyproxy/envoy/blob/master/include/envoy/stats/symbol_table.h), - -Once all the call-sites that allocate stat-names have been converted to the -SymbolTable API, the real implementation can be swapped in, the space savings -realized, and the fake implementation deleted. +the real implementation can be swapped in, the space savings realized, and the +fake implementation deleted. ## Tags and Tag Extraction From ec750f27b2e7cb4726473dc2b054322b98cf50a7 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 4 Apr 2019 11:55:09 -0400 Subject: [PATCH 27/39] remove IsolatedStats*::clear() Signed-off-by: Joshua Marantz --- source/common/stats/isolated_store_impl.cc | 6 ------ source/common/stats/isolated_store_impl.h | 4 ---- test/mocks/stats/mocks.cc | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/source/common/stats/isolated_store_impl.cc b/source/common/stats/isolated_store_impl.cc index cf0419395de4..24cddda4a9c4 100644 --- a/source/common/stats/isolated_store_impl.cc +++ b/source/common/stats/isolated_store_impl.cc @@ -40,11 +40,5 @@ ScopePtr IsolatedStoreImpl::createScope(const std::string& name) { return std::make_unique(name, *this); } -void IsolatedStoreImpl::clear() { - counters_.clear(); - gauges_.clear(); - histograms_.clear(); -} - } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index 306146f07a01..5d6bae2fbb87 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -50,8 +50,6 @@ template class IsolatedStatsCache { return vec; } - void clear() { stats_.clear(); } - private: StatNameHashMap> stats_; Allocator alloc_; @@ -96,8 +94,6 @@ class IsolatedStoreImpl : public Store { return histogramFromStatName(storage.statName()); } - void clear(); - private: IsolatedStoreImpl(std::unique_ptr&& symbol_table); diff --git a/test/mocks/stats/mocks.cc b/test/mocks/stats/mocks.cc index 363595b89e37..df4ad56f453d 100644 --- a/test/mocks/stats/mocks.cc +++ b/test/mocks/stats/mocks.cc @@ -97,7 +97,7 @@ MockStore::~MockStore() {} MockIsolatedStatsStore::MockIsolatedStatsStore() : IsolatedStoreImpl(Test::Global::get()) {} -MockIsolatedStatsStore::~MockIsolatedStatsStore() { IsolatedStoreImpl::clear(); } +MockIsolatedStatsStore::~MockIsolatedStatsStore() {} MockStatsMatcher::MockStatsMatcher() {} MockStatsMatcher::~MockStatsMatcher() {} From ec01f8ec07cb5ca19f2d75b9d80b0f2f4744717f Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sat, 6 Apr 2019 19:22:39 -0400 Subject: [PATCH 28/39] Don't use shared_ptr for the remembered disallowed stats. Signed-off-by: Joshua Marantz --- source/common/stats/symbol_table_impl.cc | 39 ++++++++++----------- source/common/stats/symbol_table_impl.h | 33 ++++++++--------- source/common/stats/tag_extractor_impl.cc | 1 - source/common/stats/thread_local_store.cc | 32 ++++++----------- source/common/stats/thread_local_store.h | 10 +++--- test/common/stats/symbol_table_impl_test.cc | 14 ++++++++ 6 files changed, 64 insertions(+), 65 deletions(-) diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index 820bb6638716..40ebd666d4ec 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -317,33 +317,32 @@ void StatNameStorage::free(SymbolTable& table) { bytes_.reset(); } -SharedStatNameStorageSet::~SharedStatNameStorageSet() { - // free() must be called before destructing SharedStatNameStorageSet to - // decrement references to all symbols. +StatNameStorageSet::~StatNameStorageSet() { + // free() must be called before destructing StatNameStorageSet to decrement + // references to all symbols. ASSERT(empty()); } -void SharedStatNameStorageSet::free(SymbolTable& symbol_table) { +void StatNameStorageSet::free(SymbolTable& symbol_table) { // We must free() all symbols referenced in the set, otherwise the symbols // will leak when the flat_hash_map superclass is destructed. They cannot // self-destruct without an explicit free() as each individual StatNameStorage // object does not have a reference to the symbol table, which would waste 8 - // bytes per stat-name. So we must iterate over the set and free it. But we - // don't want to mutate objects while they are in a set, so we just copy them, - // which is easy because they are shared_ptr. - - size_t sz = size(); - STACK_ARRAY(storage, SharedStatNameStorage, sz); - size_t i = 0; - for (const SharedStatNameStorage& name : *this) { - storage[i++] = name; - } - clear(); - - // Now that the associative container is clear, we can free all the referenced - // symbols. - for (i = 0; i < sz; ++i) { - storage[i]->free(symbol_table); + // bytes per stat-name. The easiest way to safely free all the contents of the + // symbol table set is to use flat_hash_map::extract(), which removes and + // returns an element from the set without destructing the element + // immediately. This gives us a chance to call free() on each one before they + // are destroyed. + // + // One risk here is if removing elements via flat_hash_set::begin() is + // inefficient to use in a loop like this. One can imagine a hash-table + // implementation where the performance if this usage-model would be + // poor. However, tests with 100k elements appeared to run quickly when + // compiled for optimization, so at present this is not a performance issue. + + while (!empty()) { + auto storage = extract(begin()); + storage.value().free(symbol_table); } } diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index e1b1d5769123..3a951c5e94f7 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -478,8 +478,6 @@ struct StatNameLessThan { const SymbolTable& symbol_table_; }; -using SharedStatNameStorage = std::shared_ptr; - struct HeterogeneousStatNameHash { // Specifying is_transparent indicates to the library infrastructure that // type-conversions should not be applied when calling find(), but instead @@ -491,7 +489,7 @@ struct HeterogeneousStatNameHash { using is_transparent = void; size_t operator()(StatName a) const { return a.hash(); } - size_t operator()(const SharedStatNameStorage& a) const { return a->statName().hash(); } + size_t operator()(const StatNameStorage& a) const { return a.statName().hash(); } }; struct HeterogeneousStatNameEqual { @@ -499,25 +497,24 @@ struct HeterogeneousStatNameEqual { using is_transparent = void; size_t operator()(StatName a, StatName b) const { return a == b; } - size_t operator()(const SharedStatNameStorage& a, const SharedStatNameStorage& b) const { - return a->statName() == b->statName(); + size_t operator()(const StatNameStorage& a, const StatNameStorage& b) const { + return a.statName() == b.statName(); } - size_t operator()(StatName a, const SharedStatNameStorage& b) const { return a == b->statName(); } - size_t operator()(const SharedStatNameStorage& a, StatName b) const { return a->statName() == b; } + size_t operator()(StatName a, const StatNameStorage& b) const { return a == b.statName(); } + size_t operator()(const StatNameStorage& a, StatName b) const { return a.statName() == b; } }; -// Encapsulates a set of shared_ptr. We use a subclass here -// rather than a 'using' alias because we need to ensure that when the set is -// destructed, StatNameStorage::free(symbol_table) is called on each entry. It -// is a little easier at the call-site in thread_local_store.cc to implement -// this an explicit free() method, analogous to StatNameStorage::free(), -// compared to storing a SymbolTable reference in the class and doing the free -// in the destructor, like StatNameTempStorage. -class SharedStatNameStorageSet - : public absl::flat_hash_set { +// Encapsulates a set. We use a subclass here rather than a +// 'using' alias because we need to ensure that when the set is destructed, +// StatNameStorage::free(symbol_table) is called on each entry. It is a little +// easier at the call-sites in thread_local_store.cc to implement this an +// explicit free() method, analogous to StatNameStorage::free(), compared to +// storing a SymbolTable reference in the class and doing the free in the +// destructor, like StatNameTempStorage. +class StatNameStorageSet : public absl::flat_hash_set { public: - ~SharedStatNameStorageSet(); + ~StatNameStorageSet(); void free(SymbolTable& symbol_table); }; diff --git a/source/common/stats/tag_extractor_impl.cc b/source/common/stats/tag_extractor_impl.cc index 761e82caa7b2..acf440fa695e 100644 --- a/source/common/stats/tag_extractor_impl.cc +++ b/source/common/stats/tag_extractor_impl.cc @@ -77,7 +77,6 @@ bool TagExtractorImpl::extractTag(absl::string_view stat_name, std::vector& std::match_results match; // The regex must match and contain one or more subexpressions (all after the first are ignored). - // std::string stat_name_str = std::string(stat_name); if (std::regex_search(stat_name.begin(), stat_name.end(), match, regex_) && match.size() > 1) { diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 6783c60ce504..fec140f760a6 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -199,11 +199,11 @@ void ThreadLocalStoreImpl::releaseScopeCrossThread(ScopeImpl* scope) { // This is called directly from the ScopeImpl destructor, but we can't delay // the destruction of scope->central_cache_.central_cache_.rejected_stats_ - // to wait for all the TLS rejected_stats_ caches are destructed, as those + // to wait for all the TLS rejected_stats_ caches to be destructed, as those // reference elements of SharedStatNameStorageSet. So simply swap out the set // contents into a local that we can hold onto until the TLS cache is cleared // of all references. - auto rejected_stats = new SharedStatNameStorageSet; + auto rejected_stats = new StatNameStorageSet; rejected_stats->swap(scope->central_cache_.rejected_stats_); const uint64_t scope_id = scope->scope_id_; auto clean_central_cache = [this, rejected_stats]() { @@ -281,21 +281,22 @@ class TagExtraction { std::string tag_extracted_name_; }; -bool ThreadLocalStoreImpl::checkAndRememberRejection( - StatName name, SharedStatNameStorageSet& central_rejected_stats, - StatNameHashSet* tls_rejected_stats) { +bool ThreadLocalStoreImpl::checkAndRememberRejection(StatName name, + StatNameStorageSet& central_rejected_stats, + StatNameHashSet* tls_rejected_stats) { if (stats_matcher_->acceptsAll()) { return false; } auto iter = central_rejected_stats.find(name); - SharedStatNameStorage rejected_name; + const StatNameStorage* rejected_name = nullptr; if (iter != central_rejected_stats.end()) { - rejected_name = *iter; + rejected_name = &(*iter); } else { if (rejects(name)) { - rejected_name = std::make_shared(name, symbolTable()); - central_rejected_stats.insert(rejected_name); + auto insertion = central_rejected_stats.insert(StatNameStorage(name, symbolTable())); + const StatNameStorage& rejected_name_ref = *(insertion.first); + rejected_name = &rejected_name_ref; } } if (rejected_name != nullptr) { @@ -310,27 +311,16 @@ bool ThreadLocalStoreImpl::checkAndRememberRejection( template StatType& ThreadLocalStoreImpl::ScopeImpl::safeMakeStat( StatName name, StatMap>& central_cache_map, - SharedStatNameStorageSet& central_rejected_stats, MakeStatFn make_stat, + StatNameStorageSet& central_rejected_stats, MakeStatFn make_stat, StatMap>* tls_cache, StatNameHashSet* tls_rejected_stats, StatType& null_stat) { - // const char* stat_key = name.c_str(); - // We do name-rejections on the full name, prior to truncation. if (tls_rejected_stats != nullptr && tls_rejected_stats->find(name) != tls_rejected_stats->end()) { return null_stat; } - /* - std::unique_ptr truncation_buffer; - absl::string_view truncated_name = parent_.truncateStatNameIfNeeded(name_key); - if (truncated_name.size() < name.size()) { - truncation_buffer = std::make_unique(std::string(truncated_name)); - stat_key = truncation_buffer->c_str(); // must be nul-terminated. - } - */ - // If we have a valid cache entry, return it. if (tls_cache) { auto pos = tls_cache->find(name); diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 4105296e2f3b..4dab95a1f9e5 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -198,8 +198,8 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo // We keep a TLS cache of rejected stat names. This costs memory, but // reduces runtime overhead running the matcher. Moreover, once symbol // tables are integrated, rejection will need the fully elaborated string, - // and it we need to take a global symbol-table lock to run. We keep - // this char* map here in the TLS cache to avoid taking a lock to compute + // and it we need to take a global symbol-table lock to run. We keep this + // StatName set here in the TLS cache to avoid taking a lock to compute // rejection. StatNameHashSet rejected_stats_; }; @@ -208,7 +208,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo StatMap counters_; StatMap gauges_; StatMap histograms_; - SharedStatNameStorageSet rejected_stats_; + StatNameStorageSet rejected_stats_; }; struct ScopeImpl : public TlsScope { @@ -261,7 +261,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo */ template StatType& safeMakeStat(StatName name, StatMap>& central_cache_map, - SharedStatNameStorageSet& central_rejected_stats, + StatNameStorageSet& central_rejected_stats, MakeStatFn make_stat, StatMap>* tls_cache, StatNameHashSet* tls_rejected_stats, StatType& null_stat); @@ -297,7 +297,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo bool rejectsAll() const { return stats_matcher_->rejectsAll(); } template void removeRejectedStats(StatMapClass& map, StatListClass& list); - bool checkAndRememberRejection(StatName name, SharedStatNameStorageSet& central_rejected_stats, + bool checkAndRememberRejection(StatName name, StatNameStorageSet& central_rejected_stats, StatNameHashSet* tls_rejected_stats); const Stats::StatsOptions& stats_options_; diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index 36c86aaf4365..f2bb83fdd6bd 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -507,6 +507,20 @@ TEST_P(StatNameTest, MutexContentionOnExistingSymbols) { } } +TEST_P(StatNameTest, SharedStatNameStorageSet) { + StatNameStorageSet set; + { + for (int i = 0; i < 10; ++i) { + std::string foo = absl::StrCat("foo", i); + auto insertion = set.insert(StatNameStorage(foo, *table_)); + StatNameTempStorage temp_foo(foo, *table_); + auto found = set.find(temp_foo.statName()); + EXPECT_EQ(found->statName().data(), insertion.first->statName().data()); + } + } + set.free(*table_); +} + // Tests the memory savings realized from using symbol tables with 1k // clusters. This test shows the memory drops from almost 8M to less than // 2M. Note that only SymbolTableImpl is tested for memory consumption, From ccc666bc98775349f102b6ec77f298a686855b0a Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sun, 7 Apr 2019 14:36:03 -0400 Subject: [PATCH 29/39] scope_prefixer.cc should go back to using StatName natively. Signed-off-by: Joshua Marantz --- source/common/stats/scope_prefixer.cc | 18 +++++++++++------- source/common/stats/scope_prefixer.h | 7 +++++-- source/common/stats/symbol_table_impl.h | 2 +- source/common/stats/thread_local_store.h | 1 - test/common/stats/isolated_store_impl_test.cc | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/source/common/stats/scope_prefixer.cc b/source/common/stats/scope_prefixer.cc index c0a42c3d2d4c..8469f6b8c57b 100644 --- a/source/common/stats/scope_prefixer.cc +++ b/source/common/stats/scope_prefixer.cc @@ -9,17 +9,21 @@ namespace Envoy { namespace Stats { ScopePrefixer::ScopePrefixer(absl::string_view prefix, Scope& scope) - : prefix_(Utility::sanitizeStatsName(prefix), scope.symbolTable()), scope_(scope) {} + : scope_(scope), prefix_(Utility::sanitizeStatsName(prefix), symbolTable()) {} -ScopePrefixer::~ScopePrefixer() { - prefix_.free(scope_.symbolTable()); +ScopePrefixer::ScopePrefixer(StatName prefix, Scope& scope) + : scope_(scope), prefix_(prefix, symbolTable()) {} + +ScopePrefixer::~ScopePrefixer() { prefix_.free(symbolTable()); } + +ScopePtr ScopePrefixer::createScopeFromStatName(StatName name) { + SymbolTable::StoragePtr joined = symbolTable().join({prefix_.statName(), name}); + return std::make_unique(StatName(joined.get()), scope_); } ScopePtr ScopePrefixer::createScope(const std::string& name) { - // StatNameStorage scope_stat_name(name, symbolTable()); // Takes a lock. - // StatNameStorage joiner(prefix_.statName(), scope_stat_name.statName()); - return std::make_unique(symbolTable().toString(prefix_.statName()) + "." + name, - scope_); + StatNameTempStorage stat_name_storage(Utility::sanitizeStatsName(name), symbolTable()); + return createScopeFromStatName(stat_name_storage.statName()); } Counter& ScopePrefixer::counterFromStatName(StatName name) { diff --git a/source/common/stats/scope_prefixer.h b/source/common/stats/scope_prefixer.h index 195d9f1b4af3..8b4db408397a 100644 --- a/source/common/stats/scope_prefixer.h +++ b/source/common/stats/scope_prefixer.h @@ -10,7 +10,10 @@ namespace Stats { class ScopePrefixer : public Scope { public: ScopePrefixer(absl::string_view prefix, Scope& scope); - virtual ~ScopePrefixer(); + ScopePrefixer(StatName prefix, Scope& scope); + ~ScopePrefixer() override; + + ScopePtr createScopeFromStatName(StatName name); // Scope ScopePtr createScope(const std::string& name) override; @@ -39,8 +42,8 @@ class ScopePrefixer : public Scope { NullGaugeImpl& nullGauge(const std::string& str) override { return scope_.nullGauge(str); } private: - StatNameStorage prefix_; Scope& scope_; + StatNameStorage prefix_; }; } // namespace Stats diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 3a951c5e94f7..97def139ba7c 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -132,7 +132,7 @@ class SymbolTableImpl : public SymbolTable { bool lessThan(const StatName& a, const StatName& b) const override; void free(const StatName& stat_name) override; void incRefCount(const StatName& stat_name) override; - SymbolTable::StoragePtr join(const std::vector& stat_names) const override; + StoragePtr join(const std::vector& stat_names) const override; void populateList(const absl::string_view* names, uint32_t num_names, StatNameList& list) override; StoragePtr encode(absl::string_view name) override; diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 5e081e51b48d..4dab95a1f9e5 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -270,7 +270,6 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo std::unique_ptr& truncated_name_storage, std::vector& tags, std::string& tag_extracted_name); - static std::atomic next_scope_id_; const uint64_t scope_id_; diff --git a/test/common/stats/isolated_store_impl_test.cc b/test/common/stats/isolated_store_impl_test.cc index 50c4aea7d0d9..c9e9c2cd49b7 100644 --- a/test/common/stats/isolated_store_impl_test.cc +++ b/test/common/stats/isolated_store_impl_test.cc @@ -54,7 +54,7 @@ TEST_F(StatsIsolatedStoreImplTest, All) { EXPECT_EQ("g1", g1.tagExtractedName()); EXPECT_EQ("scope1.g2", g2.tagExtractedName()); EXPECT_EQ(0, g1.tags().size()); - EXPECT_EQ(0, g1.tags().size()); + EXPECT_EQ(0, g2.tags().size()); Histogram& h1 = store_.histogram("h1"); Histogram& h2 = scope1->histogram("h2"); From 68bf3145f46c41ad14a592afc548ee92086a2b91 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sat, 20 Apr 2019 19:17:11 -0400 Subject: [PATCH 30/39] Address review comments. Signed-off-by: Joshua Marantz --- include/envoy/stats/scope.h | 6 +-- include/envoy/stats/stats.h | 9 ++++- source/common/stats/heap_stat_data.cc | 3 +- source/common/stats/metric_impl.cc | 27 +++++++++----- source/common/stats/symbol_table_impl.cc | 19 +++++----- source/common/stats/symbol_table_impl.h | 41 +++++++++++++++++++-- test/common/stats/symbol_table_impl_test.cc | 33 ++++++++++++----- test/integration/stats_integration_test.cc | 17 ++++++++- 8 files changed, 115 insertions(+), 40 deletions(-) diff --git a/include/envoy/stats/scope.h b/include/envoy/stats/scope.h index eb3dbf0fa896..d462231368d5 100644 --- a/include/envoy/stats/scope.h +++ b/include/envoy/stats/scope.h @@ -49,7 +49,7 @@ class Scope { virtual Counter& counterFromStatName(StatName name) PURE; /** - * TODO(jmarantz): this variant is deprecated: use counterFromStatName. + * TODO(#6667): this variant is deprecated: use counterFromStatName. * @param name The name, expressed as a string. * @return a counter within the scope's namespace. */ @@ -62,7 +62,7 @@ class Scope { virtual Gauge& gaugeFromStatName(StatName name) PURE; /** - * TODO(jmarantz): this variant is deprecated: use gaugeFromStatName. + * TODO(#6667): this variant is deprecated: use gaugeFromStatName. * @param name The name, expressed as a string. * @return a gauge within the scope's namespace. */ @@ -80,7 +80,7 @@ class Scope { virtual Histogram& histogramFromStatName(StatName name) PURE; /** - * TODO(jmarantz): this variant is deprecated: use histogramFromStatName. + * TODO(#6667): this variant is deprecated: use histogramFromStatName. * @param name The name, expressed as a string. * @return a histogram within the scope's namespace with a particular value type. */ diff --git a/include/envoy/stats/stats.h b/include/envoy/stats/stats.h index d93fb76166d0..1289d93bd1b0 100644 --- a/include/envoy/stats/stats.h +++ b/include/envoy/stats/stats.h @@ -44,8 +44,13 @@ class Metric { virtual std::vector tags() const PURE; /** - * Returns the name of the Metric with the portions designated as tags - * removed as a string. + * Returns the name of the Metric with the portions designated as tags removed + * as a string. For example, The stat name "vhost.foo.vcluster.bar.c1" would + * have "foo" extracted as the value of tag "vhost" and "bar" extracted as the + * value of tag "vcluster". Thus the tagExtractedName is simply + * "vhost.vcluster.c1". + * + * @return The stat name with all tag values extracted. */ virtual std::string tagExtractedName() const PURE; diff --git a/source/common/stats/heap_stat_data.cc b/source/common/stats/heap_stat_data.cc index c13fec4f6265..42d48967a608 100644 --- a/source/common/stats/heap_stat_data.cc +++ b/source/common/stats/heap_stat_data.cc @@ -24,7 +24,8 @@ void HeapStatData::free(SymbolTable& symbol_table) { } HeapStatData& HeapStatDataAllocator::alloc(StatName name) { - std::unique_ptr> data_ptr( + using HeapStatDataFreeFn = std::function; + std::unique_ptr data_ptr( HeapStatData::alloc(name, symbolTable()), [this](HeapStatData* d) { d->free(symbolTable()); }); Thread::ReleasableLockGuard lock(mutex_); diff --git a/source/common/stats/metric_impl.cc b/source/common/stats/metric_impl.cc index 6851d2abd3ee..d68240587915 100644 --- a/source/common/stats/metric_impl.cc +++ b/source/common/stats/metric_impl.cc @@ -17,8 +17,10 @@ MetricImpl::~MetricImpl() { MetricImpl::MetricImpl(absl::string_view tag_extracted_name, const std::vector& tags, SymbolTable& symbol_table) { // Encode all the names and tags into transient storage so we can count the - // required bytes. - uint32_t num_names = 1 /* tag_extracted_name */ + 2 * tags.size(); + // required bytes. 1 is added to account for the tag_extracted_name, and we + // multiply the number of tags by 2 to account for the name and value of each + // tag. + uint32_t num_names = 1 + 2 * tags.size(); STACK_ARRAY(names, absl::string_view, num_names); names[0] = tag_extracted_name; int index = 0; @@ -39,33 +41,38 @@ StatName MetricImpl::tagExtractedStatName() const { StatName stat_name; stat_names_.iterate([&stat_name](StatName s) -> bool { stat_name = s; - return false; + return false; // Returning 'false' stops the iteration. }); return stat_name; } std::vector MetricImpl::tags() const { std::vector tags; - enum { TagExtractedName, Name, Value } state = TagExtractedName; + enum { TagExtractedName, TagName, TagValue } state = TagExtractedName; Tag tag; const SymbolTable& symbol_table = symbolTable(); + + // StatNameList maintains a linear ordered collection of StatNames, and we + // are mapping that into a tag-extracted name (the first element), followed + // by alternating TagName and TagValue. So we use a little state machine + // as we iterate through the stat_names_. stat_names_.iterate([&tags, &state, &tag, &symbol_table](StatName stat_name) -> bool { switch (state) { case TagExtractedName: - state = Name; + state = TagName; break; - case Name: + case TagName: tag.name_ = symbol_table.toString(stat_name); - state = Value; + state = TagValue; break; - case Value: + case TagValue: tag.value_ = symbol_table.toString(stat_name); tags.emplace_back(tag); - state = Name; + state = TagName; } return true; }); - ASSERT(state != Value); + ASSERT(state != TagValue); return tags; } diff --git a/source/common/stats/symbol_table_impl.cc b/source/common/stats/symbol_table_impl.cc index 40ebd666d4ec..6a96a340d72c 100644 --- a/source/common/stats/symbol_table_impl.cc +++ b/source/common/stats/symbol_table_impl.cc @@ -320,7 +320,7 @@ void StatNameStorage::free(SymbolTable& table) { StatNameStorageSet::~StatNameStorageSet() { // free() must be called before destructing StatNameStorageSet to decrement // references to all symbols. - ASSERT(empty()); + ASSERT(hash_set_.empty()); } void StatNameStorageSet::free(SymbolTable& symbol_table) { @@ -334,14 +334,15 @@ void StatNameStorageSet::free(SymbolTable& symbol_table) { // immediately. This gives us a chance to call free() on each one before they // are destroyed. // - // One risk here is if removing elements via flat_hash_set::begin() is - // inefficient to use in a loop like this. One can imagine a hash-table - // implementation where the performance if this usage-model would be - // poor. However, tests with 100k elements appeared to run quickly when - // compiled for optimization, so at present this is not a performance issue. - - while (!empty()) { - auto storage = extract(begin()); + // There's a performance risk here, if removing elements via + // flat_hash_set::begin() is inefficient to use in a loop like this. One can + // imagine a hash-table implementation where the performance of this + // usage-model would be poor. However, tests with 100k elements appeared to + // run quickly when compiled for optimization, so at present this is not a + // performance issue. + + while (!hash_set_.empty()) { + auto storage = hash_set_.extract(hash_set_.begin()); storage.value().free(symbol_table); } } diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 97def139ba7c..135d0619338c 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -139,8 +139,6 @@ class SymbolTableImpl : public SymbolTable { void callWithStringView(StatName stat_name, const std::function& fn) const override; - void encode(absl::string_view name, Encoding& encoding); - #ifndef ENVOY_CONFIG_COVERAGE void debugPrint() const override; #endif @@ -511,11 +509,46 @@ struct HeterogeneousStatNameEqual { // explicit free() method, analogous to StatNameStorage::free(), compared to // storing a SymbolTable reference in the class and doing the free in the // destructor, like StatNameTempStorage. -class StatNameStorageSet : public absl::flat_hash_set { +class StatNameStorageSet { public: + using HashSet = + absl::flat_hash_set; + using iterator = HashSet::iterator; + ~StatNameStorageSet(); + + /** + * Releases all symbols held in this set. Must be called prior to destruction. + * + * @param symbol_table The symbol table that owns the symbols. + */ void free(SymbolTable& symbol_table); + + /** + * @param storage The StatNameStorage to add to the set. + */ + std::pair insert(StatNameStorage&& storage) { + return hash_set_.insert(std::move(storage)); + } + + /** + * @param stat_name The stat_name to find. + * @return the iterator pointing to the stat_name, or end() if not found. + */ + iterator find(StatName stat_name) { return hash_set_.find(stat_name); } + + /** + * @return the end-marker. + */ + iterator end() { return hash_set_.end(); } + + /** + * @param set the storage set to swap with. + */ + void swap(StatNameStorageSet& set) { hash_set_.swap(set.hash_set_); } + +private: + HashSet hash_set_; }; } // namespace Stats diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index f2bb83fdd6bd..9a4a3c96ffe8 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -74,6 +74,16 @@ class StatNameTest : public testing::TestWithParam { return stat_name_storage_.back().statName(); } + void exerciseStatNameStorageSetInsertAndFind(StatNameStorageSet& set) { + for (int i = 0; i < 10; ++i) { + std::string foo = absl::StrCat("foo", i); + auto insertion = set.insert(StatNameStorage(foo, *table_)); + StatNameTempStorage temp_foo(foo, *table_); + auto found = set.find(temp_foo.statName()); + EXPECT_EQ(found->statName().data(), insertion.first->statName().data()); + } + } + FakeSymbolTableImpl* fake_symbol_table_{nullptr}; SymbolTableImpl* real_symbol_table_{nullptr}; std::unique_ptr table_; @@ -507,20 +517,23 @@ TEST_P(StatNameTest, MutexContentionOnExistingSymbols) { } } -TEST_P(StatNameTest, SharedStatNameStorageSet) { +TEST_P(StatNameTest, SharedStatNameStorageSetInsertAndFind) { StatNameStorageSet set; - { - for (int i = 0; i < 10; ++i) { - std::string foo = absl::StrCat("foo", i); - auto insertion = set.insert(StatNameStorage(foo, *table_)); - StatNameTempStorage temp_foo(foo, *table_); - auto found = set.find(temp_foo.statName()); - EXPECT_EQ(found->statName().data(), insertion.first->statName().data()); - } - } + exerciseStatNameStorageSetInsertAndFind(set); set.free(*table_); } +TEST_P(StatNameTest, SharedStatNameStorageSetAssertIfNotFreed) { +#ifndef NDEBUG + EXPECT_DEATH( + { + StatNameStorageSet set; + exerciseStatNameStorageSetInsertAndFind(set); + }, + ""); +#endif +} + // Tests the memory savings realized from using symbol tables with 1k // clusters. This test shows the memory drops from almost 8M to less than // 2M. Note that only SymbolTableImpl is tested for memory consumption, diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index dce396dd4813..421c26df8c6d 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -195,7 +195,22 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { EXPECT_LT(start_mem, m1); EXPECT_LT(start_mem, m1001); - EXPECT_LT(m_per_cluster, 49000); // As of 2019/03/29, m_per_cluster = 48839 (libstdc++) + + // Note: if you are increasing this golden value because you are adding a + // stat, please confirim that this will be generally useful to most Envoy + // users. Otherwise you are adding to the per-cluster memory overhead, which + // will be significant for Envoy installations that are massively + // multi-tenant. + // + // History of golden values: + // + // Date PR Bytes Per Cluster Author Notes + // ---------- ----- ----------------- ---------------- ----- + // 2019/03/20 6329 59015 cmluciano Initial checkin + // 2019/04/12 6477 59576 htuch Implementing Endpoint lease... + // 2019/04/20 6161 49415 jmarantz Pack tags and tag extracted names + + EXPECT_EQ(m_per_cluster, 49415); } } // namespace From a76248ac1a50e1015a2b70bbb82f65fdf16389da Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sat, 20 Apr 2019 20:25:10 -0400 Subject: [PATCH 31/39] pull over test cleanup from #6504 Signed-off-by: Joshua Marantz --- source/common/stats/symbol_table_impl.h | 5 +++ test/common/stats/symbol_table_impl_test.cc | 37 +++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 135d0619338c..0765045f29d9 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -547,6 +547,11 @@ class StatNameStorageSet { */ void swap(StatNameStorageSet& set) { hash_set_.swap(set.hash_set_); } + /** + * @return the number of elements in the set. + */ + size_t size() const { return hash_set_.size(); } + private: HashSet hash_set_; }; diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index 9a4a3c96ffe8..c2c56c6fb7ba 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -74,16 +74,6 @@ class StatNameTest : public testing::TestWithParam { return stat_name_storage_.back().statName(); } - void exerciseStatNameStorageSetInsertAndFind(StatNameStorageSet& set) { - for (int i = 0; i < 10; ++i) { - std::string foo = absl::StrCat("foo", i); - auto insertion = set.insert(StatNameStorage(foo, *table_)); - StatNameTempStorage temp_foo(foo, *table_); - auto found = set.find(temp_foo.statName()); - EXPECT_EQ(found->statName().data(), insertion.first->statName().data()); - } - } - FakeSymbolTableImpl* fake_symbol_table_{nullptr}; SymbolTableImpl* real_symbol_table_{nullptr}; std::unique_ptr table_; @@ -519,16 +509,37 @@ TEST_P(StatNameTest, MutexContentionOnExistingSymbols) { TEST_P(StatNameTest, SharedStatNameStorageSetInsertAndFind) { StatNameStorageSet set; - exerciseStatNameStorageSetInsertAndFind(set); + const int iters = 10; + for (int i = 0; i < iters; ++i) { + std::string foo = absl::StrCat("foo", i); + auto insertion = set.insert(StatNameStorage(foo, *table_)); + StatNameTempStorage temp_foo(foo, *table_); + auto found = set.find(temp_foo.statName()); + EXPECT_EQ(found->statName().data(), insertion.first->statName().data()); + } + StatNameTempStorage bar("bar", *table_); + EXPECT_EQ(set.end(), set.find(bar.statName())); + EXPECT_EQ(iters, set.size()); set.free(*table_); } -TEST_P(StatNameTest, SharedStatNameStorageSetAssertIfNotFreed) { +TEST_P(StatNameTest, SharedStatNameStorageSetSwap) { + StatNameStorageSet set1, set2; + set1.insert(StatNameStorage("foo", *table_)); + EXPECT_EQ(1, set1.size()); + EXPECT_EQ(0, set2.size()); + set1.swap(set2); + EXPECT_EQ(0, set1.size()); + EXPECT_EQ(1, set2.size()); + set2.free(*table_); +} + +TEST_P(StatNameTest, SharedStatNameStorageSetIfDestroyedWithoutFree) { #ifndef NDEBUG EXPECT_DEATH( { StatNameStorageSet set; - exerciseStatNameStorageSetInsertAndFind(set); + set.insert(StatNameStorage("foo", *table_)); }, ""); #endif From e74fe2aa37872a57f72a8c85f9e19c42cb7675e4 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sat, 20 Apr 2019 20:26:59 -0400 Subject: [PATCH 32/39] spelling Signed-off-by: Joshua Marantz --- test/integration/stats_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index 421c26df8c6d..6bbb052e14df 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -197,7 +197,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { EXPECT_LT(start_mem, m1001); // Note: if you are increasing this golden value because you are adding a - // stat, please confirim that this will be generally useful to most Envoy + // stat, please confirm that this will be generally useful to most Envoy // users. Otherwise you are adding to the per-cluster memory overhead, which // will be significant for Envoy installations that are massively // multi-tenant. From 9eeea113318805929ea827b98ce0c1b4f02aedda Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sat, 20 Apr 2019 20:27:32 -0400 Subject: [PATCH 33/39] spelling pedantry Signed-off-by: Joshua Marantz --- test/integration/stats_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index 6bbb052e14df..cb94dba878ff 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -206,7 +206,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { // // Date PR Bytes Per Cluster Author Notes // ---------- ----- ----------------- ---------------- ----- - // 2019/03/20 6329 59015 cmluciano Initial checkin + // 2019/03/20 6329 59015 cmluciano Initial version // 2019/04/12 6477 59576 htuch Implementing Endpoint lease... // 2019/04/20 6161 49415 jmarantz Pack tags and tag extracted names From 9caab6a6c59a0f51f67a6a41d856030c735caacc Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sat, 20 Apr 2019 20:28:15 -0400 Subject: [PATCH 34/39] OK it's impossible to put usernames in comments due to pedantic spelling. Signed-off-by: Joshua Marantz --- test/integration/stats_integration_test.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index cb94dba878ff..fa09138e12df 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -204,11 +204,11 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { // // History of golden values: // - // Date PR Bytes Per Cluster Author Notes - // ---------- ----- ----------------- ---------------- ----- - // 2019/03/20 6329 59015 cmluciano Initial version - // 2019/04/12 6477 59576 htuch Implementing Endpoint lease... - // 2019/04/20 6161 49415 jmarantz Pack tags and tag extracted names + // Date PR Bytes Per Cluster Notes + // ---------- ----- ----------------- ----- + // 2019/03/20 6329 59015 Initial version + // 2019/04/12 6477 59576 Implementing Endpoint lease... + // 2019/04/20 6161 49415 Pack tags and tag extracted names EXPECT_EQ(m_per_cluster, 49415); } From e12810b9713fea8544c068e02b249f11a3b46fd0 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 22 Apr 2019 16:03:44 -0400 Subject: [PATCH 35/39] fix comment. Signed-off-by: Joshua Marantz --- source/common/stats/symbol_table_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 0765045f29d9..24390a4eee29 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -502,7 +502,7 @@ struct HeterogeneousStatNameEqual { size_t operator()(const StatNameStorage& a, StatName b) const { return a.statName() == b; } }; -// Encapsulates a set. We use a subclass here rather than a +// Encapsulates a set. We use containment here rather than a // 'using' alias because we need to ensure that when the set is destructed, // StatNameStorage::free(symbol_table) is called on each entry. It is a little // easier at the call-sites in thread_local_store.cc to implement this an From 30b188e43265fa2df508607edf80092a63dab20f Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Wed, 24 Apr 2019 17:41:44 -0400 Subject: [PATCH 36/39] Add more comments to help clarify review comments. Signed-off-by: Joshua Marantz --- include/envoy/stats/tag_producer.h | 9 ++++++- source/common/stats/histogram_impl.h | 18 ++++++++++++- source/common/stats/metric_impl.cc | 2 +- source/common/stats/metric_impl.h | 11 +++----- .../common/stats/stat_data_allocator_impl.h | 26 +++++++++++++++++-- source/common/stats/symbol_table_impl.h | 3 +++ 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/include/envoy/stats/tag_producer.h b/include/envoy/stats/tag_producer.h index 9da9ee106037..5ec72877981b 100644 --- a/include/envoy/stats/tag_producer.h +++ b/include/envoy/stats/tag_producer.h @@ -18,7 +18,14 @@ class TagProducer { /** * Take a metric name and a vector then add proper tags into the vector and - * return an extracted metric name. + * return an extracted metric name. The tags array will be populated with + * name/value pairs extracted from the full metric name, using the regular + * expressions in source/common/config/well_known_names.cc. For example, the + * stat name "vhost.foo.vcluster.bar.c1" would have "foo" extracted as the + * value of tag "vhost" and "bar" extracted as the value of tag + * "vcluster", so this will populate tags with {"vhost", "foo"} and + * {"vcluster", "bar"}, and return "vhost.vcluster.c1". + * * @param metric_name std::string a name of Stats::Metric (Counter, Gauge, Histogram). * @param tags std::vector a set of Stats::Tag. */ diff --git a/source/common/stats/histogram_impl.h b/source/common/stats/histogram_impl.h index 321954c89f68..3669afec0c31 100644 --- a/source/common/stats/histogram_impl.h +++ b/source/common/stats/histogram_impl.h @@ -57,7 +57,17 @@ class HistogramImpl : public Histogram, public MetricImpl { : MetricImpl(tag_extracted_name, tags, parent.symbolTable()), name_(name, parent.symbolTable()), parent_(parent) {} ~HistogramImpl() { + // We must explicitly free the StatName here using the SymbolTable reference + // we access via parent_. A pure RAII alternative would be to use + // StatNameTempStorage rather than StatNameStorage, which will cost a total + // of 16 bytes per stat, counting the extra SymbolTable& reference here, + // plus the extra SymbolTable& reference in MetricImpl. name_.free(symbolTable()); + + // We must explicitly free the StatName here in order to supply the + // SymbolTable reference. An RAII alternative would be to store a + // reference to the SymbolTable in MetricImpl, which would cost 8 bytes + // per stat. MetricImpl::clear(); } @@ -83,7 +93,13 @@ class HistogramImpl : public Histogram, public MetricImpl { class NullHistogramImpl : public Histogram, NullMetricImpl { public: explicit NullHistogramImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} - ~NullHistogramImpl() { MetricImpl::clear(); } + ~NullHistogramImpl() { + // MetricImpl must be explicitly cleared() before destruction, otherwise it + // will not be able to access the SymbolTable& to free the symbols. An RAII + // alternative would be to store the SymbolTable reference in the + // MetricImpl, costing 8 bytes per stat. + MetricImpl::clear(); + } void recordValue(uint64_t) override {} }; diff --git a/source/common/stats/metric_impl.cc b/source/common/stats/metric_impl.cc index d68240587915..b111758d675b 100644 --- a/source/common/stats/metric_impl.cc +++ b/source/common/stats/metric_impl.cc @@ -20,7 +20,7 @@ MetricImpl::MetricImpl(absl::string_view tag_extracted_name, const std::vector(), symbol_table), symbol_table_(symbol_table), - stat_name_storage_("", symbol_table) {} - ~NullMetricImpl() { stat_name_storage_.free(symbol_table_); } + : MetricImpl("", std::vector(), symbol_table), stat_name_storage_("", symbol_table) {} - const SymbolTable& symbolTable() const override { return symbol_table_; } - SymbolTable& symbolTable() override { return symbol_table_; } + const SymbolTable& symbolTable() const override { return stat_name_storage_.symbolTable(); } + SymbolTable& symbolTable() override { return stat_name_storage_.symbolTable(); } bool used() const override { return false; } StatName statName() const override { return stat_name_storage_.statName(); } private: - SymbolTable& symbol_table_; - StatNameStorage stat_name_storage_; + StatNameTempStorage stat_name_storage_; }; } // namespace Stats diff --git a/source/common/stats/stat_data_allocator_impl.h b/source/common/stats/stat_data_allocator_impl.h index 9789f1f69134..82db3a9562b8 100644 --- a/source/common/stats/stat_data_allocator_impl.h +++ b/source/common/stats/stat_data_allocator_impl.h @@ -63,6 +63,11 @@ template class CounterImpl : public Counter, public MetricImpl : MetricImpl(tag_extracted_name, tags, alloc.symbolTable()), data_(data), alloc_(alloc) {} ~CounterImpl() override { alloc_.free(data_); + + // MetricImpl must be explicitly cleared() before destruction, otherwise it + // will not be able to access the SymbolTable& to free the symbols. An RAII + // alternative would be to store the SymbolTable reference in the + // MetricImpl, costing 8 bytes per stat. MetricImpl::clear(); } @@ -94,7 +99,13 @@ template class CounterImpl : public Counter, public MetricImpl class NullCounterImpl : public Counter, NullMetricImpl { public: explicit NullCounterImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} - ~NullCounterImpl() override { MetricImpl::clear(); } + ~NullCounterImpl() override { + // MetricImpl must be explicitly cleared() before destruction, otherwise it + // will not be able to access the SymbolTable& to free the symbols. An RAII + // alternative would be to store the SymbolTable reference in the + // MetricImpl, costing 8 bytes per stat. + MetricImpl::clear(); + } void add(uint64_t) override {} void inc() override {} @@ -113,6 +124,11 @@ template class GaugeImpl : public Gauge, public MetricImpl { : MetricImpl(tag_extracted_name, tags, alloc.symbolTable()), data_(data), alloc_(alloc) {} ~GaugeImpl() override { alloc_.free(data_); + + // MetricImpl must be explicitly cleared() before destruction, otherwise it + // will not be able to access the SymbolTable& to free the symbols. An RAII + // alternative would be to store the SymbolTable reference in the + // MetricImpl, costing 8 bytes per stat. MetricImpl::clear(); } @@ -150,7 +166,13 @@ template class GaugeImpl : public Gauge, public MetricImpl { class NullGaugeImpl : public Gauge, NullMetricImpl { public: explicit NullGaugeImpl(SymbolTable& symbol_table) : NullMetricImpl(symbol_table) {} - ~NullGaugeImpl() override { MetricImpl::clear(); } + ~NullGaugeImpl() override { + // MetricImpl must be explicitly cleared() before destruction, otherwise it + // will not be able to access the SymbolTable& to free the symbols. An RAII + // alternative would be to store the SymbolTable reference in the + // MetricImpl, costing 8 bytes per stat. + MetricImpl::clear(); + } void add(uint64_t) override {} void inc() override {} diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 24390a4eee29..40d0aaf2194b 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -384,6 +384,9 @@ class StatNameTempStorage : public StatNameStorage { ~StatNameTempStorage() { free(symbol_table_); } + SymbolTable& symbolTable() { return symbol_table_; } + const SymbolTable& symbolTable() const { return symbol_table_; } + private: SymbolTable& symbol_table_; }; From 818353380e63e7edab5d9509e44509fdde134055 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 25 Apr 2019 10:40:12 -0400 Subject: [PATCH 37/39] Rename StatNameTempStorage to StatNameManagedStorage and use it in Hystrix. Signed-off-by: Joshua Marantz --- source/common/stats/histogram_impl.h | 2 +- source/common/stats/isolated_store_impl.h | 6 +++--- source/common/stats/metric_impl.h | 2 +- source/common/stats/scope_prefixer.cc | 2 +- source/common/stats/scope_prefixer.h | 6 +++--- source/common/stats/symbol_table_impl.h | 12 ++++++------ source/common/stats/thread_local_store.h | 8 ++++---- source/extensions/stat_sinks/hystrix/hystrix.cc | 2 -- source/extensions/stat_sinks/hystrix/hystrix.h | 3 +-- test/common/stats/symbol_table_impl_test.cc | 12 ++++++------ test/integration/server.h | 6 +++--- test/mocks/stats/mocks.cc | 2 +- test/mocks/stats/mocks.h | 2 +- test/server/http/admin_test.cc | 4 ++-- 14 files changed, 33 insertions(+), 36 deletions(-) diff --git a/source/common/stats/histogram_impl.h b/source/common/stats/histogram_impl.h index 3669afec0c31..5fceb00687ff 100644 --- a/source/common/stats/histogram_impl.h +++ b/source/common/stats/histogram_impl.h @@ -59,7 +59,7 @@ class HistogramImpl : public Histogram, public MetricImpl { ~HistogramImpl() { // We must explicitly free the StatName here using the SymbolTable reference // we access via parent_. A pure RAII alternative would be to use - // StatNameTempStorage rather than StatNameStorage, which will cost a total + // StatNameManagedStorage rather than StatNameStorage, which will cost a total // of 16 bytes per stat, counting the extra SymbolTable& reference here, // plus the extra SymbolTable& reference in MetricImpl. name_.free(symbolTable()); diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index adaf864bee38..c0c34576e011 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -81,15 +81,15 @@ class IsolatedStoreImpl : public StoreImpl { } Counter& counter(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return histogramFromStatName(storage.statName()); } diff --git a/source/common/stats/metric_impl.h b/source/common/stats/metric_impl.h index 911822cd4f37..4fc6d160bbba 100644 --- a/source/common/stats/metric_impl.h +++ b/source/common/stats/metric_impl.h @@ -56,7 +56,7 @@ class NullMetricImpl : public MetricImpl { StatName statName() const override { return stat_name_storage_.statName(); } private: - StatNameTempStorage stat_name_storage_; + StatNameManagedStorage stat_name_storage_; }; } // namespace Stats diff --git a/source/common/stats/scope_prefixer.cc b/source/common/stats/scope_prefixer.cc index 8469f6b8c57b..521696059e4a 100644 --- a/source/common/stats/scope_prefixer.cc +++ b/source/common/stats/scope_prefixer.cc @@ -22,7 +22,7 @@ ScopePtr ScopePrefixer::createScopeFromStatName(StatName name) { } ScopePtr ScopePrefixer::createScope(const std::string& name) { - StatNameTempStorage stat_name_storage(Utility::sanitizeStatsName(name), symbolTable()); + StatNameManagedStorage stat_name_storage(Utility::sanitizeStatsName(name), symbolTable()); return createScopeFromStatName(stat_name_storage.statName()); } diff --git a/source/common/stats/scope_prefixer.h b/source/common/stats/scope_prefixer.h index 8b4db408397a..22bf13e8932c 100644 --- a/source/common/stats/scope_prefixer.h +++ b/source/common/stats/scope_prefixer.h @@ -23,15 +23,15 @@ class ScopePrefixer : public Scope { void deliverHistogramToSinks(const Histogram& histograms, uint64_t val) override; Counter& counter(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return histogramFromStatName(storage.statName()); } diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 40d0aaf2194b..5eb0dadc1980 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -251,7 +251,7 @@ class SymbolTableImpl : public SymbolTable { * will fire to guard against symbol-table leaks. * * Thus this class is inconvenient to directly use as temp storage for building - * a StatName from a string. Instead it should be used via StatNameTempStorage. + * a StatName from a string. Instead it should be used via StatNameManagedStorage. */ class StatNameStorage { public: @@ -371,18 +371,18 @@ StatName StatNameStorage::statName() const { return StatName(bytes_.get()); } * than stored in a larger structure such as a map, where the redundant copies * of the SymbolTable& would be costly in aggregate. */ -class StatNameTempStorage : public StatNameStorage { +class StatNameManagedStorage : public StatNameStorage { public: // Basic constructor for when you have a name as a string, and need to // generate symbols for it. - StatNameTempStorage(absl::string_view name, SymbolTable& table) + StatNameManagedStorage(absl::string_view name, SymbolTable& table) : StatNameStorage(name, table), symbol_table_(table) {} // Obtains new backing storage for an already existing StatName. - StatNameTempStorage(StatName src, SymbolTable& table) + StatNameManagedStorage(StatName src, SymbolTable& table) : StatNameStorage(src, table), symbol_table_(table) {} - ~StatNameTempStorage() { free(symbol_table_); } + ~StatNameManagedStorage() { free(symbol_table_); } SymbolTable& symbolTable() { return symbol_table_; } const SymbolTable& symbolTable() const { return symbol_table_; } @@ -511,7 +511,7 @@ struct HeterogeneousStatNameEqual { // easier at the call-sites in thread_local_store.cc to implement this an // explicit free() method, analogous to StatNameStorage::free(), compared to // storing a SymbolTable reference in the class and doing the free in the -// destructor, like StatNameTempStorage. +// destructor, like StatNameManagedStorage. class StatNameStorageSet { public: using HashSet = diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 4dab95a1f9e5..9e223ac3a9a2 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -229,15 +229,15 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo SymbolTable& symbolTable() override { return parent_.symbolTable(); } Counter& counter(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return histogramFromStatName(storage.statName()); } @@ -267,7 +267,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo StatNameHashSet* tls_rejected_stats, StatType& null_stat); void extractTagsAndTruncate(StatName& name, - std::unique_ptr& truncated_name_storage, + std::unique_ptr& truncated_name_storage, std::vector& tags, std::string& tag_extracted_name); static std::atomic next_scope_id_; diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc index 2f4ad6155a06..f8442b2c9d52 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.cc +++ b/source/extensions/stat_sinks/hystrix/hystrix.cc @@ -276,8 +276,6 @@ HystrixSink::HystrixSink(Server::Instance& server, const uint64_t num_buckets) MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false); } -HystrixSink::~HystrixSink() { cluster_upstream_rq_time_.free(server_.stats().symbolTable()); } - Http::Code HystrixSink::handlerHystrixEventStream(absl::string_view, Http::HeaderMap& response_headers, Buffer::Instance&, diff --git a/source/extensions/stat_sinks/hystrix/hystrix.h b/source/extensions/stat_sinks/hystrix/hystrix.h index 4079a05fa144..c37a98db15c6 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.h +++ b/source/extensions/stat_sinks/hystrix/hystrix.h @@ -49,7 +49,6 @@ typedef std::unique_ptr ClusterStatsCachePtr; class HystrixSink : public Stats::Sink, public Logger::Loggable { public: HystrixSink(Server::Instance& server, uint64_t num_buckets); - ~HystrixSink() override; Http::Code handlerHystrixEventStream(absl::string_view, Http::HeaderMap& response_headers, Buffer::Instance&, Server::AdminStream& admin_stream); void flush(Stats::Source& source) override; @@ -160,7 +159,7 @@ class HystrixSink : public Stats::Sink, public Logger::Loggable cluster_stats_cache_map_; // Saved StatName for "cluster.upstream_rq_time" for fast comparisons in loop. - Stats::StatNameStorage cluster_upstream_rq_time_; + Stats::StatNameManagedStorage cluster_upstream_rq_time_; }; typedef std::unique_ptr HystrixSinkPtr; diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index 7391f45ecfbb..a53e6ad8979c 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -396,11 +396,11 @@ TEST_P(StatNameTest, RacingSymbolCreation) { // Block each thread on waking up a common condition variable, // so we make it likely to race on creation. creation.wait(); - StatNameTempStorage initial(stat_name_string, *table_); + StatNameManagedStorage initial(stat_name_string, *table_); creates.DecrementCount(); access.wait(); - StatNameTempStorage second(stat_name_string, *table_); + StatNameManagedStorage second(stat_name_string, *table_); accesses.DecrementCount(); wait.wait(); @@ -462,11 +462,11 @@ TEST_P(StatNameTest, MutexContentionOnExistingSymbols) { // Block each thread on waking up a common condition variable, // so we make it likely to race on creation. creation.wait(); - StatNameTempStorage initial(stat_name_string, *table_); + StatNameManagedStorage initial(stat_name_string, *table_); creates.DecrementCount(); access.wait(); - StatNameTempStorage second(stat_name_string, *table_); + StatNameManagedStorage second(stat_name_string, *table_); accesses.DecrementCount(); wait.wait(); @@ -513,11 +513,11 @@ TEST_P(StatNameTest, SharedStatNameStorageSetInsertAndFind) { for (int i = 0; i < iters; ++i) { std::string foo = absl::StrCat("foo", i); auto insertion = set.insert(StatNameStorage(foo, *table_)); - StatNameTempStorage temp_foo(foo, *table_); + StatNameManagedStorage temp_foo(foo, *table_); auto found = set.find(temp_foo.statName()); EXPECT_EQ(found->statName().data(), insertion.first->statName().data()); } - StatNameTempStorage bar("bar", *table_); + StatNameManagedStorage bar("bar", *table_); EXPECT_EQ(set.end(), set.find(bar.statName())); EXPECT_EQ(iters, set.size()); set.free(*table_); diff --git a/test/integration/server.h b/test/integration/server.h index 844036d83619..d8cd3cbb840a 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -94,15 +94,15 @@ class TestScopeWrapper : public Scope { } Counter& counter(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return counterFromStatName(storage.statName()); } Gauge& gauge(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return gaugeFromStatName(storage.statName()); } Histogram& histogram(const std::string& name) override { - StatNameTempStorage storage(name, symbolTable()); + StatNameManagedStorage storage(name, symbolTable()); return histogramFromStatName(storage.statName()); } diff --git a/test/mocks/stats/mocks.cc b/test/mocks/stats/mocks.cc index 18f487294e19..bea653c71e84 100644 --- a/test/mocks/stats/mocks.cc +++ b/test/mocks/stats/mocks.cc @@ -29,7 +29,7 @@ MockMetric::MetricName::~MetricName() { void MockMetric::setTagExtractedName(absl::string_view name) { tag_extracted_name_ = std::string(name); tag_extracted_stat_name_ = - std::make_unique(tagExtractedName(), *symbol_table_); + std::make_unique(tagExtractedName(), *symbol_table_); } void MockMetric::MetricName::MetricName::operator=(absl::string_view name) { diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index bb1d5007bf24..37edb5e6a6a3 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -71,7 +71,7 @@ class MockMetric : public virtual Metric { private: std::string tag_extracted_name_; - std::unique_ptr tag_extracted_stat_name_; + std::unique_ptr tag_extracted_stat_name_; }; class MockCounter : public Counter, public MockMetric { diff --git a/test/server/http/admin_test.cc b/test/server/http/admin_test.cc index 880317825dc2..52ac66b6b980 100644 --- a/test/server/http/admin_test.cc +++ b/test/server/http/admin_test.cc @@ -1269,12 +1269,12 @@ class PrometheusStatsFormatterTest : public testing::Test { PrometheusStatsFormatterTest() : alloc_(symbol_table_) {} void addCounter(const std::string& name, std::vector cluster_tags) { - Stats::StatNameTempStorage storage(name, symbol_table_); + Stats::StatNameManagedStorage storage(name, symbol_table_); counters_.push_back(alloc_.makeCounter(storage.statName(), name, cluster_tags)); } void addGauge(const std::string& name, std::vector cluster_tags) { - Stats::StatNameTempStorage storage(name, symbol_table_); + Stats::StatNameManagedStorage storage(name, symbol_table_); gauges_.push_back(alloc_.makeGauge(storage.statName(), name, cluster_tags)); } From ead848f0eedb9148b4b5b65c4e20e552ea4bdf30 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 25 Apr 2019 10:45:29 -0400 Subject: [PATCH 38/39] Improve commenting for StatNameManagedStorage as that's what we expect most call-sites outside of stats to use. Signed-off-by: Joshua Marantz --- source/common/stats/symbol_table_impl.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 5eb0dadc1980..173ad3e7a017 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -367,9 +367,18 @@ StatName StatNameStorage::statName() const { return StatName(bytes_.get()); } * Contains the backing store for a StatName and enough context so it can * self-delete through RAII. This works by augmenting StatNameStorage with a * reference to the SymbolTable&, so it has an extra 8 bytes of footprint. It - * is intended to be used in tests or as a scoped temp in a function, rather - * than stored in a larger structure such as a map, where the redundant copies - * of the SymbolTable& would be costly in aggregate. + * is intended to be used in cases where simplicity of implementation is more + * important than byte-savings, for example: + * - outside the stats system + * - in tests + * - as a scoped temp in a function + * Due to the extra 8 bytes per instance, scalability should be taken into + * account before using this as (say) a value or key in a map. In those + * scenarios, it would be better to store the SymbolTable reference once + * for the entire map. + * + * In the stat structures, we generally use StatNameStorage to avoid the + * per-stat overhead. */ class StatNameManagedStorage : public StatNameStorage { public: From 18ae590e0f5ddff42b87574cec612d835c7e224c Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 25 Apr 2019 23:17:30 -0400 Subject: [PATCH 39/39] delete commented-out code. Signed-off-by: Joshua Marantz --- source/common/stats/thread_local_store.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index fec140f760a6..b342d2793be5 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -341,8 +341,6 @@ StatType& ThreadLocalStoreImpl::ScopeImpl::safeMakeStat( return null_stat; } else { TagExtraction extraction(parent_, name); - // std::shared_ptr stat = make_stat(parent_.alloc_, extraction.truncatedStatName(), - // extraction.tagExtractedName(), extraction.tags()); std::shared_ptr stat = make_stat(parent_.alloc_, name, extraction.tagExtractedName(), extraction.tags()); if (stat == nullptr) {