Skip to content
Merged
37 changes: 33 additions & 4 deletions envoy/stats/scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,48 @@ using CounterOptConstRef = absl::optional<std::reference_wrapper<const Counter>>
using GaugeOptConstRef = absl::optional<std::reference_wrapper<const Gauge>>;
using HistogramOptConstRef = absl::optional<std::reference_wrapper<const Histogram>>;
using TextReadoutOptConstRef = absl::optional<std::reference_wrapper<const TextReadout>>;
using ScopePtr = std::unique_ptr<Scope>;
using ConstScopeSharedPtr = std::shared_ptr<const Scope>;
using ScopeSharedPtr = std::shared_ptr<Scope>;

// TODO(jmarantz): In the initial transformation to store Scope as shared_ptr,
// we don't change all the references, as that would result in an unreviewable
// PR that combines a small number of semantic changes and a large number of
// files with trivial changes. Furthermore, code that depends on the Envoy stats
// infrastructure that's not in this repo will stop compiling when we remove
// ScopePtr. We should fully remove this alias in a future PR and change all the
// references, once known parthers that might break from this change of a chance

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// references, once known parthers that might break from this change of a chance
// references, once known consumers that might break from this change have a chance

?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good; done by hand to avoid what I fear would be a DCO violation :) Committing shortly.

// to do the global replace in their own repos.
using ScopePtr = ScopeSharedPtr;

template <class StatType> using IterateFn = std::function<bool(const RefcountPtr<StatType>&)>;

