Skip to content
15 changes: 15 additions & 0 deletions include/envoy/stats/scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ class Scope {
*/
virtual const SymbolTable& constSymbolTable() const PURE;
virtual SymbolTable& symbolTable() PURE;

/**
* Performs fast conversion of string to StatName in the hot-path, at the
* expense of significant per-name-per-thread memory overhead. This incurs
* a lock only the first time this string is referenced from a thread. It
* should only be used names that cannot be determined during startup or xDS
* update. Forthose names that can be determined at that time, the StatNames
* should be collected via StatNamePool or StatNameManagedStorage once, and
* then used later on in the hot-path to compose fully elaborated StatName
* objects via SymbolTable::join(), which is lock-free.
*
* @param name The name of the stat or fragment of stat.
* @return the stat name allocated from symbolTable().
*/
virtual StatName fastMemoryIntensiveStatNameLookup(absl::string_view name) PURE;
};

} // namespace Stats
Expand Down
19 changes: 19 additions & 0 deletions include/envoy/stats/symbol_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class SymbolTable {
friend struct HeapStatData;
friend class StatNameStorage;
friend class StatNameList;
friend class StringStatNameMap;

// The following methods are private, but are called by friend classes
// StatNameStorage and StatNameList, which must be friendly with SymbolTable
Expand Down Expand Up @@ -182,6 +183,24 @@ class SymbolTable {
*
*/
virtual StoragePtr encode(absl::string_view name) PURE;

/**
* Returns a vector of absl:string_view containing the symbols in the
* StatName. The string_view elements reference memory that is held in the
* SymbolTable as long as a reference to the symbols is retained. This takes
* a lock on the symbol table.
*/
virtual std::vector<absl::string_view>
statNameToStringVector(const StatName& stat_name) const PURE;

/**
* Splits a string_view into multiple symbols. This does not require a lock on
* the symbol table.
*
* @param str the string to split, based on the semantics of the SymbolTable.
* @return the string, split into tokens.
*/
virtual std::vector<absl::string_view> splitString(absl::string_view str) const PURE;
};

using SharedSymbolTable = std::shared_ptr<SymbolTable>;
Expand Down
10 changes: 5 additions & 5 deletions source/common/common/hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ struct ConstCharStarHash {
};

struct ConstCharStarEqual {
size_t operator()(const char* a, const char* b) const { return strcmp(a, b) == 0; }
bool operator()(const char* a, const char* b) const { return strcmp(a, b) == 0; }
};

template <class Value>
Expand Down Expand Up @@ -111,10 +111,10 @@ struct HeterogeneousStringEqual {
// See description for HeterogeneousStringHash::is_transparent.
using is_transparent = void;

size_t operator()(absl::string_view a, absl::string_view b) const { return a == b; }
size_t operator()(const SharedString& a, const SharedString& b) const { return *a == *b; }
size_t operator()(absl::string_view a, const SharedString& b) const { return a == *b; }
size_t operator()(const SharedString& a, absl::string_view b) const { return *a == b; }
bool operator()(absl::string_view a, absl::string_view b) const { return a == b; }
bool operator()(const SharedString& a, const SharedString& b) const { return *a == *b; }
bool operator()(absl::string_view a, const SharedString& b) const { return a == *b; }
bool operator()(const SharedString& a, absl::string_view b) const { return *a == b; }
};

// We use heterogeneous hash/equal functors to do a find() without constructing
Expand Down
5 changes: 5 additions & 0 deletions source/common/stats/fake_symbol_table_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ class FakeSymbolTableImpl : public SymbolTable {
fn(toStringView(stat_name));
}

std::vector<absl::string_view> statNameToStringVector(const StatName& stat_name) const override {
return {toStringView(stat_name)};
}
std::vector<absl::string_view> splitString(absl::string_view s) const override { return {s}; }

private:
absl::string_view toStringView(const StatName& stat_name) const {
return {reinterpret_cast<const char*>(stat_name.data()),
Expand Down
13 changes: 13 additions & 0 deletions source/common/stats/isolated_store_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,22 @@ IsolatedStoreImpl::IsolatedStoreImpl(SymbolTable& symbol_table)
}),
null_gauge_(symbol_table) {}

IsolatedStoreImpl::~IsolatedStoreImpl() { stat_name_set_.free(symbolTable()); }

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

StatName IsolatedStoreImpl::fastMemoryIntensiveStatNameLookup(absl::string_view name) {
absl::optional<StatName> stat_name = string_stat_name_map_.find(name, symbolTable());
if (!stat_name) {
StatNameStorage storage(name, symbolTable());
auto insertion = stat_name_set_.insert(std::move(storage));
ASSERT(insertion.second); // If the name is not in the map, it should not be in the set.
stat_name = insertion.first->statName();
}
return *stat_name;
}

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

// Stats::Scope
Counter& counterFromStatName(StatName name) override { return counters_.get(name); }
Expand Down Expand Up @@ -110,6 +111,8 @@ class IsolatedStoreImpl : public StoreImpl {
return histogramFromStatName(storage.statName());
}

StatName fastMemoryIntensiveStatNameLookup(absl::string_view name) override;

private:
IsolatedStoreImpl(std::unique_ptr<SymbolTable>&& symbol_table);

Expand All @@ -119,6 +122,8 @@ class IsolatedStoreImpl : public StoreImpl {
IsolatedStatsCache<Gauge> gauges_;
IsolatedStatsCache<Histogram> histograms_;
NullGaugeImpl null_gauge_;
StatNameStorageSet stat_name_set_;
StringStatNameMap string_stat_name_map_;
};

} // namespace Stats
Expand Down
12 changes: 10 additions & 2 deletions source/common/stats/scope_prefixer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
#include "common/stats/symbol_table_impl.h"
#include "common/stats/utility.h"

