Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a57e03a
Improve cpu and memory usage of stats (counters, gauges and text
pradeepcrao Aug 20, 2021
9154c94
fix formatting.
pradeepcrao Aug 21, 2021
8325266
Iterate over stats to be sinked instead of creating and returning a
pradeepcrao Aug 30, 2021
d556414
Revert added test for phase 2
pradeepcrao Aug 30, 2021
f91bb29
Revert added test for phase 2
pradeepcrao Aug 30, 2021
db248ad
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Aug 31, 2021
5be8b36
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Aug 31, 2021
c847d1c
Add missing header
pradeepcrao Aug 31, 2021
6e38658
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Aug 31, 2021
291a2ff
Keep rejected counters, gauges and text readouts in the allocator,
pradeepcrao Sep 1, 2021
b9039ac
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
e5a8a82
Remove hasStat. Move deleted stats to stats set in ~AllocatorImpl.
pradeepcrao Sep 1, 2021
b2e5d5f
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
cb7750f
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
e4615c1
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
66bfed4
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 1, 2021
915ea77
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 1, 2021
6f6ce30
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
bb3306b
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 1, 2021
a85a887
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 1, 2021
3eeb986
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 1, 2021
d7b5ed5
Add tests for forEach<Metric> apis.
pradeepcrao Sep 1, 2021
58bd2ba
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
3e71e1a
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
9166754
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 1, 2021
f6a456b
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 1, 2021
65b2c78
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 1, 2021
f92e178
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 2, 2021
4a5572d
Merge branch 'stats_no_vector' of github.com:pradeepcrao/envoy into s…
pradeepcrao Sep 2, 2021
7cad621
Add test, fix crash.
pradeepcrao Sep 2, 2021
b10d6e1
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 2, 2021
0f9ea87
Add test to document behavior for rejected stats on store and allocator.
pradeepcrao Sep 3, 2021
d6116e8
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 3, 2021
263205e
Address comments.
pradeepcrao Sep 3, 2021
801190f
Merge remote-tracking branch 'upstream/main' into stats_no_vector
pradeepcrao Sep 3, 2021
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
25 changes: 25 additions & 0 deletions envoy/stats/allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ class Allocator {
virtual const SymbolTable& constSymbolTable() const PURE;
virtual SymbolTable& symbolTable() PURE;

/**
* Mark rejected stats as deleted by moving them to a different vector, so they don't show up
* when iterating over stats, but prevent crashes when trying to access references to them.
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.

Note here the surprising behavior that if you allocate a stat after having done this, you'll get a new one, and that callers should seek to avoid this situation (as ThreadLocalStore does).

* Note that allocating a stat with the same name after calling this will
* return a new stat. Hence callers should seek to avoid this situation, as is
* done in ThreadLocalStore.
*/
virtual void markCounterForDeletion(const CounterSharedPtr& counter) PURE;
virtual void markGaugeForDeletion(const GaugeSharedPtr& gauge) PURE;
virtual void markTextReadoutForDeletion(const TextReadoutSharedPtr& text_readout) PURE;

/**
* Iterate over all stats that need to be sinked. Note, that implementations can potentially hold
* on to a mutex that will deadlock if the passed in functors try to create or delete a stat.
* @param f_size functor that is provided the number of all sinked stats. Note this is called
* only once, prior to any calls to f_stat.
* @param f_stat functor that is provided one sinked stat at a time.
*/
virtual void forEachCounter(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Counter&)> f_stat) const PURE;
virtual void forEachGauge(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Gauge&)> f_stat) const PURE;
virtual void forEachTextReadout(std::function<void(std::size_t)> f_size,
std::function<void(Stats::TextReadout&)> f_stat) const PURE;

// TODO(jmarantz): create a parallel mechanism to instantiate histograms. At
// the moment, histograms don't fit the same pattern of counters and gauges
// as they are not actually created in the context of a stats allocator.
Expand Down
16 changes: 16 additions & 0 deletions envoy/stats/store.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "envoy/common/pure.h"
#include "envoy/stats/histogram.h"
#include "envoy/stats/scope.h"
#include "envoy/stats/stats.h"
#include "envoy/stats/stats_matcher.h"
#include "envoy/stats/tag_producer.h"

Expand Down Expand Up @@ -48,6 +49,21 @@ class Store : public Scope {
* @return a list of all known histograms.
*/
virtual std::vector<ParentHistogramSharedPtr> histograms() const PURE;

/**
* Iterate over all stats that need to be sinked. Note, that implementations can potentially hold
* on to a mutex that will deadlock if the passed in functors try to create or delete a stat.
* @param f_size functor that is provided the number of all sinked stats.
* @param f_stat functor that is provided one sinked stat at a time.
*/
virtual void forEachCounter(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Counter&)> f_stat) const PURE;

virtual void forEachGauge(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Gauge&)> f_stat) const PURE;

virtual void forEachTextReadout(std::function<void(std::size_t)> f_size,
std::function<void(Stats::TextReadout&)> f_stat) const PURE;
};

using StorePtr = std::unique_ptr<Store>;
Expand Down
92 changes: 92 additions & 0 deletions source/common/stats/allocator_impl.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "source/common/stats/allocator_impl.h"

#include <algorithm>
#include <cstdint>

#include "envoy/stats/stats.h"
Expand All @@ -25,6 +26,25 @@ const char AllocatorImpl::DecrementToZeroSyncPoint[] = "decrement-zero";
AllocatorImpl::~AllocatorImpl() {
ASSERT(counters_.empty());
ASSERT(gauges_.empty());

#ifndef NDEBUG
// Move deleted stats into the sets for the ASSERTs in removeFromSetLockHeld to function.
for (auto& counter : deleted_counters_) {
auto insertion = counters_.insert(counter.get());
// Assert that there were no duplicates.
ASSERT(insertion.second);
}
for (auto& gauge : deleted_gauges_) {
auto insertion = gauges_.insert(gauge.get());
// Assert that there were no duplicates.
ASSERT(insertion.second);
}
for (auto& text_readout : deleted_text_readouts_) {
auto insertion = text_readouts_.insert(text_readout.get());
// Assert that there were no duplicates.
ASSERT(insertion.second);
}
#endif
}

#ifndef ENVOY_CONFIG_COVERAGE
Expand Down Expand Up @@ -316,5 +336,77 @@ Counter* AllocatorImpl::makeCounterInternal(StatName name, StatName tag_extracte
return new CounterImpl(name, *this, tag_extracted_name, stat_name_tags);
}

void AllocatorImpl::forEachCounter(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Counter&)> f_stat) const {
Thread::LockGuard lock(mutex_);
if (f_size != nullptr) {
f_size(counters_.size());
}
for (auto& counter : counters_) {
f_stat(*counter);
}
}

void AllocatorImpl::forEachGauge(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Gauge&)> f_stat) const {
Thread::LockGuard lock(mutex_);
if (f_size != nullptr) {
f_size(gauges_.size());
}
for (auto& gauge : gauges_) {
f_stat(*gauge);
}
}

void AllocatorImpl::forEachTextReadout(std::function<void(std::size_t)> f_size,
std::function<void(Stats::TextReadout&)> f_stat) const {
Thread::LockGuard lock(mutex_);
if (f_size != nullptr) {
f_size(text_readouts_.size());
}
for (auto& text_readout : text_readouts_) {
f_stat(*text_readout);
}
}

void AllocatorImpl::markCounterForDeletion(const CounterSharedPtr& counter) {
Thread::LockGuard lock(mutex_);
auto iter = counters_.find(counter->statName());
if (iter == counters_.end()) {
// This has already been marked for deletion.
return;
}
ASSERT(counter.get() == *iter);
// Duplicates are ASSERTed in ~AllocatorImpl.
deleted_counters_.emplace_back(*iter);
counters_.erase(iter);
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.

I thought of a concern with this tactic for this class: if someone allocates a counter with the same name as one that was marked for deletion, we'll wind with two different counter objects with the same name. That might be OK but we might want to add a test.

It won't actually happen with ThreadLocalStore because it only calls markCounterForDeletion on stats that are rejected by the matcher, and the matcher can't be changed once it's added. The reason that this is needed is that the matcher can be added after some stats are created, and we need to effectively get rid of the now-rejected stats without causing references to freed memory.

Possible remedies I thought of include:

  • add a test and accept that as a weird use-case for this class, that won't be hit by ThreadLocalStore.
  • prevent this by checking against deleted_counters_ (which could be a set) during allocation
  • declare this invalid by asserting against deleted_counters_ for debug builds
  • use a bit in flags_ to denote that a stat needs be considered as marked for deletion, and skip that during forEach. You could report the correct size by keeping track of the number of deleted counters in the class. I think this is the biggest change from what you have, but would mean that if you try to allocate a stat with the same name as one that was deleted, you'll get back the previously deleted stat.

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.

use a bit in flags_ to denote that a stat needs be considered as marked for deletion, and skip that during forEach. You could report the correct size by keeping track of the number of deleted counters in the class. I think this is the biggest change from what you have, but would mean that if you try to allocate a stat with the same name as one that was deleted, you'll get back the previously deleted stat.

This approach sounds good to me, fwiw.

Stepping back though, I'm trying to understand why this logic was moved into the allocator versus where it used to be in thread local store? Can you help me understand that?

}

void AllocatorImpl::markGaugeForDeletion(const GaugeSharedPtr& gauge) {
Thread::LockGuard lock(mutex_);
auto iter = gauges_.find(gauge->statName());
if (iter == gauges_.end()) {
// This has already been marked for deletion.
return;
}
ASSERT(gauge.get() == *iter);
// Duplicates are ASSERTed in ~AllocatorImpl.
deleted_gauges_.emplace_back(*iter);
gauges_.erase(iter);
}

