-
Notifications
You must be signed in to change notification settings - Fork 5.5k
stats: add symbol table for future stat name encoding #3927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
8de2463
1c45948
a3df93b
a78ea8d
59e8129
475dcd3
9b3594b
e7b619a
b3f1b7f
bdc714c
0ded3e5
b8b3f60
ae650c3
14e8bb3
86dc207
cb6c754
ff30317
6e42c86
f848464
3687311
2772592
edb35e3
c872073
2f83e68
85c5d8b
7f93df9
190da3d
f2da353
9e68dfd
8e794d5
bd47676
19c6e65
da2b7d7
9466aa3
86dab2c
208512d
27aa402
aaba51b
278f92f
ce87e7e
efd3c6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| #pragma once | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "envoy/common/pure.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Stats { | ||
|
|
||
| using Symbol = uint32_t; | ||
| using SymbolVec = std::vector<Symbol>; | ||
|
|
||
| /** | ||
| * Interface for storing a stat name, which is a wrapper around a SymbolVec. | ||
| */ | ||
| class StatName { | ||
| public: | ||
| StatName() {} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Default constructor not needed. |
||
| virtual ~StatName(){}; | ||
| virtual std::string toString() const PURE; | ||
| virtual SymbolVec toSymbols() const PURE; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we don't need to -- and should not -- expose this API or the Symbol type in the interface. What I think you can do is exploit a C++ feature called something covariant returns (https://stackoverflow.com/questions/1260757/when-is-c-covariance-the-best-solution) So SymbolTableImpl::encode() can return a StatNameImpl*, even though SymbolTable::encode() returns a StatName*. I'm a little hazy on whether this works with std::unique_ptr though, so that needs to be experimented. But the higher level point is that users of this class should never be working with SymbolVec, and shouldn't see those in the API.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we just forward declare
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still want a toSymbols() equivalent fn for testing, see b8b3f60. |
||
| }; | ||
|
|
||
| using StatNamePtr = std::unique_ptr<StatName>; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so -- StatNames should be totally unique, AFAIK.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I had thought of this too and almost wrote the same comment. I don't think this needs to be shared, but it's possible, based on usage there might emerge a need. |
||
|
|
||
| /** | ||
| * Interface for shortening and retrieving stat names. | ||
| * Guarantees that x = encode(x).toString() for any x. | ||
| * | ||
| * Even though the symbol table does manual reference counting, curr_counter_ is monotonically | ||
| * increasing. So encoding "foo", freeing the sole stat containing "foo", and then re-encoding "foo" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a long running server may never have more than 4G symbols, but I'm not sure we wouldn't wrap over the course of a long run with lots of config changes. Would it be hard to keep a free-list and recycle? I also feel like it it makes things slightly more efficient then to use a vector rather than a map for the int-to-string mapping, but I don't really care that much.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you have 100 clusters updated every 1s, this is 497 days. We probably have less frequent cluster updates but some deployments will be larger. I don't mind the idea of a free list to avoid this. I'm not fully sold on efficiency here of vector vs. map, it's a time-space tradeoff. |
||
| * will produce a different symbol each time. | ||
| */ | ||
| class SymbolTable { | ||
| public: | ||
| virtual ~SymbolTable() {} | ||
|
|
||
| /** | ||
| * Encodes a stat name into a StatNamePtr. Expects the name to be period-delimited. | ||
| * | ||
| * @param name the stat name to encode. | ||
| * @return StatNamePtr a unique_ptr to the StatName class encapsulating the symbol vector. | ||
| */ | ||
| virtual StatNamePtr encode(const std::string& name) PURE; | ||
| }; | ||
|
|
||
| } // namespace Stats | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| #include "common/stats/symbol_table_impl.h" | ||
|
|
||
| #include <memory> | ||
| #include <unordered_map> | ||
| #include <vector> | ||
|
|
||
| #include "common/common/assert.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Stats { | ||
|
|
||
| // TODO(ambuc): There is a possible performance optimization here for avoiding the encoding of IPs, | ||
| // if they appear in stat names. We don't want to waste time symbolizing an integer as an integer, | ||
| // if we can help it. | ||
| StatNamePtr SymbolTableImpl::encode(const std::string& name) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think encode() should take an absl::string_view as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, implemented in c872073. |
||
| SymbolVec symbol_vec; | ||
| std::vector<std::string> name_vec = absl::StrSplit(name, '.'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absl::string_view |
||
| symbol_vec.reserve(name_vec.size()); | ||
| std::transform(name_vec.begin(), name_vec.end(), std::back_inserter(symbol_vec), | ||
| [this](std::string x) { return toSymbol(x); }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absl::string_view |
||
|
|
||
| return std::make_unique<StatNameImpl>(symbol_vec, this); | ||
| } | ||
|
|
||
| std::string SymbolTableImpl::decode(const SymbolVec& symbol_vec) const { | ||
| std::vector<std::string> name; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absl::string_view |
||
| name.reserve(symbol_vec.size()); | ||
| std::transform(symbol_vec.begin(), symbol_vec.end(), std::back_inserter(name), | ||
| [this](Symbol x) { return fromSymbol(x); }); | ||
| return absl::StrJoin(name, "."); | ||
| } | ||
|
|
||
| void SymbolTableImpl::free(const SymbolVec& symbol_vec) { | ||
| for (const Symbol symbol : symbol_vec) { | ||
| auto decode_search = decode_map_.find(symbol); | ||
| RELEASE_ASSERT(decode_search != decode_map_.end(), ""); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These should not be release asserts. If these are invariants/preconditions, which I believe they are, the Envoy style is to use ASSERT and documentation. RELEASE_ASSERTs are reserved for places where we are eliding error handling, see https://github.com/envoyproxy/envoy/blob/master/STYLE.md#error-handling for full discussion.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, changed in 85c5d8b. |
||
|
|
||
| std::string str = decode_search->second; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. const std::string& |
||
| auto encode_search = encode_map_.find(str); | ||
| RELEASE_ASSERT(encode_search != encode_map_.end(), ""); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
|
|
||
| ((encode_search->second).second)--; | ||
| if ((encode_search->second).second == 0) { | ||
| decode_map_.erase(decode_search); | ||
| encode_map_.erase(encode_search); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Symbol SymbolTableImpl::toSymbol(const std::string& str) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absl::string_view all the way down :) |
||
| Symbol result; | ||
| auto encode_insert = encode_map_.insert({str, std::make_pair(curr_counter_, 1)}); | ||
| // If the insertion took place, we mirror the insertion in the decode_map. | ||
| if (encode_insert.second) { | ||
| auto decode_insert = decode_map_.insert({curr_counter_, str}); | ||
| // We expect the decode_map to be in lockstep. | ||
| RELEASE_ASSERT(decode_insert.second, ""); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above points on RELEASE_ASSERT. |
||
| result = curr_counter_; | ||
| ++curr_counter_; | ||
| } else { | ||
| // If the insertion didn't take place, return the actual value at that location | ||
| result = (encode_insert.first)->second.first; | ||
| // and up the refcount at that location | ||
| ++(encode_insert.first)->second.second; | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| std::string SymbolTableImpl::fromSymbol(const Symbol symbol) const { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absl::string_view
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See 3687311. |
||
| auto search = decode_map_.find(symbol); | ||
| RELEASE_ASSERT(search != decode_map_.end(), ""); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. |
||
| return search->second; | ||
| } | ||
|
|
||
| } // namespace Stats | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| #pragma once | ||
|
|
||
| #include <algorithm> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <unordered_map> | ||
| #include <vector> | ||
|
|
||
| #include "envoy/common/exception.h" | ||
| #include "envoy/stats/symbol_table.h" | ||
|
|
||
| #include "common/common/assert.h" | ||
|
|
||
| #include "absl/strings/str_join.h" | ||
| #include "absl/strings/str_split.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Stats { | ||
|
|
||
| class SymbolTableImpl : public SymbolTable { | ||
| friend class StatNameImpl; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would put this at the top of |
||
|
|
||
| public: | ||
| StatNamePtr encode(const std::string& name) override; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you try making this return I think the dynamic_casts you have are OK if you need to have them, but let's see if we can avoid it if it's easy.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that covariant returns won't work here because of a weird cross-dependency between SymbolTableImpl and StatNameImpl. StatNameImpl expects a SymbolTableImpl& to store internally, so if we declare a covariant return it's unclear which of the two impls to write first, since they both would depend on each other. I'm comfortable with the |
||
|
|
||
| // For testing purposes only. | ||
| size_t size() const { return sizeof(SymbolTableImpl) + encode_map_.size() + decode_map_.size(); } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. size() seems like a useful and idiomatic API to include, but I would expect it to be the number of symbols. It's not entirely clear to me what this particular impl is returning though; you should doc that. the first sizeof returns a number of bytes, and the map size() calls return cardinalities which need to be multiplied by something to become bytes. |
||
|
|
||
| private: | ||
| /** | ||
| * Decodes a vector of symbols back into its period-delimited stat name. | ||
| * If decoding fails on any part of the symbol_vec, we release_assert and crash hard, since this | ||
| * should never happen, and we don't want to continue running with a corrupt stats set. | ||
| * | ||
| * @param symbol_vec the vector of symbols to decode. | ||
| * @return std::string the retrieved stat name. | ||
| */ | ||
| std::string decode(const SymbolVec& symbol_vec) const; | ||
|
|
||
| /** | ||
| * Since SymbolTableImpl does manual reference counting, a client of SymbolTable (such as | ||
| * StatName) must manually call free(symbol_vec) when it is freeing the stat it represents. This | ||
| * way, the symbol table will grow and shrink dynamically, instead of being write-only. | ||
| * | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
| * @return bool whether or not the total free operation was successful. Expected to be true. | ||
| */ | ||
| void free(const SymbolVec& symbol_vec); | ||
|
|
||
| Symbol curr_counter_ = 0; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
|
|
||
| // Bimap implementation. | ||
| // The encode map stores both the symbol and the ref count of that symbol. | ||
| std::unordered_map<std::string, std::pair<Symbol, uint32_t>> encode_map_; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW I was messing around in some of my own experiments, and want to propose an alternative to using pair here, to make call sites more readable. Declare a struct: Dereferences this are more readable because they are symbolic. etc. https://en.cppreference.com/w/c/language/struct I think this is relatively new syntax, as in multiple decades of C/C++ I never saw this before. But it's pretty nifty IMO.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You were right, I missed this comment. Implementing this in edb35e3. |
||
| std::unordered_map<Symbol, std::string> decode_map_; | ||
|
|
||
| Symbol toSymbol(const std::string& str); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: method decls before fields. |
||
| std::string fromSymbol(const Symbol symbol) const; | ||
| }; | ||
|
|
||
| /** | ||
| * Implements RAII for Symbols, since the StatName destructor does the work of freeing its component | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
||
| * symbols. | ||
| */ | ||
| class StatNameImpl : public StatName { | ||
| public: | ||
| StatNameImpl(SymbolVec symbol_vec, SymbolTableImpl* symbol_table) | ||
| : symbol_vec_(symbol_vec), symbol_table_(symbol_table) {} | ||
| ~StatNameImpl() override { symbol_table_->free(symbol_vec_); } | ||
| std::string toString() const override { return symbol_table_->decode(symbol_vec_); } | ||
| SymbolVec toSymbols() const override { return symbol_vec_; } | ||
|
|
||
| private: | ||
| SymbolVec symbol_vec_; | ||
| SymbolTableImpl* symbol_table_; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Envoy convention here is to use a non-const ref. |
||
| }; | ||
|
|
||
| } // namespace Stats | ||
| } // namespace Envoy | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can move both of these to the _impl.h