Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3d5849d
First working draft of simpler reference-counted heap allocator
ambuc Apr 25, 2018
b6aa029
Fixed reference to pointer to RawStatData
ambuc Apr 25, 2018
d615eef
Add HeapAlloc test for HeapRawStatDataAllocator
ambuc Apr 25, 2018
cdd8418
Initialized data based on truncated name
ambuc Apr 25, 2018
101c7aa
Remove leftover comments
ambuc Apr 25, 2018
282c56b
Initialized data based on non-truncated name
ambuc Apr 25, 2018
830c718
Initialized data based on truncated name, and also warn in both alloc…
ambuc Apr 25, 2018
4de411a
Remove leftover couts
ambuc Apr 25, 2018
11a8cdb
First working draft of simpler reference-counted heap allocator
ambuc Apr 25, 2018
ecd7714
Fixed reference to pointer to RawStatData
ambuc Apr 25, 2018
8c00102
Add HeapAlloc test for HeapRawStatDataAllocator
ambuc Apr 25, 2018
ddcb0f9
Initialized data based on truncated name
ambuc Apr 25, 2018
7031caa
Remove leftover comments
ambuc Apr 25, 2018
93eb3d7
Initialized data based on non-truncated name
ambuc Apr 25, 2018
b4b1b39
Initialized data based on truncated name, and also warn in both alloc…
ambuc Apr 25, 2018
7e2b8d6
Remove leftover couts
ambuc Apr 25, 2018
96582ad
Merge branch 'refcount-stats-in-heap-alloc' of https://github.com/amb…
ambuc Apr 27, 2018
bec4fac
Fix free stat issue, typedef datamap, assorted
ambuc Apr 27, 2018
ea27f46
Add mutex to HeapRawStatDataAllocator
ambuc Apr 30, 2018
a4eb61e
Rename mutex, make non-mutable
ambuc Apr 30, 2018
b388492
Use unordered_set<> for HeapRawStatDataAllocator's stats
ambuc May 1, 2018
3b9d839
Tighten mutex; remove temp key
ambuc May 1, 2018
b10f663
Minimize time spent holding lock
ambuc May 1, 2018
759e6d9
Fix camelCase style issue
ambuc May 1, 2018
8a8a39d
Add more robust null checking around RawDataTest/Truncate
ambuc May 1, 2018
f0e19ae
Change EXPECTs to ASSERTs in RawStatDataTest/Truncate
ambuc May 1, 2018
76ebd08
Tighten mutex around HeapRawStatDataAllocator::free()
ambuc May 1, 2018
87318c0
More specific asserts around unordered_map::erase behavior
ambuc May 2, 2018
84913f9
Merge branch 'master' into refcount-stats-in-heap-alloc
ambuc May 2, 2018
5194ec2
Add GUARDED_BY to mutex_, documentation around StringRawDataSet
ambuc May 3, 2018
ce99186
Merge branch 'master' into refcount-stats-in-heap-alloc
ambuc May 3, 2018
5429301
Formatting updates
ambuc May 3, 2018
40ae83b
Add documentation for HeapRawStatDataAllocator
ambuc May 4, 2018
20526e7
Edits for documentation for HeapRawStatDataAllocator
ambuc May 4, 2018
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
1 change: 1 addition & 0 deletions source/common/stats/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ envoy_cc_library(
"//source/common/common:hash_lib",
"//source/common/common:non_copyable",
"//source/common/common:perf_annotation_lib",
"//source/common/common:thread_annotations",
"//source/common/common:utility_lib",
"//source/common/config:well_known_names",
"//source/common/protobuf",
Expand Down
36 changes: 30 additions & 6 deletions source/common/stats/stats_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,26 @@ bool TagExtractorImpl::extractTag(const std::string& stat_name, std::vector<Tag>
}

RawStatData* HeapRawStatDataAllocator::alloc(const std::string& name) {
// This must be zero-initialized
RawStatData* data = static_cast<RawStatData*>(::calloc(RawStatData::size(), 1));
data->initialize(name);
return data;

// Because the RawStatData object is initialized with and contains a truncated
// version of the std::string name, storing the stats in a map would require
// storing the name twice. Performing a lookup on the set is similarly
// expensive to performing a map lookup, since both require copying a truncated version of the
// string before doing the hash lookup.
std::unique_lock<std::mutex> lock(mutex_);
auto ret = stats_.insert(data);
RawStatData* existing_data = *ret.first;
lock.unlock();

if (!ret.second) {
::free(data);
++existing_data->ref_count_;
return existing_data;
} else {
return data;
}
}

TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v2::StatsConfig& config)
Expand Down Expand Up @@ -256,18 +272,26 @@ TagProducerImpl::addDefaultExtractors(const envoy::config::metrics::v2::StatsCon
}

