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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions include/envoy/stats/scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ using TextReadoutOptConstRef = absl::optional<std::reference_wrapper<const TextR
using ScopePtr = std::unique_ptr<Scope>;
using ScopeSharedPtr = std::shared_ptr<Scope>;

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).
Expand Down Expand Up @@ -194,6 +196,47 @@ class Scope {
*/
virtual const SymbolTable& constSymbolTable() const PURE;
virtual SymbolTable& symbolTable() PURE;

/**
* Calls 'fn' for every counter. Note that in the case of overlapping scopes,
* the implementation may call fn more than one time for each counter. Iteration
* stops if `fn` returns false;
*
* @param fn Function to be run for every counter, or until fn return false.
* @return false if fn(counter) return false during iteration, true if every counter was hit.
*/
virtual bool iterate(const IterateFn<Counter>& fn) const PURE;

/**
* Calls 'fn' for every gauge. Note that in the case of overlapping scopes,
* the implementation may call fn more than one time for each gauge. Iteration
* stops if `fn` returns false;
*
* @param fn Function to be run for every gauge, or until fn return false.
* @return false if fn(gauge) return false during iteration, true if every gauge was hit.
*/
virtual bool iterate(const IterateFn<Gauge>& fn) const PURE;

/**
* Calls 'fn' for every histogram. Note that in the case of overlapping
* scopes, the implementation may call fn more than one time for each
* histogram. Iteration stops if `fn` returns false;
*
* @param fn Function to be run for every histogram, or until fn return false.
* @return false if fn(histogram) return false during iteration, true if every histogram was hit.
*/
virtual bool iterate(const IterateFn<Histogram>& fn) const PURE;

/**
* Calls 'fn' for every text readout. Note that in the case of overlapping
* scopes, the implementation may call fn more than one time for each
* text readout. Iteration stops if `fn` returns false;
*
* @param fn Function to be run for every text readout, or until fn return false.
* @return false if fn(text_readout) return false during iteration, true if every text readout
* was hit.
*/
virtual bool iterate(const IterateFn<TextReadout>& fn) const PURE;
};

} // namespace Stats
Expand Down
16 changes: 16 additions & 0 deletions source/common/stats/isolated_store_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ template <class Base> class IsolatedStatsCache {
return vec;
}

bool iterate(const IterateFn<Base>& fn) const {
for (auto& stat : stats_) {
if (!fn(stat.second)) {
return false;
}
}
return true;
}

private:
friend class IsolatedStoreImpl;

Expand Down Expand Up @@ -154,6 +163,13 @@ class IsolatedStoreImpl : public StoreImpl {
return text_readouts_.find(name);
}

bool iterate(const IterateFn<Counter>& fn) const override { return counters_.iterate(fn); }
bool iterate(const IterateFn<Gauge>& fn) const override { return gauges_.iterate(fn); }
bool iterate(const IterateFn<Histogram>& fn) const override { return histograms_.iterate(fn); }
bool iterate(const IterateFn<TextReadout>& fn) const override {
return text_readouts_.iterate(fn);
}

// Stats::Store
std::vector<CounterSharedPtr> counters() const override { return counters_.toVector(); }
std::vector<GaugeSharedPtr> gauges() const override {
Expand Down
27 changes: 27 additions & 0 deletions source/common/stats/scope_prefixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,34 @@ class ScopePrefixer : public Scope {

NullGaugeImpl& nullGauge(const std::string& str) override { return scope_.nullGauge(str); }

bool iterate(const IterateFn<Counter>& fn) const override { return iterHelper(fn); }
bool iterate(const IterateFn<Gauge>& fn) const override { return iterHelper(fn); }
bool iterate(const IterateFn<Histogram>& fn) const override { return iterHelper(fn); }
bool iterate(const IterateFn<TextReadout>& fn) const override { return iterHelper(fn); }

private:
template <class StatType> bool iterHelper(const IterateFn<StatType>& fn) const {
// We determine here what's in the scope by looking at name
// prefixes. Strictly speaking this is not correct, as a stat name can be in
// different scopes. But there is no data in `ScopePrefixer` to resurrect
// actual membership of a stat in a scope, so we go by name matching. Note
// that `ScopePrefixer` is not used in `ThreadLocalStore`, which has
// accurate maps describing which stats are in which scopes.
//
// TODO(jmarantz): In the scope of this limited implementation, it would be
// faster to match on the StatName prefix. This would be possible if
// SymbolTable exposed a split() method.
std::string prefix_str = scope_.symbolTable().toString(prefix_.statName());
if (!prefix_str.empty() && !absl::EndsWith(prefix_str, ".")) {
prefix_str += ".";
}
IterateFn<StatType> filter_scope = [&fn,
&prefix_str](const RefcountPtr<StatType>& stat) -> bool {
return !absl::StartsWith(stat->name(), prefix_str) || fn(stat);
};
return scope_.iterate(filter_scope);
}

Scope& scope_;
StatNameStorage prefix_;
};
Expand Down
37 changes: 37 additions & 0 deletions source/common/stats/thread_local_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ class ThreadLocalStoreImpl : Logger::Loggable<Logger::Id::stats>, public StoreRo
return absl::nullopt;
}