#include "absl/strings/str_cat.h"

namespace Envoy {
namespace Stats {

ScopePrefixer::ScopePrefixer(absl::string_view prefix, Scope& scope)
: scope_(scope), prefix_(Utility::sanitizeStatsName(prefix), symbolTable()) {}
: scope_(scope), prefix_(Utility::sanitizeStatsName(prefix), symbolTable()),
prefix_string_(std::string(prefix)) {}

ScopePrefixer::ScopePrefixer(StatName prefix, Scope& scope)
: scope_(scope), prefix_(prefix, symbolTable()) {}
: scope_(scope), prefix_(prefix, symbolTable()),
prefix_string_(symbolTable().toString(prefix) + ".") {}

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

Expand Down Expand Up @@ -62,5 +66,9 @@ void ScopePrefixer::deliverHistogramToSinks(const Histogram& histograms, uint64_
scope_.deliverHistogramToSinks(histograms, val);
}

StatName ScopePrefixer::fastMemoryIntensiveStatNameLookup(absl::string_view name) {
return scope_.fastMemoryIntensiveStatNameLookup(absl::StrCat(prefix_string_, name));
}

} // namespace Stats
} // namespace Envoy
3 changes: 3 additions & 0 deletions source/common/stats/scope_prefixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ class ScopePrefixer : public Scope {

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

StatName fastMemoryIntensiveStatNameLookup(absl::string_view name) override;

private:
Scope& scope_;
StatNameStorage prefix_;
std::string prefix_string_;
};

} // namespace Stats
Expand Down
44 changes: 43 additions & 1 deletion source/common/stats/symbol_table_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ void SymbolTableImpl::callWithStringView(StatName stat_name,
}

std::string SymbolTableImpl::decodeSymbolVec(const SymbolVec& symbols) const {
return absl::StrJoin(decodeSymbolVecTokens(symbols), ".");
}

std::vector<absl::string_view>
SymbolTableImpl::statNameToStringVector(const StatName& stat_name) const {
return decodeSymbolVecTokens(Encoding::decodeSymbols(stat_name.data(), stat_name.dataSize()));
}

std::vector<absl::string_view>
SymbolTableImpl::decodeSymbolVecTokens(const SymbolVec& symbols) const {
std::vector<absl::string_view> name_tokens;
name_tokens.reserve(symbols.size());
{
Expand All @@ -162,7 +172,7 @@ std::string SymbolTableImpl::decodeSymbolVec(const SymbolVec& symbols) const {
name_tokens.push_back(fromSymbol(symbol));
}
}
return absl::StrJoin(name_tokens, ".");
return name_tokens;
}

