| // Copyright 2019 The Abseil Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "absl/strings/internal/cordz_sample_token.h" |
| |
| #include <memory> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "absl/memory/memory.h" |
| #include "absl/random/random.h" |
| #include "absl/strings/cordz_test_helpers.h" |
| #include "absl/strings/internal/cord_rep_flat.h" |
| #include "absl/strings/internal/cordz_handle.h" |
| #include "absl/strings/internal/cordz_info.h" |
| #include "absl/synchronization/internal/thread_pool.h" |
| #include "absl/synchronization/notification.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| |
| namespace absl { |
| ABSL_NAMESPACE_BEGIN |
| namespace cord_internal { |
| namespace { |
| |
| using ::testing::ElementsAre; |
| using ::testing::Eq; |
| using ::testing::Ne; |
| |
| // Used test values |
| auto constexpr kTrackCordMethod = CordzUpdateTracker::kConstructorString; |
| |
| TEST(CordzSampleTokenTest, IteratorTraits) { |
| static_assert(std::is_copy_constructible<CordzSampleToken::Iterator>::value, |
| ""); |
| static_assert(std::is_copy_assignable<CordzSampleToken::Iterator>::value, ""); |
| static_assert(std::is_move_constructible<CordzSampleToken::Iterator>::value, |
| ""); |
| static_assert(std::is_move_assignable<CordzSampleToken::Iterator>::value, ""); |
| static_assert( |
| std::is_same< |
| std::iterator_traits<CordzSampleToken::Iterator>::iterator_category, |
| std::input_iterator_tag>::value, |
| ""); |
| static_assert( |
| std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::value_type, |
| const CordzInfo&>::value, |
| ""); |
| static_assert( |
| std::is_same< |
| std::iterator_traits<CordzSampleToken::Iterator>::difference_type, |
| ptrdiff_t>::value, |
| ""); |
| static_assert( |
| std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::pointer, |
| const CordzInfo*>::value, |
| ""); |
| static_assert( |
| std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::reference, |
| const CordzInfo&>::value, |
| ""); |
| } |
| |
| TEST(CordzSampleTokenTest, IteratorEmpty) { |
| CordzSampleToken token; |
| EXPECT_THAT(token.begin(), Eq(token.end())); |
| } |
| |
| TEST(CordzSampleTokenTest, Iterator) { |
| TestCordData cord1, cord2, cord3; |
| CordzInfo::TrackCord(cord1.data, kTrackCordMethod, 1); |
| CordzInfo* info1 = cord1.data.cordz_info(); |
| CordzInfo::TrackCord(cord2.data, kTrackCordMethod, 1); |
| CordzInfo* info2 = cord2.data.cordz_info(); |
| CordzInfo::TrackCord(cord3.data, kTrackCordMethod, 1); |
| CordzInfo* info3 = cord3.data.cordz_info(); |
| |
| CordzSampleToken token; |
| std::vector<const CordzInfo*> found; |
| for (const CordzInfo& cord_info : token) { |
| found.push_back(&cord_info); |
| } |
| |
| EXPECT_THAT(found, ElementsAre(info3, info2, info1)); |
| |
| info1->Untrack(); |
| info2->Untrack(); |
| info3->Untrack(); |
| } |
| |
| TEST(CordzSampleTokenTest, IteratorEquality) { |
| TestCordData cord1; |
| TestCordData cord2; |
| TestCordData cord3; |
| CordzInfo::TrackCord(cord1.data, kTrackCordMethod, 1); |
| CordzInfo* info1 = cord1.data.cordz_info(); |
| |
| CordzSampleToken token1; |
| // lhs starts with the CordzInfo corresponding to cord1 at the head. |
| CordzSampleToken::Iterator lhs = token1.begin(); |
| |
| CordzInfo::TrackCord(cord2.data, kTrackCordMethod, 1); |
| CordzInfo* info2 = cord2.data.cordz_info(); |
| |
| CordzSampleToken token2; |
| // rhs starts with the CordzInfo corresponding to cord2 at the head. |
| CordzSampleToken::Iterator rhs = token2.begin(); |
| |
| CordzInfo::TrackCord(cord3.data, kTrackCordMethod, 1); |
| CordzInfo* info3 = cord3.data.cordz_info(); |
| |
| // lhs is on cord1 while rhs is on cord2. |
| EXPECT_THAT(lhs, Ne(rhs)); |
| |
| rhs++; |
| // lhs and rhs are both on cord1, but they didn't come from the same |
| // CordzSampleToken. |
| EXPECT_THAT(lhs, Ne(rhs)); |
| |
| lhs++; |
| rhs++; |
| // Both lhs and rhs are done, so they are on nullptr. |
| EXPECT_THAT(lhs, Eq(rhs)); |
| |
| info1->Untrack(); |
| info2->Untrack(); |
| info3->Untrack(); |
| } |
| |
| TEST(CordzSampleTokenTest, MultiThreaded) { |
| Notification stop; |
| static constexpr int kNumThreads = 4; |
| static constexpr int kNumCords = 3; |
| static constexpr int kNumTokens = 3; |
| absl::synchronization_internal::ThreadPool pool(kNumThreads); |
| |
| for (int i = 0; i < kNumThreads; ++i) { |
| pool.Schedule([&stop]() { |
| absl::BitGen gen; |
| TestCordData cords[kNumCords]; |
| std::unique_ptr<CordzSampleToken> tokens[kNumTokens]; |
| |
| while (!stop.HasBeenNotified()) { |
| // Randomly perform one of five actions: |
| // 1) Untrack |
| // 2) Track |
| // 3) Iterate over Cords visible to a token. |
| // 4) Unsample |
| // 5) Sample |
| int index = absl::Uniform(gen, 0, kNumCords); |
| if (absl::Bernoulli(gen, 0.5)) { |
| TestCordData& cord = cords[index]; |
| // Track/untrack. |
| if (cord.data.is_profiled()) { |
| // 1) Untrack |
| cord.data.cordz_info()->Untrack(); |
| cord.data.clear_cordz_info(); |
| } else { |
| // 2) Track |
| CordzInfo::TrackCord(cord.data, kTrackCordMethod, 1); |
| } |
| } else { |
| std::unique_ptr<CordzSampleToken>& token = tokens[index]; |
| if (token) { |
| if (absl::Bernoulli(gen, 0.5)) { |
| // 3) Iterate over Cords visible to a token. |
| for (const CordzInfo& info : *token) { |
| // This is trivial work to allow us to compile the loop. |
| EXPECT_THAT(info.Next(*token), Ne(&info)); |
| } |
| } else { |
| // 4) Unsample |
| token = nullptr; |
| } |
| } else { |
| // 5) Sample |
| token = absl::make_unique<CordzSampleToken>(); |
| } |
| } |
| } |
| for (TestCordData& cord : cords) { |
| CordzInfo::MaybeUntrackCord(cord.data.cordz_info()); |
| } |
| }); |
| } |
| // The threads will hammer away. Give it a little bit of time for tsan to |
| // spot errors. |
| absl::SleepFor(absl::Seconds(3)); |
| stop.Notify(); |
| } |
| |
| } // namespace |
| } // namespace cord_internal |
| ABSL_NAMESPACE_END |
| } // namespace absl |