1 // Copyright 2019 The Abseil 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 // https://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 "absl/strings/internal/cordz_sample_token.h"
16
17 #include <memory>
18 #include <type_traits>
19 #include <vector>
20
21 #include "gmock/gmock.h"
22 #include "gtest/gtest.h"
23 #include "absl/memory/memory.h"
24 #include "absl/random/random.h"
25 #include "absl/strings/cordz_test_helpers.h"
26 #include "absl/strings/internal/cord_rep_flat.h"
27 #include "absl/strings/internal/cordz_handle.h"
28 #include "absl/strings/internal/cordz_info.h"
29 #include "absl/synchronization/internal/thread_pool.h"
30 #include "absl/synchronization/notification.h"
31 #include "absl/time/clock.h"
32 #include "absl/time/time.h"
33
34 namespace absl {
35 ABSL_NAMESPACE_BEGIN
36 namespace cord_internal {
37 namespace {
38
39 using ::testing::ElementsAre;
40 using ::testing::Eq;
41 using ::testing::Ne;
42
43 // Used test values
44 auto constexpr kTrackCordMethod = CordzUpdateTracker::kConstructorString;
45
TEST(CordzSampleTokenTest,IteratorTraits)46 TEST(CordzSampleTokenTest, IteratorTraits) {
47 static_assert(std::is_copy_constructible<CordzSampleToken::Iterator>::value,
48 "");
49 static_assert(std::is_copy_assignable<CordzSampleToken::Iterator>::value, "");
50 static_assert(std::is_move_constructible<CordzSampleToken::Iterator>::value,
51 "");
52 static_assert(std::is_move_assignable<CordzSampleToken::Iterator>::value, "");
53 static_assert(
54 std::is_same<
55 std::iterator_traits<CordzSampleToken::Iterator>::iterator_category,
56 std::input_iterator_tag>::value,
57 "");
58 static_assert(
59 std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::value_type,
60 const CordzInfo&>::value,
61 "");
62 static_assert(
63 std::is_same<
64 std::iterator_traits<CordzSampleToken::Iterator>::difference_type,
65 ptrdiff_t>::value,
66 "");
67 static_assert(
68 std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::pointer,
69 const CordzInfo*>::value,
70 "");
71 static_assert(
72 std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::reference,
73 const CordzInfo&>::value,
74 "");
75 }
76
TEST(CordzSampleTokenTest,IteratorEmpty)77 TEST(CordzSampleTokenTest, IteratorEmpty) {
78 CordzSampleToken token;
79 EXPECT_THAT(token.begin(), Eq(token.end()));
80 }
81
TEST(CordzSampleTokenTest,Iterator)82 TEST(CordzSampleTokenTest, Iterator) {
83 TestCordData cord1, cord2, cord3;
84 CordzInfo::TrackCord(cord1.data, kTrackCordMethod);
85 CordzInfo* info1 = cord1.data.cordz_info();
86 CordzInfo::TrackCord(cord2.data, kTrackCordMethod);
87 CordzInfo* info2 = cord2.data.cordz_info();
88 CordzInfo::TrackCord(cord3.data, kTrackCordMethod);
89 CordzInfo* info3 = cord3.data.cordz_info();
90
91 CordzSampleToken token;
92 std::vector<const CordzInfo*> found;
93 for (const CordzInfo& cord_info : token) {
94 found.push_back(&cord_info);
95 }
96
97 EXPECT_THAT(found, ElementsAre(info3, info2, info1));
98
99 info1->Untrack();
100 info2->Untrack();
101 info3->Untrack();
102 }
103
TEST(CordzSampleTokenTest,IteratorEquality)104 TEST(CordzSampleTokenTest, IteratorEquality) {
105 TestCordData cord1;
106 TestCordData cord2;
107 TestCordData cord3;
108 CordzInfo::TrackCord(cord1.data, kTrackCordMethod);
109 CordzInfo* info1 = cord1.data.cordz_info();
110
111 CordzSampleToken token1;
112 // lhs starts with the CordzInfo corresponding to cord1 at the head.
113 CordzSampleToken::Iterator lhs = token1.begin();
114
115 CordzInfo::TrackCord(cord2.data, kTrackCordMethod);
116 CordzInfo* info2 = cord2.data.cordz_info();
117
118 CordzSampleToken token2;
119 // rhs starts with the CordzInfo corresponding to cord2 at the head.
120 CordzSampleToken::Iterator rhs = token2.begin();
121
122 CordzInfo::TrackCord(cord3.data, kTrackCordMethod);
123 CordzInfo* info3 = cord3.data.cordz_info();
124
125 // lhs is on cord1 while rhs is on cord2.
126 EXPECT_THAT(lhs, Ne(rhs));
127
128 rhs++;
129 // lhs and rhs are both on cord1, but they didn't come from the same
130 // CordzSampleToken.
131 EXPECT_THAT(lhs, Ne(rhs));
132
133 lhs++;
134 rhs++;
135 // Both lhs and rhs are done, so they are on nullptr.
136 EXPECT_THAT(lhs, Eq(rhs));
137
138 info1->Untrack();
139 info2->Untrack();
140 info3->Untrack();
141 }
142
TEST(CordzSampleTokenTest,MultiThreaded)143 TEST(CordzSampleTokenTest, MultiThreaded) {
144 Notification stop;
145 static constexpr int kNumThreads = 4;
146 static constexpr int kNumCords = 3;
147 static constexpr int kNumTokens = 3;
148 absl::synchronization_internal::ThreadPool pool(kNumThreads);
149
150 for (int i = 0; i < kNumThreads; ++i) {
151 pool.Schedule([&stop]() {
152 absl::BitGen gen;
153 TestCordData cords[kNumCords];
154 std::unique_ptr<CordzSampleToken> tokens[kNumTokens];
155
156 while (!stop.HasBeenNotified()) {
157 // Randomly perform one of five actions:
158 // 1) Untrack
159 // 2) Track
160 // 3) Iterate over Cords visible to a token.
161 // 4) Unsample
162 // 5) Sample
163 int index = absl::Uniform(gen, 0, kNumCords);
164 if (absl::Bernoulli(gen, 0.5)) {
165 TestCordData& cord = cords[index];
166 // Track/untrack.
167 if (cord.data.is_profiled()) {
168 // 1) Untrack
169 cord.data.cordz_info()->Untrack();
170 cord.data.clear_cordz_info();;
171 } else {
172 // 2) Track
173 CordzInfo::TrackCord(cord.data, kTrackCordMethod);
174 }
175 } else {
176 std::unique_ptr<CordzSampleToken>& token = tokens[index];
177 if (token) {
178 if (absl::Bernoulli(gen, 0.5)) {
179 // 3) Iterate over Cords visible to a token.
180 for (const CordzInfo& info : *token) {
181 // This is trivial work to allow us to compile the loop.
182 EXPECT_THAT(info.Next(*token), Ne(&info));
183 }
184 } else {
185 // 4) Unsample
186 token = nullptr;
187 }
188 } else {
189 // 5) Sample
190 token = absl::make_unique<CordzSampleToken>();
191 }
192 }
193 }
194 for (TestCordData& cord : cords) {
195 CordzInfo::MaybeUntrackCord(cord.data.cordz_info());
196 }
197 });
198 }
199 // The threads will hammer away. Give it a little bit of time for tsan to
200 // spot errors.
201 absl::SleepFor(absl::Seconds(3));
202 stop.Notify();
203 }
204
205 } // namespace
206 } // namespace cord_internal
207 ABSL_NAMESPACE_END
208 } // namespace absl
209