/**
* A named scope for stats. Scopes are a grouping of stats that can be acted on as a unit if needed
* (for example to free/delete all of them).
*
* We enable use of shared pointers for Scopes to make it possible for the admin
* stats handler to safely capture all the scope references and remain robust to
* other threads deleting those scopes while rendering an admin stats page.
*
* We use std::shared_ptr rather than Stats::RefcountPtr, which we use for other
* stats, because:
* * existing uses of shared_ptr<Scope> exist in the Wasm extension and would need
* to be rewritten to allow for RefcountPtr<Scope>.
* * the main advantage of RefcountPtr is it's smaller per instance by (IIRC) 16
* bytes, but there are not typically enough scopes that the extra per-scope
* overhead would matter.
* * It's a little less coding to use enable_shared_from_this compared to adding
* a ref_count to the scope object, for each of its implementations.
*/
class Scope {
class Scope : public std::enable_shared_from_this<Scope> {
public:
virtual ~Scope() = default;

/** @return a shared_ptr for this */
ScopeSharedPtr makeShared() { return shared_from_this(); }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be named getShared() or similar; makeShared() is too similar to std::make_shared() but this one doesn't construct a new object.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point: done.

/** @return a const shared_ptr for this */
ConstScopeSharedPtr makeConstShared() const { return shared_from_this(); }

/**
* Allocate a new scope. NOTE: The implementation should correctly handle overlapping scopes
* that point to the same reference counted backing stats. This allows a new scope to be
Expand All @@ -47,7 +76,7 @@ class Scope {
*
* @param name supplies the scope's namespace prefix.
*/
virtual ScopePtr createScope(const std::string& name) PURE;
virtual ScopeSharedPtr createScope(const std::string& name) PURE;

/**
* Allocate a new scope. NOTE: The implementation should correctly handle overlapping scopes
Expand All @@ -56,7 +85,7 @@ class Scope {
*
* @param name supplies the scope's namespace prefix.
*/
virtual ScopePtr scopeFromStatName(StatName name) PURE;
virtual ScopeSharedPtr scopeFromStatName(StatName name) PURE;

/**
* Deliver an individual histogram value to all registered sinks.
Expand Down
13 changes: 8 additions & 5 deletions source/common/stats/isolated_store_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ IsolatedStoreImpl::IsolatedStoreImpl(SymbolTable& symbol_table)
return alloc_.makeTextReadout(name, name, StatNameTagVector{});
}),
null_counter_(new NullCounterImpl(symbol_table)),
null_gauge_(new NullGaugeImpl(symbol_table)) {}
null_gauge_(new NullGaugeImpl(symbol_table)),
default_scope_(std::make_shared<ScopePrefixer>("", *this)) {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we run into a situation wherein we have a shared_ptr to ScopePrefixer for the admin panel, but not the underlying scope?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lifetime concerns are not changed by this PR; the Store needs to outlive all the Scopes declared from it.

In general if you instantiate any Scope-derived object on the stack, and you will get some time of failure (e.g. a weak_ptr exception).

That is why I changed default_scope_ to be allocated with make_shared, enabling #19693 to hold onto a reference to it, and mirroring the implementation in thread_local_store.cc.


ScopePtr IsolatedStoreImpl::createScope(const std::string& name) {
return std::make_unique<ScopePrefixer>(name, *this);
IsolatedStoreImpl::~IsolatedStoreImpl() = default;

ScopeSharedPtr IsolatedStoreImpl::createScope(const std::string& name) {
return std::make_shared<ScopePrefixer>(name, *this);
}

ScopePtr IsolatedStoreImpl::scopeFromStatName(StatName name) {
return std::make_unique<ScopePrefixer>(name, *this);
ScopeSharedPtr IsolatedStoreImpl::scopeFromStatName(StatName name) {
return std::make_shared<ScopePrefixer>(name, *this);
}

} // namespace Stats
Expand Down
9 changes: 5 additions & 4 deletions source/common/stats/isolated_store_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class IsolatedStoreImpl : public StoreImpl {
public:
IsolatedStoreImpl();
explicit IsolatedStoreImpl(SymbolTable& symbol_table);
~IsolatedStoreImpl() override;

// Stats::Scope
Counter& counterFromStatNameWithTags(const StatName& name,
Expand All @@ -140,8 +141,8 @@ class IsolatedStoreImpl : public StoreImpl {
Counter& counter = counters_.get(joiner.nameWithTags());
return counter;
}
ScopePtr createScope(const std::string& name) override;
ScopePtr scopeFromStatName(StatName name) override;
ScopeSharedPtr createScope(const std::string& name) override;
ScopeSharedPtr scopeFromStatName(StatName name) override;
void deliverHistogramToSinks(const Histogram&, uint64_t) override {}
Gauge& gaugeFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef tags,
Gauge::ImportMode import_mode) override {
Expand Down Expand Up @@ -236,8 +237,7 @@ class IsolatedStoreImpl : public StoreImpl {
if (f_size != nullptr) {
f_size(1);
}
const Scope& scope = *this;
f_stat(scope);
f_stat(*default_scope_);
}

Stats::StatName prefix() const override { return StatName(); }
Expand Down Expand Up @@ -265,6 +265,7 @@ class IsolatedStoreImpl : public StoreImpl {
IsolatedStatsCache<TextReadout> text_readouts_;
RefcountPtr<NullCounterImpl> null_counter_;
RefcountPtr<NullGaugeImpl> null_gauge_;
ScopeSharedPtr default_scope_;
};

} // namespace Stats
Expand Down
6 changes: 3 additions & 3 deletions source/common/stats/scope_prefixer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ ScopePrefixer::ScopePrefixer(StatName prefix, Scope& scope)

ScopePrefixer::~ScopePrefixer() { prefix_.free(symbolTable()); }

ScopePtr ScopePrefixer::scopeFromStatName(StatName name) {
ScopeSharedPtr ScopePrefixer::scopeFromStatName(StatName name) {
SymbolTable::StoragePtr joined = symbolTable().join({prefix_.statName(), name});
return std::make_unique<ScopePrefixer>(StatName(joined.get()), scope_);
return std::make_shared<ScopePrefixer>(StatName(joined.get()), scope_);
}

ScopePtr ScopePrefixer::createScope(const std::string& name) {
ScopeSharedPtr ScopePrefixer::createScope(const std::string& name) {
StatNameManagedStorage stat_name_storage(Utility::sanitizeStatsName(name), symbolTable());
return scopeFromStatName(stat_name_storage.statName());
}
Expand Down
4 changes: 2 additions & 2 deletions source/common/stats/scope_prefixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class ScopePrefixer : public Scope {
~ScopePrefixer() override;

// Scope
ScopePtr createScope(const std::string& name) override;
ScopePtr scopeFromStatName(StatName name) override;
ScopeSharedPtr createScope(const std::string& name) override;
ScopeSharedPtr scopeFromStatName(StatName name) override;
Counter& counterFromStatNameWithTags(const StatName& name,
StatNameTagVectorOptConstRef tags) override;
Gauge& gaugeFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef tags,
Expand Down
2 changes: 1 addition & 1 deletion source/common/stats/stat_merger.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class StatMerger {
// preserve all stats throughout the hot restart. Then, when the restart completes, dropping
// the scope will drop exactly those stats whose names have not already been accessed through
// another store/scope.
ScopePtr temp_scope_;
ScopeSharedPtr temp_scope_;
};

} // namespace Stats
Expand Down
4 changes: 2 additions & 2 deletions source/common/stats/thread_local_store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ std::vector<CounterSharedPtr> ThreadLocalStoreImpl::counters() const {
return ret;
}

ScopePtr ThreadLocalStoreImpl::createScope(const std::string& name) {
ScopeSharedPtr ThreadLocalStoreImpl::createScope(const std::string& name) {
StatNameManagedStorage stat_name_storage(Utility::sanitizeStatsName(name), alloc_.symbolTable());
return scopeFromStatName(stat_name_storage.statName());
}

ScopePtr ThreadLocalStoreImpl::scopeFromStatName(StatName name) {
ScopeSharedPtr ThreadLocalStoreImpl::scopeFromStatName(StatName name) {
auto new_scope = std::make_unique<ScopeImpl>(*this, name);
Thread::LockGuard lock(lock_);
scopes_.emplace(new_scope.get());
Expand Down
10 changes: 5 additions & 5 deletions source/common/stats/thread_local_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ class ThreadLocalStoreImpl : Logger::Loggable<Logger::Id::stats>, public StoreRo
Counter& counterFromString(const std::string& name) override {
return default_scope_->counterFromString(name);
}
ScopePtr createScope(const std::string& name) override;
ScopePtr scopeFromStatName(StatName name) override;
ScopeSharedPtr createScope(const std::string& name) override;
ScopeSharedPtr scopeFromStatName(StatName name) override;
void deliverHistogramToSinks(const Histogram& histogram, uint64_t value) override {
return default_scope_->deliverHistogramToSinks(histogram, value);
}
Expand Down Expand Up @@ -348,10 +348,10 @@ class ThreadLocalStoreImpl : Logger::Loggable<Logger::Id::stats>, public StoreRo
Histogram::Unit unit) override;
TextReadout& textReadoutFromStatNameWithTags(const StatName& name,
StatNameTagVectorOptConstRef tags) override;
ScopePtr createScope(const std::string& name) override {
ScopeSharedPtr createScope(const std::string& name) override {
return parent_.createScope(symbolTable().toString(prefix_.statName()) + "." + name);
}
ScopePtr scopeFromStatName(StatName name) override {
ScopeSharedPtr scopeFromStatName(StatName name) override {
SymbolTable::StoragePtr joined = symbolTable().join({prefix_.statName(), name});
return parent_.scopeFromStatName(StatName(joined.get()));
}
Expand Down Expand Up @@ -520,7 +520,7 @@ class ThreadLocalStoreImpl : Logger::Loggable<Logger::Id::stats>, public StoreRo
ThreadLocal::TypedSlotPtr<TlsCache> tls_cache_;
mutable Thread::MutexBasicLockable lock_;
absl::flat_hash_set<ScopeImpl*> scopes_ ABSL_GUARDED_BY(lock_);
ScopePtr default_scope_;
ScopeSharedPtr default_scope_;
std::list<std::reference_wrapper<Sink>> timer_sinks_;
TagProducerPtr tag_producer_;
StatsMatcherPtr stats_matcher_;
Expand Down
2 changes: 1 addition & 1 deletion source/common/stats/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ struct ElementVisitor {

namespace Utility {

ScopePtr scopeFromStatNames(Scope& scope, const StatNameVec& elements) {
ScopeSharedPtr scopeFromStatNames(Scope& scope, const StatNameVec& elements) {
SymbolTable::StoragePtr joined = scope.symbolTable().join(elements);
return scope.scopeFromStatName(StatName(joined.get()));
}
Expand Down
2 changes: 1 addition & 1 deletion source/common/stats/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ absl::optional<StatName> findTag(const Metric& metric, StatName find_tag_name);
* @param elements The vector of mixed DynamicName and StatName
* @return A scope named using the joined elements.
*/
ScopePtr scopeFromStatNames(Scope& scope, const StatNameVec& names);
ScopeSharedPtr scopeFromStatNames(Scope& scope, const StatNameVec& names);

/**
* Creates a counter from a vector of tokens which are used to create the
Expand Down
24 changes: 12 additions & 12 deletions test/common/stats/isolated_store_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class StatsIsolatedStoreImplTest : public testing::Test {
};

TEST_F(StatsIsolatedStoreImplTest, All) {
ScopePtr scope1 = store_->createScope("scope1.");
ScopeSharedPtr scope1 = store_->createScope("scope1.");
Counter& c1 = store_->counterFromString("c1");
Counter& c2 = scope1->counterFromString("c2");
EXPECT_EQ("c1", c1.name());
Expand Down Expand Up @@ -108,11 +108,11 @@ TEST_F(StatsIsolatedStoreImplTest, All) {
ASSERT_TRUE(found_histogram.has_value());
EXPECT_EQ(&h1, &found_histogram->get());

ScopePtr scope2 = scope1->scopeFromStatName(makeStatName("foo."));
ScopeSharedPtr scope2 = scope1->scopeFromStatName(makeStatName("foo."));
EXPECT_EQ("scope1.foo.bar", scope2->counterFromString("bar").name());

// Validate that we sanitize away bad characters in the stats prefix.
ScopePtr scope3 = scope1->createScope(std::string("foo:\0:.", 7));
ScopeSharedPtr scope3 = scope1->createScope(std::string("foo:\0:.", 7));
EXPECT_EQ("scope1.foo___.bar", scope3->counterFromString("bar").name());

EXPECT_EQ(4UL, store_->counters().size());
Expand All @@ -125,14 +125,14 @@ TEST_F(StatsIsolatedStoreImplTest, All) {
}

TEST_F(StatsIsolatedStoreImplTest, PrefixIsStatName) {
ScopePtr scope1 = store_->createScope("scope1");
ScopePtr scope2 = scope1->scopeFromStatName(makeStatName("scope2"));
ScopeSharedPtr scope1 = store_->createScope("scope1");
ScopeSharedPtr scope2 = scope1->scopeFromStatName(makeStatName("scope2"));
Counter& c1 = scope2->counterFromString("c1");
EXPECT_EQ("scope1.scope2.c1", c1.name());
}

TEST_F(StatsIsolatedStoreImplTest, AllWithSymbolTable) {
ScopePtr scope1 = store_->createScope("scope1.");
ScopeSharedPtr scope1 = store_->createScope("scope1.");
Counter& c1 = store_->counterFromStatName(makeStatName("c1"));
Counter& c2 = scope1->counterFromStatName(makeStatName("c2"));
EXPECT_EQ("c1", c1.name());
Expand Down Expand Up @@ -174,11 +174,11 @@ TEST_F(StatsIsolatedStoreImplTest, AllWithSymbolTable) {
h1.recordValue(200);
h2.recordValue(200);

ScopePtr scope2 = scope1->createScope("foo.");
ScopeSharedPtr scope2 = scope1->createScope("foo.");
EXPECT_EQ("scope1.foo.bar", scope2->counterFromStatName(makeStatName("bar")).name());

// Validate that we sanitize away bad characters in the stats prefix.
ScopePtr scope3 = scope1->createScope(std::string("foo:\0:.", 7));
ScopeSharedPtr scope3 = scope1->createScope(std::string("foo:\0:.", 7));
EXPECT_EQ("scope1.foo___.bar", scope3->counterFromString("bar").name());

EXPECT_EQ(4UL, store_->counters().size());
Expand All @@ -187,7 +187,7 @@ TEST_F(StatsIsolatedStoreImplTest, AllWithSymbolTable) {
}

TEST_F(StatsIsolatedStoreImplTest, ConstSymtabAccessor) {
ScopePtr scope = store_->createScope("scope.");
ScopeSharedPtr scope = store_->createScope("scope.");
const Scope& cscope = *scope;
const SymbolTable& const_symbol_table = cscope.constSymbolTable();
SymbolTable& symbol_table = scope->symbolTable();
Expand All @@ -197,7 +197,7 @@ TEST_F(StatsIsolatedStoreImplTest, ConstSymtabAccessor) {
TEST_F(StatsIsolatedStoreImplTest, LongStatName) {
const std::string long_string(128, 'A');

ScopePtr scope = store_->createScope("scope.");
ScopeSharedPtr scope = store_->createScope("scope.");
Counter& counter = scope->counterFromString(long_string);
EXPECT_EQ(absl::StrCat("scope.", long_string), counter.name());
}
Expand Down Expand Up @@ -250,8 +250,8 @@ TEST_F(StatsIsolatedStoreImplTest, StatNamesStruct) {
MAKE_STAT_NAMES_STRUCT(StatNames, ALL_TEST_STATS);
StatNames stat_names(store_->symbolTable());
EXPECT_EQ("prefix", store_->symbolTable().toString(stat_names.prefix_));
ScopePtr scope1 = store_->createScope("scope1.");
ScopePtr scope2 = store_->createScope("scope2.");
ScopeSharedPtr scope1 = store_->createScope("scope1.");
ScopeSharedPtr scope2 = store_->createScope("scope2.");
MAKE_STATS_STRUCT(Stats, StatNames, ALL_TEST_STATS);
Stats stats1(stat_names, *scope1);
EXPECT_EQ("scope1.test_counter", stats1.test_counter_.name());
Expand Down
Loading