-
Notifications
You must be signed in to change notification settings - Fork 5.5k
HeapStatData with a distinct allocation mechanism for RawStatData #3710
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 15 commits
a7f5585
d5e88bf
be3cfee
dc1f5a5
27f1d9e
ee8cf0b
29741c2
3513338
7204822
6c09c7c
d427401
e21dd1d
343eae5
d809c61
5d3e916
c79f73d
5c6c925
e9d78cf
77eecc5
8ba7c45
85a91de
83a3797
42aed4a
f48bf77
ccb29e1
9a757c2
7f84547
7d15bf8
5f82615
8dbaff2
700d540
319462b
c0f9499
1b89d7f
08fe789
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 |
|---|---|---|
|
|
@@ -135,35 +135,47 @@ bool TagExtractorImpl::extractTag(const std::string& stat_name, std::vector<Tag> | |
| return false; | ||
| } | ||
|
|
||
| RawStatData* HeapRawStatDataAllocator::alloc(const std::string& name) { | ||
| uint64_t num_bytes_to_allocate = RawStatData::structSize(name.size()); | ||
| RawStatData* data = static_cast<RawStatData*>(::calloc(num_bytes_to_allocate, 1)); | ||
| if (data == nullptr) { | ||
| throw std::bad_alloc(); | ||
| } | ||
| data->checkAndInit(name, num_bytes_to_allocate); | ||
| HeapStatData::HeapStatData(absl::string_view key) : name_(key.data(), key.size()) {} | ||
|
|
||
| HeapStatData* HeapStatDataAllocator::alloc(const std::string& name) { | ||
|
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 mentioned this earlier, but the comment got buried under some other changes. I'm relatively confident that adding truncation in HeapStatDataAllocator is a user-facing change. Previous to this change we did not truncate stats in non hot restart mode. This behavior change should be documented in the release notes. I'm also not sure it makes sense to change this behavior if we ultimately do not want non hot restart stats to be truncated as is suggested in #3965 (since I believe that this is the existing behavior). I could be wrong about the behavior changing, but if so, please point out where they are currently being truncated on master.
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. Reading again I think you are right; it's pretty subtle though. In current master, we truncate when making a RawStatData in RawStatData::truncateAndInit(), but asymmetrically, we truncate at the call-site in ThreadLocalStoreImpl::safeMakeStat() only when calling the fallback. OK let me fix that; it's a little cleaner to just do this right. |
||
| auto data = std::make_unique<HeapStatData>(truncateStatName(name)); | ||
|
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 didn't see this behavior change documented in the PR description - are we now going back to truncating in non hot restart mode? I think we should probably think carefully about what we want the user-facing behavior to look like long-term before changing the behavior again to avoid churn for something that is at least nominally user-facing. As @mattklein123 has noted in the past, this behavior has become quite complex, so I think it's worth some thought to simplify the user-facing surface and document the behavior. Google internally and (I assume) most other users of Envoy in non hot restart mode have no reason to want stats to be truncated. I think it's totally okay to allow truncation in that mode, but if we do, I think it would be better to allow it to be configurable via the command line. We could try to incorporate this optionality into the existing command line parameter somehow (for instance, allow users to pass in 0 for no truncation). WDYT? Side note: I'm less concerned about the isolated stats store as those stats are not exported to or queryable by the user unless made available through other means. We could add a way to pass in stats options from whatever code is creating it, but I see no reason why it needs to conform to the parameters used for the primary stats store as it's meant to be a distinct entity.
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 this is actually not a behavior change; we are truncating prior to this PR in thread_local_store, but not IsolatedStore. It follows the configurable options.
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. Can we file an issue for @mrice32's request?
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.
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'm not seeing where we're doing the truncation for the non-hot restart case. Can you please point me to it? Here we seem to be passing through the full name, and I can't seem to find anywhere that we actually perform truncation (unless we're falling back in the hot restart case) in thread_local_store.
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. @mrice32 : HeapStatDataAllocator::alloc() truncates in the first line of its impl:
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.
Sorry, I didn't communicate that clearly. I was referring to the code before your change. I think we were not truncating before, and you changed the behavior such that we are. I'm looking for where we were truncating in the non hot restart case before your change, and I can't find it. The link in my previous comment is pointing to master. |
||
| Thread::ReleasableLockGuard lock(mutex_); | ||
| auto ret = stats_.insert(data); | ||
| RawStatData* existing_data = *ret.first; | ||
| auto ret = stats_.insert(data.get()); | ||
| HeapStatData* existing_data = *ret.first; | ||
| lock.release(); | ||
|
|
||
| if (!ret.second) { | ||
| ::free(data); | ||
| ++existing_data->ref_count_; | ||
| return existing_data; | ||
| } else { | ||
| return data; | ||
| if (ret.second) { | ||
| return data.release(); | ||
| } | ||
| ++existing_data->ref_count_; | ||
| return existing_data; | ||
| } | ||
|
|
||
| void HeapStatDataAllocator::free(HeapStatData& data) { | ||
|
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. Generally it makes it easier to review if we separate out code moves like this from substantive changes.
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...I moved HeapStatDataAllocator::free to the same relative spot where HeapRawStatDataAllocator::free used to be; though that was in a weird spot. I can move it again later. Left a TODO. |
||
| ASSERT(data.ref_count_ > 0); | ||
| if (--data.ref_count_ > 0) { | ||
| return; | ||
| } | ||
|
|
||
| { | ||
| Thread::LockGuard lock(mutex_); | ||
|
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. Can you remind me here (and/or in a code comment) why
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. ref_count_ is an atomic scalar, and . stats_ is a map annotated as GUARDED_BY(mutex_) |
||
| size_t key_removed = stats_.erase(&data); | ||
| ASSERT(key_removed == 1); | ||
| } | ||
|
|
||
| delete &data; | ||
| } | ||
|
|
||
| /** | ||
| * Counter implementation that wraps a RawStatData. | ||
| * Counter implementation that wraps a StatData. StatData must have data members: | ||
| * int64_t value_; | ||
| * int64_t pending_increment_; | ||
|
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. Aren't these all atomics?
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. Good point -- done. |
||
| * int16_t flags_; | ||
| */ | ||
| class CounterImpl : public Counter, public MetricImpl { | ||
| template <class StatData> class CounterImpl : public Counter, public MetricImpl { | ||
|
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'm not a fan of templated classes unless they are absolutely necessary because they have a way of being contagious. There's likely a good reason that this is the way it was done, but I can't quite see what the design path was to get here. Could you explain? What's preventing us from making a base
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 the only reasonable alternative for sharing this management code between HeapStatData and RawStatData, from a coding perspective, is making StatData a class with virtual methods. That seems suboptimal from (a) a performance perspective, adding an indirect function-call layer to a hot path and (b) size, adding the a vptr to each stat (though that's tiny compared to the string :) The only other performant options I can think of are copying the body (not maintainable) or macros (not maintainable or debuggable).
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. Why do we need to use templates here vs. polymorphism? Would prefer the latter if possible for the usual template reasons (horrible compiler errors being my pet peeve).
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'm not following your suggestion. AFAIK polymorphism is not an alternative to templates, for code-sharing the class bodies. The most direct alternative is macros, and a functional (but less performance and memory-efficient) substitute in this case would be virtual inheritance from a base class. I think templates are the correct solution here.
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. Polymorphism == virtual inheritance for the purpose of my comment :) OK, I'm willing to buy templates for performance reasons since stats increment etc. is actually on the hot path. I think you should add justification at the template declaration sites explicitly calling this out, since we default to "avoid templates unless absolutely necessary" in Envoy, an maybe someone comes along and undoes this if you don't ;)
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. done
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. Just to toss my hat in here - as I mentioned above, I agree with Harvey that the Envoy default is to avoid templates as much as possible. I agree that this is the most performant solution for sharing code that I can think of. However, it seems like the majority of the Envoy codebase (even portions in the hot path) use virtual function calls everywhere. This feels a little bit like premature optimization. We already do one virtual function call into Counter and Gauge per change since all the functions on those classes are virtual. Do we have an idea how much an extra virtual call here will affect real-world performance? I'm not super opinionated on this, and if no one else has issues with using templates this extensively, we can leave it as is, but it seems like this does go against general Envoy convention.
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. It's about 5 instructions for a virtual dispatch. I think it's very likely premature optimization but I didn't look into the change in very deep detail.
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. Ah -- you are right. this all goes through CounterImpl::inc() which is a virtual override, so it isn't hyper-optimized anyway, but two virtual function calls are still more expensive than one. I still feel like templates are a natural expression of this code though; I've been doing templates for so long that I don't have any kind of qualms about them. But actually -- I wrote this code like 2 months ago and it's coming back to me now -- I think the real reason I wanted to stay away from virtual inheritance her is that the vptr may not work well in shared memory since it's a pointer. I will add that to the comment. Also, I did a quick recursive grep: and found there are 93 template class in the Envoy codebase, including the ones I've added here. So it may not be the most common approach but it's certainly not rare.
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. Added comments in code:
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 think the way you would implement this with virtual inheritance is by wrapping a reference to the struct that is stored in shared memory in a virtual class that would be local to a single instance of Envoy. This is similar to how CounterImpl, etc works today - just more nested. Anyway, I'll yield to @mattklein123 and @htuch on this since they are much more aware of what the overall Envoy conventions and perf tradeoffs are.
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 the code is cleaner w/ templates it's fine. I think the high level guidance is to think about whether templates make the code more readable or not. In many cases the answer is not but this seems fine if you think it's better. |
||
| public: | ||
| CounterImpl(RawStatData& data, RawStatDataAllocator& alloc, std::string&& tag_extracted_name, | ||
| std::vector<Tag>&& tags) | ||
| CounterImpl(StatData& data, StatDataAllocatorImpl<StatData>& alloc, | ||
| std::string&& tag_extracted_name, std::vector<Tag>&& tags) | ||
| : MetricImpl(data.name_, std::move(tag_extracted_name), std::move(tags)), data_(data), | ||
| alloc_(alloc) {} | ||
| ~CounterImpl() { alloc_.free(data_); } | ||
|
|
@@ -182,17 +194,17 @@ class CounterImpl : public Counter, public MetricImpl { | |
| uint64_t value() const override { return data_.value_; } | ||
|
|
||
| private: | ||
| RawStatData& data_; | ||
| RawStatDataAllocator& alloc_; | ||
| StatData& data_; | ||
| StatDataAllocatorImpl<StatData>& alloc_; | ||
| }; | ||
|
|
||
| /** | ||
| * Gauge implementation that wraps a RawStatData. | ||
| * Gauge implementation that wraps a StatData. | ||
| */ | ||
| class GaugeImpl : public Gauge, public MetricImpl { | ||
| template <class StatData> class GaugeImpl : public Gauge, public MetricImpl { | ||
| public: | ||
| GaugeImpl(RawStatData& data, RawStatDataAllocator& alloc, std::string&& tag_extracted_name, | ||
| std::vector<Tag>&& tags) | ||
| GaugeImpl(StatData& data, StatDataAllocatorImpl<StatData>& alloc, | ||
| std::string&& tag_extracted_name, std::vector<Tag>&& tags) | ||
| : MetricImpl(data.name_, std::move(tag_extracted_name), std::move(tags)), data_(data), | ||
| alloc_(alloc) {} | ||
| ~GaugeImpl() { alloc_.free(data_); } | ||
|
|
@@ -217,8 +229,8 @@ class GaugeImpl : public Gauge, public MetricImpl { | |
| bool used() const override { return data_.flags_ & Flags::Used; } | ||
|
|
||
| private: | ||
| RawStatData& data_; | ||
| RawStatDataAllocator& alloc_; | ||
| StatData& data_; | ||
| StatDataAllocatorImpl<StatData>& alloc_; | ||
| }; | ||
|
|
||
| TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v2::StatsConfig& config) { | ||
|
|
@@ -319,20 +331,17 @@ TagProducerImpl::addDefaultExtractors(const envoy::config::metrics::v2::StatsCon | |
| return names; | ||
| } | ||
|
|
||
| void HeapRawStatDataAllocator::free(RawStatData& data) { | ||
| ASSERT(data.ref_count_ > 0); | ||
| if (--data.ref_count_ > 0) { | ||
| return; | ||
| } | ||
|
|
||
| size_t key_removed; | ||
| { | ||
| Thread::LockGuard lock(mutex_); | ||
| key_removed = stats_.erase(&data); | ||
| template <class StatData> | ||
| absl::string_view StatDataAllocatorImpl<StatData>::truncateStatName(absl::string_view key) { | ||
| const uint64_t max_width = stats_options_.maxNameLength(); | ||
| if (key.size() > max_width) { | ||
| ENVOY_LOG_MISC( | ||
| warn, | ||
| "Statistic '{}' is too long with {} characters, it will be truncated to {} characters", key, | ||
| key.size(), max_width); | ||
| return absl::string_view(key.data(), max_width); | ||
| } | ||
|
|
||
| ASSERT(key_removed == 1); | ||
| ::free(&data); | ||
| return key; | ||
| } | ||
|
|
||
| void RawStatData::initialize(absl::string_view key, uint64_t xfer_size) { | ||
|
|
@@ -420,26 +429,32 @@ void SourceImpl::clearCache() { | |
| histograms_.reset(); | ||
| } | ||
|
|
||
| CounterSharedPtr RawStatDataAllocator::makeCounter(const std::string& name, | ||
| std::string&& tag_extracted_name, | ||
| std::vector<Tag>&& tags) { | ||
| RawStatData* data = alloc(name); | ||
| template <class StatData> | ||
| CounterSharedPtr StatDataAllocatorImpl<StatData>::makeCounter(const std::string& name, | ||
| std::string&& tag_extracted_name, | ||
| std::vector<Tag>&& tags) { | ||
| StatData* data = alloc(name); | ||
| if (data == nullptr) { | ||
| return nullptr; | ||
| } | ||
| return std::make_shared<CounterImpl>(*data, *this, std::move(tag_extracted_name), | ||
| std::move(tags)); | ||
| return std::make_shared<CounterImpl<StatData>>(*data, *this, std::move(tag_extracted_name), | ||
| std::move(tags)); | ||
| } | ||
|
|
||
| GaugeSharedPtr RawStatDataAllocator::makeGauge(const std::string& name, | ||
| std::string&& tag_extracted_name, | ||
| std::vector<Tag>&& tags) { | ||
| RawStatData* data = alloc(name); | ||
| template <class StatData> | ||
| GaugeSharedPtr StatDataAllocatorImpl<StatData>::makeGauge(const std::string& name, | ||
| std::string&& tag_extracted_name, | ||
| std::vector<Tag>&& tags) { | ||
| StatData* data = alloc(name); | ||
| if (data == nullptr) { | ||
| return nullptr; | ||
| } | ||
| return std::make_shared<GaugeImpl>(*data, *this, std::move(tag_extracted_name), std::move(tags)); | ||
| return std::make_shared<GaugeImpl<StatData>>(*data, *this, std::move(tag_extracted_name), | ||
| std::move(tags)); | ||
| } | ||
|
|
||
| template class StatDataAllocatorImpl<HeapStatData>; | ||
| template class StatDataAllocatorImpl<RawStatData>; | ||
|
|
||
| } // namespace Stats | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -283,12 +283,14 @@ class MetricImpl : public virtual Metric { | |
| const std::vector<Tag> tags_; | ||
| }; | ||
|
|
||
| /** | ||
| * Implements a StatDataAllocator that uses RawStatData -- capable of deploying | ||
| * in a shared memory block without internal pointers. | ||
| */ | ||
| class RawStatDataAllocator : public StatDataAllocator { | ||
| template <class StatData> class StatDataAllocatorImpl : public StatDataAllocator { | ||
| public: | ||
| /** | ||
| * @param stats_options The stat optinos, which must live longer than the allocator. | ||
|
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: s/optinos/options/
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. done |
||
| */ | ||
| explicit StatDataAllocatorImpl(const StatsOptions& stats_options) | ||
| : stats_options_(stats_options) {} | ||
|
|
||
| // StatDataAllocator | ||
| CounterSharedPtr makeCounter(const std::string& name, std::string&& tag_extracted_name, | ||
| std::vector<Tag>&& tags) override; | ||
|
|
@@ -297,21 +299,40 @@ class RawStatDataAllocator : public StatDataAllocator { | |
|
|
||
| /** | ||
| * @param name the full name of the stat. | ||
| * @return RawStatData* a raw stat 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. | ||
| * @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 RawStatData* alloc(const std::string& name) PURE; | ||
| virtual StatData* alloc(const std::string& 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. | ||
| * @param data the data returned by alloc(). | ||
| */ | ||
| virtual void free(RawStatData& data) PURE; | ||
| virtual void free(StatData& data) PURE; | ||
|
|
||
| /** | ||
| * Truncates a stat name based on the options passed into the constructor, | ||
| * issuing a warning if a truncation was needed. | ||
| * | ||
| * @param stat_name The original stat name. | ||
| * @return absl::string_view The stat name, truncated if needed to meet limits in stat_options_. | ||
| */ | ||
| absl::string_view truncateStatName(absl::string_view stat_name); | ||
|
|
||
| /** | ||
| * @return const StatsOptions& The options passed into the constructor. | ||
| */ | ||
| const StatsOptions& statsOptions() const override { return stats_options_; } | ||
|
|
||
| private: | ||
| const StatsOptions& stats_options_; | ||
| }; | ||
|
|
||
| using RawStatDataAllocator = StatDataAllocatorImpl<RawStatData>; | ||
|
|
||
| /** | ||
| * Implementation of HistogramStatistics for circllhist. | ||
| */ | ||
|
|
@@ -373,30 +394,61 @@ class SourceImpl : public Source { | |
| }; | ||
|
|
||
| /** | ||
| * Implementation of RawStatDataAllocator that uses an unordered set to store | ||
| * RawStatData pointers. | ||
| * This structure is an alternate backing store for both CounterImpl and GaugeImpl. It is designed | ||
| * so that it can be allocated efficiently from the heap on demand. | ||
| */ | ||
| struct HeapStatData { | ||
| explicit HeapStatData(absl::string_view key); | ||
|
|
||
| /** | ||
| * Returns the name as a string_view. This is required by BlockMemoryHashSet. | ||
| */ | ||
| absl::string_view key() const { return name_; } | ||
|
|
||
| std::atomic<uint64_t> value_{0}; | ||
| std::atomic<uint64_t> pending_increment_{0}; | ||
| std::atomic<uint16_t> flags_{0}; | ||
| std::atomic<uint16_t> ref_count_{1}; | ||
| std::atomic<uint32_t> unused_{0}; | ||
| std::string name_; | ||
| }; | ||
|
|
||
| /** | ||
| * Implementation of StatDataAllocator using a pure heap-based strategy, so that | ||
| * Envoy implementations that do not require hot-restart can use less memory. | ||
| */ | ||
| class HeapRawStatDataAllocator : public RawStatDataAllocator { | ||
| class HeapStatDataAllocator : public StatDataAllocatorImpl<HeapStatData> { | ||
| public: | ||
| // RawStatDataAllocator | ||
| ~HeapRawStatDataAllocator() { ASSERT(stats_.empty()); } | ||
| RawStatData* alloc(const std::string& name) override; | ||
| void free(RawStatData& data) override; | ||
| explicit HeapStatDataAllocator(const StatsOptions& options) : StatDataAllocatorImpl(options) {} | ||
| // HeapStatDataAllocator() {} | ||
|
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. ?
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. done |
||
| ~HeapStatDataAllocator() { ASSERT(stats_.empty()); } | ||
|
|
||
| // StatDataAllocator | ||
| HeapStatData* alloc(const std::string& name) override; | ||
| void free(HeapStatData& data) override; | ||
|
|
||
| private: | ||
| struct RawStatDataHash_ { | ||
| size_t operator()(const RawStatData* a) const { return HashUtil::xxHash64(a->key()); } | ||
| struct HeapStatHash_ { | ||
| size_t operator()(const HeapStatData* a) const { return HashUtil::xxHash64(a->key()); } | ||
| }; | ||
| struct RawStatDataCompare_ { | ||
| bool operator()(const RawStatData* a, const RawStatData* b) const { | ||
| struct HeapStatCompare_ { | ||
| bool operator()(const HeapStatData* a, const HeapStatData* b) const { | ||
| return (a->key() == b->key()); | ||
| } | ||
| }; | ||
| typedef std::unordered_set<RawStatData*, RawStatDataHash_, RawStatDataCompare_> StringRawDataSet; | ||
|
|
||
| // An unordered set of RawStatData pointers which keys off the key() | ||
| // TODO(jmarantz): storing the set of stats as a string-set wastes an | ||
| // enourmous amount of of memory, because all the stats are composed using the | ||
| // same set of cluster names and patterns found in | ||
| // source/common/config/well_known_names.cc. This should eventually be changed to | ||
| // store a symbol-table of decomposed stat segments (e.g. split on "." to start) and | ||
| // arrays referencing those segments. The segments can be ref-counted so they | ||
| // no longer consume memory once deleted. | ||
|
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. Either remove TODO or point to @ambuc 's latest PR here.
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. Rewrote to reference the new PR. |
||
| typedef std::unordered_set<HeapStatData*, HeapStatHash_, HeapStatCompare_> StatSet; | ||
|
|
||
| // An unordered set of HeapStatData pointers which keys off the key() | ||
| // field in each object. This necessitates a custom comparator and hasher. | ||
| StringRawDataSet stats_ GUARDED_BY(mutex_); | ||
| 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. | ||
|
|
@@ -443,8 +495,16 @@ template <class Base> class IsolatedStatsCache { | |
| */ | ||
| class IsolatedStoreImpl : public Store { | ||
| public: | ||
| IsolatedStoreImpl() | ||
| : counters_([this](const std::string& name) -> CounterSharedPtr { | ||
| // TODO(jmarantz): It appears that if the configuartion overrides the max | ||
| // option length, this isolated store will not see the adjustment. I am | ||
| // not sure if that's a problem. Ideally I'd like to take the stat_options | ||
| // here we can creating the StatAllocator, but it's not immediately obvious | ||
| // where to pull that from. I think this issue existed starting with | ||
| // https://github.com/envoyproxy/envoy/pull/3629, which removed the shadowing | ||
| // of option size in a static. | ||
|
|
||
| /* explicit */ IsolatedStoreImpl(/*Stats::StatsOptions& stats_options*/) | ||
|
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. Note behavior regression here:
If someone (@ambuc @mattklein123 @mrice32) can share the desired behavior (not truncated at all vs truncated with the other stats) I will fix.
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 this comes down to the question of whether or not we expect an IsolatedStore to have stats which are interoperable with other stats. My instinct is no, due to its isolated nature -- in which case this behavior is not something anyone relies upon. Thoughts?
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. Per discussion, I force the truncation at 1M bytes, so effectively we are not truncating in this PR. |
||
| : alloc_(stats_options_), counters_([this](const std::string& name) -> CounterSharedPtr { | ||
| std::string tag_extracted_name = name; | ||
| std::vector<Tag> tags; | ||
| return alloc_.makeCounter(name, std::move(tag_extracted_name), std::move(tags)); | ||
|
|
@@ -500,11 +560,11 @@ class IsolatedStoreImpl : public Store { | |
| const std::string prefix_; | ||
| }; | ||
|
|
||
| HeapRawStatDataAllocator alloc_; | ||
| StatsOptionsImpl stats_options_; | ||
|
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. nit:
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. no longer const-able; I need to update it after allocating it, in order to set the truncation limit high. |
||
| HeapStatDataAllocator alloc_; | ||
| IsolatedStatsCache<Counter> counters_; | ||
| IsolatedStatsCache<Gauge> gauges_; | ||
| IsolatedStatsCache<Histogram> histograms_; | ||
| const Stats::StatsOptionsImpl stats_options_; | ||
| }; | ||
|
|
||
| } // namespace Stats | ||
|
|
||
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.
If we're getting rid of checkAndInit, is there other cleanup in the regular RawStatData struct we can do now, now that it's been decoupled from HeapStatData? Maybe RawStatData, if only for blocked / shared memory, can be less general and more opinionated about its internal memory representation.
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 don't think we're getting rid of checkAndInit. It is still needed in the shm case. I don't think anything gets easier by knowing it's always shm.
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.
correction...checkAndInit is just merged with initialize() now.