Skip to content
This repository was archived by the owner on Jul 31, 2023. It is now read-only.

Commit f0f7303

Browse files
author
Ian Sturdy
authored
Add a TagSet class providing equality and hashing for key/value maps. (#95)
1 parent f83b273 commit f0f7303

5 files changed

Lines changed: 231 additions & 0 deletions

File tree

opencensus/stats/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ cc_library(
6565
"internal/set_aggregation_window.cc",
6666
"internal/stats_exporter.cc",
6767
"internal/stats_manager.cc",
68+
"internal/tag_set.cc",
6869
"internal/view.cc",
6970
"internal/view_data.cc",
7071
"internal/view_data_impl.cc",
@@ -84,6 +85,7 @@ cc_library(
8485
"measure_descriptor.h",
8586
"measure_registry.h",
8687
"stats_exporter.h",
88+
"tag_set.h",
8789
"view.h",
8890
"view_data.h",
8991
"view_descriptor.h",
@@ -185,6 +187,17 @@ cc_test(
185187
],
186188
)
187189

190+
cc_test(
191+
name = "tag_set_test",
192+
size = "small",
193+
srcs = ["internal/tag_set_test.cc"],
194+
copts = TEST_COPTS,
195+
deps = [
196+
":core",
197+
"@com_google_googletest//:gtest_main",
198+
],
199+
)
200+
188201
cc_test(
189202
name = "view_data_impl_test",
190203
srcs = ["internal/view_data_impl_test.cc"],
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2018, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "opencensus/stats/tag_set.h"
16+
17+
#include <functional>
18+
#include <initializer_list>
19+
#include <string>
20+
#include <utility>
21+
#include <vector>
22+
23+
#include "absl/strings/string_view.h"
24+
25+
namespace opencensus {
26+
namespace stats {
27+
28+
TagSet::TagSet(
29+
std::initializer_list<std::pair<absl::string_view, absl::string_view>>
30+
tags) {
31+
tags_.reserve(tags.size());
32+
for (const auto& tag : tags) {
33+
tags_.emplace_back(std::string(tag.first), std::string(tag.second));
34+
}
35+
Initialize();
36+
}
37+
38+
TagSet::TagSet(std::vector<std::pair<std::string, std::string>> tags)
39+
: tags_(std::move(tags)) {
40+
Initialize();
41+
}
42+
43+
void TagSet::Initialize() {
44+
std::sort(tags_.begin(), tags_.end());
45+
46+
std::hash<std::string> hasher;
47+
hash_ = 1;
48+
for (const auto& tag : tags_) {
49+
static const size_t kMul = static_cast<size_t>(0xdc3eb94af8ab4c93ULL);
50+
hash_ *= kMul;
51+
hash_ = ((hash_ << 19) |
52+
(hash_ >> (std::numeric_limits<size_t>::digits - 19))) +
53+
hasher(tag.first);
54+
hash_ *= kMul;
55+
hash_ = ((hash_ << 19) |
56+
(hash_ >> (std::numeric_limits<size_t>::digits - 19))) +
57+
hasher(tag.second);
58+
}
59+
}
60+
61+
std::size_t TagSet::Hash::operator()(const TagSet& tag_set) const {
62+
return tag_set.hash_;
63+
}
64+
65+
bool TagSet::operator==(const TagSet& other) const {
66+
return tags_ == other.tags_;
67+
}
68+
69+
} // namespace stats
70+
} // namespace opencensus
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2018, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "opencensus/stats/tag_set.h"
16+
17+
#include <string>
18+
#include <unordered_map>
19+
#include <utility>
20+
#include <vector>
21+
22+
#include "gmock/gmock.h"
23+
#include "gtest/gtest.h"
24+
25+
namespace opencensus {
26+
namespace stats {
27+
namespace {
28+
29+
TEST(TagSetTest, ConstructorsEquivalent) {
30+
const std::vector<std::pair<std::string, std::string>> tags({{"k", "v"}});
31+
EXPECT_EQ(TagSet(tags), TagSet({{"k", "v"}}));
32+
}
33+
34+
TEST(TagSetTest, TagsSorted) {
35+
const std::vector<std::pair<std::string, std::string>> expected = {
36+
{"B", "v"}, {"a", "v"}, {"b", "v"}};
37+
const std::vector<std::pair<std::string, std::string>> tags(
38+
{{"b", "v"}, {"a", "v"}, {"B", "v"}});
39+
EXPECT_THAT(TagSet(tags).tags(), ::testing::ElementsAreArray(expected));
40+
EXPECT_THAT(TagSet({{"B", "v"}, {"b", "v"}, {"a", "v"}}).tags(),
41+
::testing::ElementsAreArray(expected));
42+
}
43+
44+
TEST(TagSetTest, EqualityDisregardsOrder) {
45+
EXPECT_EQ(TagSet({{"k1", "v1"}, {"k2", "v2"}}),
46+
TagSet({{"k2", "v2"}, {"k1", "v1"}}));
47+
}
48+
49+
TEST(TagSetTest, EqualityRespectsMissingKeys) {
50+
EXPECT_NE(TagSet({{"k1", "v1"}, {"k2", "v2"}}), TagSet({{"k1", "v1"}}));
51+
}
52+
53+
TEST(TagSetTest, EqualityRespectsKeyValuePairings) {
54+
EXPECT_NE(TagSet({{"k1", "v1"}, {"k2", "v2"}}),
55+
TagSet({{"k1", "v2"}, {"k2", "v1"}}));
56+
}
57+
58+
TEST(TagSetTest, HashDisregardsOrder) {
59+
TagSet ts1({{"k1", "v1"}, {"k2", "v2"}});
60+
TagSet ts2({{"k2", "v2"}, {"k1", "v1"}});
61+
EXPECT_EQ(TagSet::Hash()(ts1), TagSet::Hash()(ts2));
62+
}
63+
64+
TEST(TagSetTest, HashRespectsKeyValuePairings) {
65+
TagSet ts1({{"k1", "v1"}, {"k2", "v2"}});
66+
TagSet ts2({{"k1", "v2"}, {"k2", "v1"}});
67+
EXPECT_NE(TagSet::Hash()(ts1), TagSet::Hash()(ts2));
68+
}
69+
70+
TEST(TagSetTest, UnorderedMap) {
71+
// Test that the operators and hash are compatible with std::unordered_map.
72+
std::unordered_map<TagSet, int, TagSet::Hash> map;
73+
TagSet ts = {{"key", "value"}};
74+
map.emplace(ts, 1);
75+
EXPECT_NE(map.end(), map.find(ts));
76+
EXPECT_EQ(1, map.erase(ts));
77+
}
78+
79+
} // namespace
80+
} // namespace stats
81+
} // namespace opencensus

opencensus/stats/stats.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "opencensus/stats/measure_registry.h" // IWYU pragma: export
2525
#include "opencensus/stats/recording.h" // IWYU pragma: export
2626
#include "opencensus/stats/stats_exporter.h" // IWYU pragma: export
27+
#include "opencensus/stats/tag_set.h" // IWYU pragma: export
2728
#include "opencensus/stats/view.h" // IWYU pragma: export
2829
#include "opencensus/stats/view_data.h" // IWYU pragma: export
2930
#include "opencensus/stats/view_descriptor.h" // IWYU pragma: export

opencensus/stats/tag_set.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2018, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef OPENCENSUS_STATS_TAG_SET_H_
16+
#define OPENCENSUS_STATS_TAG_SET_H_
17+
18+
#include <initializer_list>
19+
#include <string>
20+
#include <utility>
21+
#include <vector>
22+
23+
#include "absl/strings/string_view.h"
24+
25+
namespace opencensus {
26+
namespace stats {
27+
28+
// TagSet represents a set of key-value tags, and provides efficient equality
29+
// and hash operations. A TagSet is expensive to construct, and should be shared
30+
// between uses where possible.
31+
// TagSet is immutable.
32+
class TagSet final {
33+
public:
34+
// Both constructors are not explicit so that Record({}, {{"k", "v"}}) works.
35+
// This constructor is needed because even though we copy to a vector
36+
// internally because c++ cannot deduce the conversion needed.
37+
TagSet(std::initializer_list<std::pair<absl::string_view, absl::string_view>>
38+
tags);
39+
// This constructor is needed so that callers can dynamically construct
40+
// tagsets. It takes the argument by value to allow it to be moved.
41+
TagSet(std::vector<std::pair<std::string, std::string>> tags);
42+
43+
// Accesses the tags sorted by key.
44+
const std::vector<std::pair<std::string, std::string>>& tags() const {
45+
return tags_;
46+
}
47+
48+
struct Hash {
49+
std::size_t operator()(const TagSet& tag_set) const;
50+
};
51+
52+
bool operator==(const TagSet& other) const;
53+
bool operator!=(const TagSet& other) const { return !(*this == other); }
54+
55+
private:
56+
void Initialize();
57+
58+
std::size_t hash_;
59+
// TODO: add an option to store string_views to avoid copies.
60+
std::vector<std::pair<std::string, std::string>> tags_;
61+
};
62+
63+
} // namespace stats
64+
} // namespace opencensus
65+
66+
#endif // OPENCENSUS_STATS_TAG_SET_H_

0 commit comments

Comments
 (0)