| #include "../RootAutoDetector.h" |
| #include "sanitizer_common/sanitizer_array_ref.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| using namespace __ctx_profile; |
| using ::testing::IsEmpty; |
| using ::testing::Not; |
| using ::testing::SizeIs; |
| |
| // Utility for describing a preorder traversal. By default it captures the |
| // address and count at a callsite node. Implicitly nodes are expected to have 1 |
| // child. If they have none, we place a Marker::term and if they have more than |
| // one, we place a Marker::split(nr_of_children) For example, using a list |
| // notation, and letters to denote a pair of address and count: |
| // (A (B C) (D (E F))) is a list of markers: A, split(2), B, term, C, |
| // term, D, split(2), E, term, F, term |
| class Marker { |
| enum class Kind { End, Value, Split }; |
| const uptr Value; |
| const uptr Count; |
| const Kind K; |
| Marker(uptr V, uptr C, Kind S) : Value(V), Count(C), K(S) {} |
| |
| public: |
| Marker(uptr V, uptr C) : Marker(V, C, Kind::Value) {} |
| |
| static Marker split(uptr V) { return Marker(V, 0, Kind::Split); } |
| static Marker term() { return Marker(0, 0, Kind::End); } |
| |
| bool isSplit() const { return K == Kind::Split; } |
| bool isTerm() const { return K == Kind::End; } |
| bool isVal() const { return K == Kind::Value; } |
| |
| bool operator==(const Marker &M) const { |
| return Value == M.Value && Count == M.Count && K == M.K; |
| } |
| }; |
| |
| class MockCallsiteTrie final : public PerThreadCallsiteTrie { |
| // Return the first multiple of 100. |
| uptr getFctStartAddr(uptr CallsiteAddress) const override { |
| return (CallsiteAddress / 100) * 100; |
| } |
| |
| static void popAndCheck(ArrayRef<Marker> &Preorder, Marker M) { |
| ASSERT_THAT(Preorder, Not(IsEmpty())); |
| ASSERT_EQ(Preorder[0], M); |
| Preorder = Preorder.drop_front(); |
| } |
| |
| static void checkSameImpl(const Trie &T, ArrayRef<Marker> &Preorder) { |
| popAndCheck(Preorder, {T.CallsiteAddress, T.Count}); |
| |
| if (T.Children.empty()) { |
| popAndCheck(Preorder, Marker::term()); |
| return; |
| } |
| |
| if (T.Children.size() > 1) |
| popAndCheck(Preorder, Marker::split(T.Children.size())); |
| |
| T.Children.forEach([&](const auto &KVP) { |
| checkSameImpl(KVP.second, Preorder); |
| return true; |
| }); |
| } |
| |
| public: |
| void checkSame(ArrayRef<Marker> Preorder) const { |
| checkSameImpl(TheTrie, Preorder); |
| ASSERT_THAT(Preorder, IsEmpty()); |
| } |
| }; |
| |
| TEST(PerThreadCallsiteTrieTest, Insert) { |
| MockCallsiteTrie R; |
| uptr Stack1[]{4, 3, 2, 1}; |
| R.insertStack(StackTrace(Stack1, 4)); |
| R.checkSame(ArrayRef<Marker>( |
| {{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, Marker::term()})); |
| |
| uptr Stack2[]{5, 4, 3, 2, 1}; |
| R.insertStack(StackTrace(Stack2, 5)); |
| R.checkSame(ArrayRef<Marker>( |
| {{0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}, {5, 1}, Marker::term()})); |
| |
| uptr Stack3[]{6, 3, 2, 1}; |
| R.insertStack(StackTrace(Stack3, 4)); |
| R.checkSame(ArrayRef<Marker>({{0, 3}, |
| {1, 3}, |
| {2, 3}, |
| {3, 3}, |
| Marker::split(2), |
| {4, 2}, |
| {5, 1}, |
| Marker::term(), |
| {6, 1}, |
| Marker::term()})); |
| uptr Stack4[]{7, 2, 1}; |
| R.insertStack(StackTrace(Stack4, 3)); |
| R.checkSame(ArrayRef<Marker>({{0, 4}, |
| {1, 4}, |
| {2, 4}, |
| Marker::split(2), |
| {7, 1}, |
| Marker::term(), |
| {3, 3}, |
| Marker::split(2), |
| {4, 2}, |
| {5, 1}, |
| Marker::term(), |
| {6, 1}, |
| Marker::term()})); |
| } |
| |
| TEST(PerThreadCallsiteTrieTest, DetectRoots) { |
| MockCallsiteTrie T; |
| |
| uptr Stack1[]{501, 302, 202, 102}; |
| uptr Stack2[]{601, 402, 203, 102}; |
| T.insertStack({Stack1, 4}); |
| T.insertStack({Stack2, 4}); |
| |
| auto R = T.determineRoots(); |
| EXPECT_THAT(R, SizeIs(2U)); |
| EXPECT_TRUE(R.contains(300)); |
| EXPECT_TRUE(R.contains(400)); |
| } |
| |
| TEST(PerThreadCallsiteTrieTest, DetectRootsNoBranches) { |
| MockCallsiteTrie T; |
| |
| uptr Stack1[]{501, 302, 202, 102}; |
| T.insertStack({Stack1, 4}); |
| |
| auto R = T.determineRoots(); |
| EXPECT_THAT(R, IsEmpty()); |
| } |
| |
| TEST(PerThreadCallsiteTrieTest, DetectRootsUnknownFct) { |
| MockCallsiteTrie T; |
| |
| uptr Stack1[]{501, 302, 202, 102}; |
| // The MockCallsiteTree address resolver resolves addresses over 100, so 40 |
| // will be mapped to 0. |
| uptr Stack2[]{601, 40, 203, 102}; |
| T.insertStack({Stack1, 4}); |
| T.insertStack({Stack2, 4}); |
| |
| auto R = T.determineRoots(); |
| ASSERT_THAT(R, SizeIs(2U)); |
| EXPECT_TRUE(R.contains(300)); |
| EXPECT_TRUE(R.contains(0)); |
| } |