bool iterate(const IterateFn<Counter>& fn) const override { return iterHelper(fn); }
bool iterate(const IterateFn<Gauge>& fn) const override { return iterHelper(fn); }
bool iterate(const IterateFn<Histogram>& fn) const override { return iterHelper(fn); }
bool iterate(const IterateFn<TextReadout>& fn) const override { return iterHelper(fn); }

// Stats::Store
std::vector<CounterSharedPtr> counters() const override;
std::vector<GaugeSharedPtr> gauges() const override;
Expand Down Expand Up @@ -349,6 +354,28 @@ class ThreadLocalStoreImpl : Logger::Loggable<Logger::Id::stats>, public StoreRo

NullGaugeImpl& nullGauge(const std::string&) override { return parent_.null_gauge_; }

template <class StatMap, class StatFn> bool iterHelper(StatFn fn, const StatMap& map) const {
for (auto& iter : map) {
if (!fn(iter.second)) {
return false;
}
}
return true;
}

bool iterate(const IterateFn<Counter>& fn) const override {
return iterHelper(fn, central_cache_->counters_);
}
bool iterate(const IterateFn<Gauge>& fn) const override {
return iterHelper(fn, central_cache_->gauges_);
}
bool iterate(const IterateFn<Histogram>& fn) const override {
return iterHelper(fn, central_cache_->histograms_);
}
bool iterate(const IterateFn<TextReadout>& fn) const override {
return iterHelper(fn, central_cache_->text_readouts_);
}

// NOTE: The find methods assume that `name` is fully-qualified.
// Implementations will not add the scope prefix.
CounterOptConstRef findCounter(StatName name) const override;
Expand Down Expand Up @@ -418,6 +445,16 @@ class ThreadLocalStoreImpl : Logger::Loggable<Logger::Id::stats>, public StoreRo
absl::flat_hash_map<uint64_t, TlsCacheEntry> scope_cache_;
};

template <class StatFn> bool iterHelper(StatFn fn) const {
Thread::LockGuard lock(lock_);
for (ScopeImpl* scope : scopes_) {
if (!scope->iterate(fn)) {
return false;
}
}
return true;
}

std::string getTagsForName(const std::string& name, TagVector& tags) const;
void clearScopeFromCaches(uint64_t scope_id, CentralCacheEntrySharedPtr central_cache);
void releaseScopeCrossThread(ScopeImpl* scope);
Expand Down
51 changes: 51 additions & 0 deletions source/common/stats/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "envoy/stats/scope.h"
#include "envoy/stats/stats.h"

#include "common/common/thread.h"
#include "common/stats/symbol_table_impl.h"

#include "absl/container/inlined_vector.h"
Expand Down Expand Up @@ -206,5 +207,55 @@ class Utility {
StatNameTagVectorOptConstRef tags = absl::nullopt);
};

/**
* Holds a reference to a stat by name. Note that the stat may not be created
* yet at the time CachedReference is created. Calling get() then does a lazy
* lookup, potentially returning absl::nullopt if the stat doesn't exist yet.
* StatReference works whether the name was constructed symbolically, or with
* StatNameDynamicStorage.
*
* Lookups are very slow, taking time proportional to the size of the scope,
* holding mutexes during the lookup. However once the lookup succeeds, the
* result is cached atomically, and further calls to get() are thus fast and
* mutex-free. The implementation may be faster for stats that are named
* symbolically.
*
* CachedReference is valid for the lifetime of the Scope. When the Scope
* becomes invalid, CachedReferences must also be dropped as they will hold
* pointers into the scope.
*/
template <class StatType> class CachedReference {
public:
CachedReference(Scope& scope, absl::string_view name) : scope_(scope), name_(std::string(name)) {}

/**
* Finds the named stat, if it exists, returning it as an optional.
*/
absl::optional<std::reference_wrapper<StatType>> get() {
StatType* stat = stat_.get([this]() -> StatType* {
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.

Seems like find() should really only be called once. Can we assert somehow that stat_ has never been set?

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 intended semantics here are that find() does caching itself so it's fine to call it lots of times.

I'm not clear on what you are suggesting as an alternative?

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 just meant that after you call find() once, the result is cached. Sorry I think I didn't give a full comment. Should there be some different between initialize() and find() in that initialize() is called once and find() should be called after initialize()? I don't feel strongly about it. In that case you could s/find/get.

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.

changed to get()

StatType* stat = nullptr;
IterateFn<StatType> check_stat = [this,
&stat](const RefcountPtr<StatType>& shared_stat) -> bool {
if (shared_stat->name() == name_) {
stat = shared_stat.get();
return false; // Stop iteration.
}
return true;
};
scope_.iterate(check_stat);
return stat;
});
if (stat == nullptr) {
return absl::nullopt;
}
return *stat;
}

private:
Scope& scope_;
const std::string name_;
Thread::AtomicPtr<StatType, Thread::AtomicPtrAllocMode::DoNotDelete> stat_;
};

} // namespace Stats
} // namespace Envoy
1 change: 1 addition & 0 deletions test/common/stats/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ envoy_cc_test(
srcs = ["utility_test.cc"],
deps = [
"//source/common/stats:isolated_store_lib",
"//source/common/stats:thread_local_store_lib",
"//source/common/stats:utility_lib",
],
)
Loading