void SymbolTableImpl::incRefCount(const StatName& stat_name) {
Expand Down Expand Up @@ -429,5 +439,37 @@ void StatNameList::clear(SymbolTable& symbol_table) {
storage_.reset();
}

size_t StringViewVectorHash::operator()(const std::vector<absl::string_view>& a) const {
uint64_t hash = 0;
for (absl::string_view sv : a) {
hash = 63 * hash + HashUtil::xxHash64(sv);
}
return hash;
}

absl::optional<StatName> StringStatNameMap::find(absl::string_view name,
const SymbolTable& symbol_table) const {
absl::optional<StatName> ret;

// TODO(jmarantz)(: change the hash/compare mechanism to avoid needing to
// allocate memory during the find() operation. We should be able to
// iterate over the "."-separated tokens to compute the hash-value without
// having to have the allocated split vector first. Similar for compare.
//
// The only nuance -- and the reason to leave this as is for now, is that
// when using FakeSymbolTable, there is no actual splitting by token.
std::vector<absl::string_view> v = symbol_table.splitString(name);
auto iter = map_.find(v);
if (iter != map_.end()) {
ret = iter->second;
}
return ret;
}

void StringStatNameMap::insert(StatName stat_name, const SymbolTable& symbol_table) {
std::vector<absl::string_view> v = symbol_table.statNameToStringVector(stat_name);
map_[v] = stat_name;
}

} // namespace Stats
} // namespace Envoy
53 changes: 44 additions & 9 deletions source/common/stats/symbol_table_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ class SymbolTableImpl : public SymbolTable {
void callWithStringView(StatName stat_name,
const std::function<void(absl::string_view)>& fn) const override;

std::vector<absl::string_view> statNameToStringVector(const StatName& stat_name) const override;
std::vector<absl::string_view> splitString(absl::string_view s) const override {
return absl::StrSplit(s, '.');
}

#ifndef ENVOY_CONFIG_COVERAGE
void debugPrint() const override;
#endif
Expand Down Expand Up @@ -183,6 +188,7 @@ class SymbolTableImpl : public SymbolTable {
* @return std::string the retrieved stat name.
*/
std::string decodeSymbolVec(const SymbolVec& symbols) const;
std::vector<absl::string_view> decodeSymbolVecTokens(const SymbolVec& symbols) const;

/**
* Convenience function for encode(), symbolizing one string segment at a time.
Expand Down Expand Up @@ -528,17 +534,11 @@ struct StatNameHash {
size_t operator()(const StatName& a) const { return a.hash(); }
};

// Helper class for constructing hash-tables with StatName keys.
struct StatNameCompare {
bool operator()(const StatName& a, const StatName& b) const { return a == b; }
};

// Value-templatized hash-map with StatName key.
template <class T>
using StatNameHashMap = absl::flat_hash_map<StatName, T, StatNameHash, StatNameCompare>;
template <class T> using StatNameHashMap = absl::flat_hash_map<StatName, T, StatNameHash>;

// Hash-set of StatNames
using StatNameHashSet = absl::flat_hash_set<StatName, StatNameHash, StatNameCompare>;
using StatNameHashSet = absl::flat_hash_set<StatName, StatNameHash>;

// Helper class for sorting StatNames.
struct StatNameLessThan {
Expand All @@ -562,13 +562,13 @@ struct HeterogeneousStatNameHash {

size_t operator()(StatName a) const { return a.hash(); }
size_t operator()(const StatNameStorage& a) const { return a.statName().hash(); }
size_t operator()(absl::string_view str) const;
};

struct HeterogeneousStatNameEqual {
// See description for HeterogeneousStatNameHash::is_transparent.
using is_transparent = void;

size_t operator()(StatName a, StatName b) const { return a == b; }
size_t operator()(const StatNameStorage& a, const StatNameStorage& b) const {
return a.statName() == b.statName();
}
Expand Down Expand Up @@ -630,5 +630,40 @@ class StatNameStorageSet {
HashSet hash_set_;
};

struct StringViewVectorHash {
size_t operator()(const std::vector<absl::string_view>& a) const;
};

// Captures a map from string_view to StatName. The backing store for both of
// these must be separately managed, and live beyond the map.
//
// This is intended for use as a TLS cache in thread_local_store.cc, with the
// backing store held in sets in the CentralCacheEntry.
class StringStatNameMap {
public:
/**
* This does not take a lock in the symbol table.
* @param name the string stat-name to find.
* @param symbol_table the symbol-table used to help split the string into tokens.
* @return the StatName, if found.
*/
absl::optional<StatName> find(absl::string_view name, const SymbolTable& symbol_table) const;

/**
* Inserts stat_name into the StatNameStorageMap. Caller must guarantee that
* the backing-store for StatName lasts longer than the StatNameStorageMap.
*
* Note: this takes a lock in the symbol table, in order to do
* symbol-string-lookups in its lock-protected symbol map.
*
* @param stat_name the name to insert.
* @param symbol_table the symbol-table used to find stable strings for the tokens in stat names.
*/
void insert(StatName stat_name, const SymbolTable& symbol_table);

private:
absl::flat_hash_map<std::vector<absl::string_view>, StatName, StringViewVectorHash> map_;
};

} // namespace Stats
} // namespace Envoy
Loading