void HeapRawStatDataAllocator::free(RawStatData& data) {
// This allocator does not ever have concurrent access to the raw data.
ASSERT(data.ref_count_ == 1);
ASSERT(data.ref_count_ > 0);
if (--data.ref_count_ > 0) {
return;
}

std::unique_lock<std::mutex> lock(mutex_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need locking at all if the old comment about "This allocator does not ever have concurrent access to the raw data" hold true?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem isn't the stat, it's the set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. Also, side note: that comment is no longer valid since the same stat can be freed/allocated multiple times, meaning that there may be cases where the allocator is operating on the same raw stat from two different threads.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT we only ever do these allocations under existing locking, e.g.

SafeAllocData alloc = parent_.safeAlloc(final_name);
. Do we need to be double locking here?

I might be wrong in my assessment, please point out if not (and add a comment to the code!).

Copy link
Member

@mrice32 mrice32 May 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point, there probably need to be comments around this. The alloc() calls are protected, but the free() calls are made from the destructors of the individual stat objects. See https://github.com/envoyproxy/envoy/blob/master/source/common/stats/stats_impl.h#L310 for an example.

size_t key_removed = stats_.erase(&data);
lock.unlock();

ASSERT(key_removed == 1);
::free(&data);
}

void RawStatData::initialize(absl::string_view key) {
ASSERT(!initialized());
if (key.size() > maxNameLength()) {
if (key.size() > Stats::RawStatData::maxNameLength()) {
ENVOY_LOG_MISC(
warn,
"Statistic '{}' is too long with {} characters, it will be truncated to {} characters", key,
key.size(), maxNameLength());
key.size(), Stats::RawStatData::maxNameLength());
}
ref_count_ = 1;

Expand Down
25 changes: 23 additions & 2 deletions source/common/stats/stats_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "common/common/assert.h"
#include "common/common/hash.h"
#include "common/common/non_copyable.h"
#include "common/common/thread_annotations.h"
#include "common/common/utility.h"
#include "common/protobuf/protobuf.h"

Expand Down Expand Up @@ -406,14 +407,34 @@ class HistogramImpl : public Histogram, public MetricImpl {
};

/**
* Implementation of RawStatDataAllocator that just allocates a new structure in memory and returns
* it.
* Implementation of RawStatDataAllocator that uses an unordered set to store
* RawStatData pointers.
*/
class HeapRawStatDataAllocator : public RawStatDataAllocator {
public:
// RawStatDataAllocator
~HeapRawStatDataAllocator() { ASSERT(stats_.empty()); }
RawStatData* alloc(const std::string& name) override;
void free(RawStatData& data) override;

private:
struct RawStatDataHash_ {
size_t operator()(const RawStatData* a) const { return HashUtil::xxHash64(a->key()); }
};
struct RawStatDataCompare_ {
bool operator()(const RawStatData* a, const RawStatData* 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()
// field in each object. This necessitates a custom comparator and hasher.
StringRawDataSet 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.
std::mutex mutex_;
};

/**
Expand Down
16 changes: 16 additions & 0 deletions test/common/stats/stats_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -478,5 +478,21 @@ TEST(RawStatDataTest, Truncate) {
alloc.free(*stat);
}

TEST(RawStatDataTest, HeapAlloc) {
HeapRawStatDataAllocator alloc;
RawStatData* stat_1 = alloc.alloc("ref_name");
ASSERT_NE(stat_1, nullptr);
RawStatData* stat_2 = alloc.alloc("ref_name");
ASSERT_NE(stat_2, nullptr);
RawStatData* stat_3 = alloc.alloc("not_ref_name");
ASSERT_NE(stat_3, nullptr);
EXPECT_EQ(stat_1, stat_2);
EXPECT_NE(stat_1, stat_3);
EXPECT_NE(stat_2, stat_3);
alloc.free(*stat_1);
alloc.free(*stat_2);
alloc.free(*stat_3);
}

} // namespace Stats
} // namespace Envoy