void AllocatorImpl::markTextReadoutForDeletion(const TextReadoutSharedPtr& text_readout) {
Thread::LockGuard lock(mutex_);
auto iter = text_readouts_.find(text_readout->statName());
if (iter == text_readouts_.end()) {
// This has already been marked for deletion.
return;
}
ASSERT(text_readout.get() == *iter);
// Duplicates are ASSERTed in ~AllocatorImpl.
deleted_text_readouts_.emplace_back(*iter);
text_readouts_.erase(iter);
}

} // namespace Stats
} // namespace Envoy
39 changes: 30 additions & 9 deletions source/common/stats/allocator_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ class AllocatorImpl : public Allocator {
SymbolTable& symbolTable() override { return symbol_table_; }
const SymbolTable& constSymbolTable() const override { return symbol_table_; }

void forEachCounter(std::function<void(std::size_t)>,
std::function<void(Stats::Counter&)>) const override;

void forEachGauge(std::function<void(std::size_t)>,
std::function<void(Stats::Gauge&)>) const override;

void forEachTextReadout(std::function<void(std::size_t)>,
std::function<void(Stats::TextReadout&)>) const override;

#ifndef ENVOY_CONFIG_COVERAGE
void debugPrint();
#endif
Expand All @@ -47,6 +56,10 @@ class AllocatorImpl : public Allocator {
*/
bool isMutexLockedForTest();

void markCounterForDeletion(const CounterSharedPtr& counter) override;
void markGaugeForDeletion(const GaugeSharedPtr& gauge) override;
void markTextReadoutForDeletion(const TextReadoutSharedPtr& text_readout) override;

protected:
virtual Counter* makeCounterInternal(StatName name, StatName tag_extracted_name,
const StatNameTagVector& stat_name_tags);
Expand All @@ -58,21 +71,29 @@ class AllocatorImpl : public Allocator {
friend class TextReadoutImpl;
friend class NotifyingAllocatorImpl;

void removeCounterFromSetLockHeld(Counter* counter) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void removeGaugeFromSetLockHeld(Gauge* gauge) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void removeTextReadoutFromSetLockHeld(Counter* counter) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// A mutex is needed here to protect both 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.
mutable Thread::MutexBasicLockable mutex_;

StatSet<Counter> counters_ ABSL_GUARDED_BY(mutex_);
StatSet<Gauge> gauges_ ABSL_GUARDED_BY(mutex_);
StatSet<TextReadout> text_readouts_ ABSL_GUARDED_BY(mutex_);

SymbolTable& symbol_table_;
// Retain storage for deleted stats; these are no longer in maps because
// the matcher-pattern was established after they were created. Since the
// stats are held by reference in code that expects them to be there, we
// can't actually delete the stats.
//
// It seems like it would be better to have each client that expects a stat
// to exist to hold it as (e.g.) a CounterSharedPtr rather than a Counter&
// but that would be fairly complex to change.
std::vector<CounterSharedPtr> deleted_counters_ ABSL_GUARDED_BY(mutex_);
std::vector<GaugeSharedPtr> deleted_gauges_ ABSL_GUARDED_BY(mutex_);
std::vector<TextReadoutSharedPtr> deleted_text_readouts_ ABSL_GUARDED_BY(mutex_);

// A mutex is needed here to protect both 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.
Thread::MutexBasicLockable mutex_;
SymbolTable& symbol_table_;

Thread::ThreadSynchronizer sync_;
};
Expand Down
24 changes: 24 additions & 0 deletions source/common/stats/isolated_store_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <algorithm>
#include <cstring>
#include <functional>
#include <string>

#include "envoy/stats/stats.h"
Expand Down Expand Up @@ -100,6 +101,14 @@ template <class Base> class IsolatedStatsCache {
return true;
}

void forEachStat(std::function<void(std::size_t)> f_size,
std::function<void(Base&)> f_stat) const {
f_size(stats_.size());
for (auto const& stat : stats_) {
f_stat(*stat.second);
}
}

private:
friend class IsolatedStoreImpl;

Expand Down Expand Up @@ -205,6 +214,21 @@ class IsolatedStoreImpl : public StoreImpl {
return textReadoutFromStatName(storage.statName());
}

void forEachCounter(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Counter&)> f_stat) const override {
counters_.forEachStat(f_size, f_stat);
}

void forEachGauge(std::function<void(std::size_t)> f_size,
std::function<void(Stats::Gauge&)> f_stat) const override {
gauges_.forEachStat(f_size, f_stat);
}

void forEachTextReadout(std::function<void(std::size_t)> f_size,
std::function<void(Stats::TextReadout&)> f_stat) const override {
text_readouts_.forEachStat(f_size, f_stat);
}

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

Expand Down
Loading