// 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_info.h" #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" #include "absl/debugging/stacktrace.h" #include "absl/debugging/symbolize.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_statistics.h" #include "absl/strings/internal/cordz_update_tracker.h" #include "absl/strings/str_cat.h" #include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { namespace { using ::testing::ElementsAre; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::Ne; using ::testing::SizeIs; // Used test values auto constexpr kUnknownMethod = CordzUpdateTracker::kUnknown; auto constexpr kTrackCordMethod = CordzUpdateTracker::kConstructorString; auto constexpr kChildMethod = CordzUpdateTracker::kConstructorCord; auto constexpr kUpdateMethod = CordzUpdateTracker::kAppendString; // Local less verbose helper std::vector DeleteQueue() { return CordzHandle::DiagnosticsGetDeleteQueue(); } std::string FormatStack(absl::Span raw_stack) { static constexpr size_t buf_size = 1 << 14; std::unique_ptr buf(new char[buf_size]); std::string output; for (void* stackp : raw_stack) { if (absl::Symbolize(stackp, buf.get(), buf_size)) { absl::StrAppend(&output, " ", buf.get(), "\n"); } } return output; } TEST(CordzInfoTest, TrackCord) { TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); ASSERT_THAT(info, Ne(nullptr)); EXPECT_FALSE(info->is_snapshot()); EXPECT_THAT(CordzInfo::Head(CordzSnapshot()), Eq(info)); EXPECT_THAT(info->GetCordRepForTesting(), Eq(data.rep.rep)); info->Untrack(); } TEST(CordzInfoTest, MaybeTrackChildCordWithoutSampling) { CordzSamplingIntervalHelper sample_none(99999); TestCordData parent, child; CordzInfo::MaybeTrackCord(child.data, parent.data, kTrackCordMethod); EXPECT_THAT(child.data.cordz_info(), Eq(nullptr)); } TEST(CordzInfoTest, MaybeTrackChildCordWithSampling) { CordzSamplingIntervalHelper sample_all(1); TestCordData parent, child; CordzInfo::MaybeTrackCord(child.data, parent.data, kTrackCordMethod); EXPECT_THAT(child.data.cordz_info(), Eq(nullptr)); } TEST(CordzInfoTest, MaybeTrackChildCordWithoutSamplingParentSampled) { CordzSamplingIntervalHelper sample_none(99999); TestCordData parent, child; CordzInfo::TrackCord(parent.data, kTrackCordMethod, 1); CordzInfo::MaybeTrackCord(child.data, parent.data, kTrackCordMethod); CordzInfo* parent_info = parent.data.cordz_info(); CordzInfo* child_info = child.data.cordz_info(); ASSERT_THAT(child_info, Ne(nullptr)); EXPECT_THAT(child_info->GetCordRepForTesting(), Eq(child.rep.rep)); EXPECT_THAT(child_info->GetParentStack(), parent_info->GetStack()); parent_info->Untrack(); child_info->Untrack(); } TEST(CordzInfoTest, MaybeTrackChildCordWithoutSamplingChildSampled) { CordzSamplingIntervalHelper sample_none(99999); TestCordData parent, child; CordzInfo::TrackCord(child.data, kTrackCordMethod, 1); CordzInfo::MaybeTrackCord(child.data, parent.data, kTrackCordMethod); EXPECT_THAT(child.data.cordz_info(), Eq(nullptr)); } TEST(CordzInfoTest, MaybeTrackChildCordWithSamplingChildSampled) { CordzSamplingIntervalHelper sample_all(1); TestCordData parent, child; CordzInfo::TrackCord(child.data, kTrackCordMethod, 1); CordzInfo::MaybeTrackCord(child.data, parent.data, kTrackCordMethod); EXPECT_THAT(child.data.cordz_info(), Eq(nullptr)); } TEST(CordzInfoTest, UntrackCord) { TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); info->Untrack(); EXPECT_THAT(DeleteQueue(), SizeIs(0u)); } TEST(CordzInfoTest, UntrackCordWithSnapshot) { TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); CordzSnapshot snapshot; info->Untrack(); EXPECT_THAT(CordzInfo::Head(CordzSnapshot()), Eq(nullptr)); EXPECT_THAT(info->GetCordRepForTesting(), Eq(data.rep.rep)); EXPECT_THAT(DeleteQueue(), ElementsAre(info, &snapshot)); } TEST(CordzInfoTest, SetCordRep) { TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); TestCordRep rep; info->Lock(CordzUpdateTracker::kAppendCord); info->SetCordRep(rep.rep); info->Unlock(); EXPECT_THAT(info->GetCordRepForTesting(), Eq(rep.rep)); info->Untrack(); } TEST(CordzInfoTest, SetCordRepNullUntracksCordOnUnlock) { TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); info->Lock(CordzUpdateTracker::kAppendString); info->SetCordRep(nullptr); EXPECT_THAT(info->GetCordRepForTesting(), Eq(nullptr)); EXPECT_THAT(CordzInfo::Head(CordzSnapshot()), Eq(info)); info->Unlock(); EXPECT_THAT(CordzInfo::Head(CordzSnapshot()), Eq(nullptr)); } TEST(CordzInfoTest, RefCordRep) { TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); size_t refcount = data.rep.rep->refcount.Get(); EXPECT_THAT(info->RefCordRep(), Eq(data.rep.rep)); EXPECT_THAT(data.rep.rep->refcount.Get(), Eq(refcount + 1)); CordRep::Unref(data.rep.rep); info->Untrack(); } #if GTEST_HAS_DEATH_TEST TEST(CordzInfoTest, SetCordRepRequiresMutex) { TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); TestCordRep rep; EXPECT_DEBUG_DEATH(info->SetCordRep(rep.rep), ".*"); info->Untrack(); } #endif // GTEST_HAS_DEATH_TEST TEST(CordzInfoTest, TrackUntrackHeadFirstV2) { CordzSnapshot snapshot; EXPECT_THAT(CordzInfo::Head(snapshot), Eq(nullptr)); TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info1 = data.data.cordz_info(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info1)); EXPECT_THAT(info1->Next(snapshot), Eq(nullptr)); TestCordData data2; CordzInfo::TrackCord(data2.data, kTrackCordMethod, 1); CordzInfo* info2 = data2.data.cordz_info(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info2)); EXPECT_THAT(info2->Next(snapshot), Eq(info1)); EXPECT_THAT(info1->Next(snapshot), Eq(nullptr)); info2->Untrack(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info1)); EXPECT_THAT(info1->Next(snapshot), Eq(nullptr)); info1->Untrack(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(nullptr)); } TEST(CordzInfoTest, TrackUntrackTailFirstV2) { CordzSnapshot snapshot; EXPECT_THAT(CordzInfo::Head(snapshot), Eq(nullptr)); TestCordData data; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info1 = data.data.cordz_info(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info1)); EXPECT_THAT(info1->Next(snapshot), Eq(nullptr)); TestCordData data2; CordzInfo::TrackCord(data2.data, kTrackCordMethod, 1); CordzInfo* info2 = data2.data.cordz_info(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info2)); EXPECT_THAT(info2->Next(snapshot), Eq(info1)); EXPECT_THAT(info1->Next(snapshot), Eq(nullptr)); info1->Untrack(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info2)); EXPECT_THAT(info2->Next(snapshot), Eq(nullptr)); info2->Untrack(); ASSERT_THAT(CordzInfo::Head(snapshot), Eq(nullptr)); } TEST(CordzInfoTest, StackV2) { TestCordData data; // kMaxStackDepth is intentionally less than 64 (which is the max depth that // Cordz will record) because if the actual stack depth is over 64 // (which it is on Apple platforms) then the expected_stack will end up // catching a few frames at the end that the actual_stack didn't get and // it will no longer be subset. At the time of this writing 58 is the max // that will allow this test to pass (with a minimum os version of iOS 9), so // rounded down to 50 to hopefully not run into this in the future if Apple // makes small modifications to its testing stack. 50 is sufficient to prove // that we got a decent stack. static constexpr int kMaxStackDepth = 50; CordzInfo::TrackCord(data.data, kTrackCordMethod, 1); CordzInfo* info = data.data.cordz_info(); std::vector local_stack; local_stack.resize(kMaxStackDepth); // In some environments we don't get stack traces. For example in Android // absl::GetStackTrace will return 0 indicating it didn't find any stack. The // resultant formatted stack will be "", but that still equals the stack // recorded in CordzInfo, which is also empty. The skip_count is 1 so that the // line number of the current stack isn't included in the HasSubstr check. local_stack.resize(static_cast( absl::GetStackTrace(local_stack.data(), kMaxStackDepth, /*skip_count=*/1))); std::string got_stack = FormatStack(info->GetStack()); std::string expected_stack = FormatStack(local_stack); // If TrackCord is inlined, got_stack should match expected_stack. If it isn't // inlined, got_stack should include an additional frame not present in // expected_stack. Either way, expected_stack should be a substring of // got_stack. EXPECT_THAT(got_stack, HasSubstr(expected_stack)); info->Untrack(); } // Local helper functions to get different stacks for child and parent. CordzInfo* TrackChildCord(InlineData& data, const InlineData& parent) { CordzInfo::TrackCord(data, parent, kChildMethod); return data.cordz_info(); } CordzInfo* TrackParentCord(InlineData& data) { CordzInfo::TrackCord(data, kTrackCordMethod, 1); return data.cordz_info(); } TEST(CordzInfoTest, GetStatistics) { TestCordData data; CordzInfo* info = TrackParentCord(data.data); CordzStatistics statistics = info->GetCordzStatistics(); EXPECT_THAT(statistics.size, Eq(data.rep.rep->length)); EXPECT_THAT(statistics.method, Eq(kTrackCordMethod)); EXPECT_THAT(statistics.parent_method, Eq(kUnknownMethod)); EXPECT_THAT(statistics.update_tracker.Value(kTrackCordMethod), Eq(1)); info->Untrack(); } TEST(CordzInfoTest, LockCountsMethod) { TestCordData data; CordzInfo* info = TrackParentCord(data.data); info->Lock(kUpdateMethod); info->Unlock(); info->Lock(kUpdateMethod); info->Unlock(); CordzStatistics statistics = info->GetCordzStatistics(); EXPECT_THAT(statistics.update_tracker.Value(kUpdateMethod), Eq(2)); info->Untrack(); } TEST(CordzInfoTest, FromParent) { TestCordData parent; TestCordData child; CordzInfo* info_parent = TrackParentCord(parent.data); CordzInfo* info_child = TrackChildCord(child.data, parent.data); std::string stack = FormatStack(info_parent->GetStack()); std::string parent_stack = FormatStack(info_child->GetParentStack()); EXPECT_THAT(stack, Eq(parent_stack)); CordzStatistics statistics = info_child->GetCordzStatistics(); EXPECT_THAT(statistics.size, Eq(child.rep.rep->length)); EXPECT_THAT(statistics.method, Eq(kChildMethod)); EXPECT_THAT(statistics.parent_method, Eq(kTrackCordMethod)); EXPECT_THAT(statistics.update_tracker.Value(kChildMethod), Eq(1)); info_parent->Untrack(); info_child->Untrack(); } } // namespace } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl