| //===- ProfileTest.cpp - XRay Profile unit tests ----------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| #include "llvm/XRay/Profile.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| #include <numeric> |
| |
| namespace llvm { |
| namespace xray { |
| namespace { |
| |
| using ::testing::AllOf; |
| using ::testing::ElementsAre; |
| using ::testing::Eq; |
| using ::testing::Field; |
| using ::testing::Not; |
| using ::testing::Pair; |
| using ::testing::UnorderedElementsAre; |
| |
| TEST(ProfileTest, CreateProfile) { Profile P; } |
| |
| TEST(ProfileTest, InternPath) { |
| Profile P; |
| auto Path0 = P.internPath({3, 2, 1}); |
| auto Path1 = P.internPath({3, 2, 1}); |
| auto Path2 = P.internPath({2, 1}); |
| EXPECT_THAT(Path0, Eq(Path1)); |
| EXPECT_THAT(Path0, Not(Eq(Path2))); |
| } |
| |
| TEST(ProfileTest, ExpandPath) { |
| Profile P; |
| auto PathID = P.internPath({3, 2, 1}); |
| auto PathOrError = P.expandPath(PathID); |
| if (!PathOrError) |
| FAIL() << "Error: " << PathOrError.takeError(); |
| EXPECT_THAT(PathOrError.get(), ElementsAre(3, 2, 1)); |
| } |
| |
| TEST(ProfileTest, AddBlocks) { |
| Profile P; |
| // Expect an error on adding empty blocks. |
| EXPECT_TRUE(errorToBool(P.addBlock({}))); |
| |
| // Thread blocks may not be empty. |
| EXPECT_TRUE(errorToBool(P.addBlock({1, {}}))); |
| |
| // Thread blocks with data must succeed. |
| EXPECT_FALSE(errorToBool(P.addBlock( |
| Profile::Block{Profile::ThreadID{1}, |
| { |
| {P.internPath({2, 1}), Profile::Data{1, 1000}}, |
| {P.internPath({3, 2, 1}), Profile::Data{10, 100}}, |
| }}))); |
| } |
| |
| TEST(ProfileTest, CopyProfile) { |
| Profile P0, P1; |
| EXPECT_FALSE(errorToBool(P0.addBlock( |
| Profile::Block{Profile::ThreadID{1}, |
| { |
| {P0.internPath({2, 1}), Profile::Data{1, 1000}}, |
| {P0.internPath({3, 2, 1}), Profile::Data{10, 100}}, |
| }}))); |
| P1 = P0; |
| EXPECT_THAT( |
| P1, UnorderedElementsAre(AllOf( |
| Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
| Field(&Profile::Block::PathData, |
| UnorderedElementsAre( |
| Pair(P1.internPath({2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(1000u)))), |
| Pair(P1.internPath({3, 2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(10u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(100u))))))))); |
| } |
| |
| TEST(ProfileTest, MoveProfile) { |
| Profile P0, P1; |
| EXPECT_FALSE(errorToBool(P0.addBlock( |
| Profile::Block{Profile::ThreadID{1}, |
| { |
| {P0.internPath({2, 1}), Profile::Data{1, 1000}}, |
| {P0.internPath({3, 2, 1}), Profile::Data{10, 100}}, |
| }}))); |
| P1 = std::move(P0); |
| EXPECT_THAT( |
| P1, UnorderedElementsAre(AllOf( |
| Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
| Field(&Profile::Block::PathData, |
| UnorderedElementsAre( |
| Pair(P1.internPath({2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(1000u)))), |
| Pair(P1.internPath({3, 2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(10u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(100u))))))))); |
| EXPECT_THAT(P0, UnorderedElementsAre()); |
| } |
| |
| TEST(ProfileTest, MergeProfilesByThread) { |
| Profile P0, P1; |
| |
| // Set up the blocks for two different threads in P0. |
| EXPECT_FALSE(errorToBool(P0.addBlock( |
| Profile::Block{Profile::ThreadID{1}, |
| {{P0.internPath({2, 1}), Profile::Data{1, 1000}}, |
| {P0.internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
| EXPECT_FALSE(errorToBool(P0.addBlock( |
| Profile::Block{Profile::ThreadID{2}, |
| {{P0.internPath({3, 1}), Profile::Data{1, 1000}}}}))); |
| |
| // Set up the blocks for two different threads in P1. |
| EXPECT_FALSE(errorToBool(P1.addBlock( |
| Profile::Block{Profile::ThreadID{1}, |
| {{P1.internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
| EXPECT_FALSE(errorToBool(P1.addBlock( |
| Profile::Block{Profile::ThreadID{2}, |
| {{P1.internPath({3, 1}), Profile::Data{1, 1000}}, |
| {P1.internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
| |
| Profile Merged = mergeProfilesByThread(P0, P1); |
| EXPECT_THAT( |
| Merged, |
| UnorderedElementsAre( |
| // We want to see two threads after the merge. |
| AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
| Field(&Profile::Block::PathData, |
| UnorderedElementsAre( |
| Pair(Merged.internPath({2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(2000u)))), |
| Pair(Merged.internPath({4, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(1000u))))))), |
| AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{2})), |
| Field(&Profile::Block::PathData, |
| UnorderedElementsAre( |
| Pair(Merged.internPath({3, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(2000u)))), |
| Pair(Merged.internPath({4, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(1000u))))))))); |
| } |
| |
| TEST(ProfileTest, MergeProfilesByStack) { |
| Profile P0, P1; |
| EXPECT_FALSE(errorToBool(P0.addBlock( |
| Profile::Block{Profile::ThreadID{1}, |
| {{P0.internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
| EXPECT_FALSE(errorToBool(P1.addBlock( |
| Profile::Block{Profile::ThreadID{2}, |
| {{P1.internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
| |
| Profile Merged = mergeProfilesByStack(P0, P1); |
| EXPECT_THAT(Merged, |
| ElementsAre(AllOf( |
| // We expect that we lose the ThreadID dimension in this |
| // algorithm. |
| Field(&Profile::Block::Thread, Eq(Profile::ThreadID{0})), |
| Field(&Profile::Block::PathData, |
| ElementsAre(Pair( |
| Merged.internPath({2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(2000u))))))))); |
| } |
| |
| TEST(ProfileTest, MergeProfilesByStackAccumulate) { |
| std::vector<Profile> Profiles(3); |
| EXPECT_FALSE(errorToBool(Profiles[0].addBlock(Profile::Block{ |
| Profile::ThreadID{1}, |
| {{Profiles[0].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
| EXPECT_FALSE(errorToBool(Profiles[1].addBlock(Profile::Block{ |
| Profile::ThreadID{2}, |
| {{Profiles[1].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
| EXPECT_FALSE(errorToBool(Profiles[2].addBlock(Profile::Block{ |
| Profile::ThreadID{3}, |
| {{Profiles[2].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
| Profile Merged = std::accumulate(Profiles.begin(), Profiles.end(), Profile(), |
| mergeProfilesByStack); |
| EXPECT_THAT(Merged, |
| ElementsAre(AllOf( |
| // We expect that we lose the ThreadID dimension in this |
| // algorithm. |
| Field(&Profile::Block::Thread, Eq(Profile::ThreadID{0})), |
| Field(&Profile::Block::PathData, |
| ElementsAre(Pair( |
| Merged.internPath({2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(3u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(3000u))))))))); |
| } |
| |
| TEST(ProfileTest, MergeProfilesByThreadAccumulate) { |
| std::vector<Profile> Profiles(2); |
| |
| // Set up the blocks for two different threads in Profiles[0]. |
| EXPECT_FALSE(errorToBool(Profiles[0].addBlock(Profile::Block{ |
| Profile::ThreadID{1}, |
| {{Profiles[0].internPath({2, 1}), Profile::Data{1, 1000}}, |
| {Profiles[0].internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
| EXPECT_FALSE(errorToBool(Profiles[0].addBlock(Profile::Block{ |
| Profile::ThreadID{2}, |
| {{Profiles[0].internPath({3, 1}), Profile::Data{1, 1000}}}}))); |
| |
| // Set up the blocks for two different threads in Profiles[1]. |
| EXPECT_FALSE(errorToBool(Profiles[1].addBlock(Profile::Block{ |
| Profile::ThreadID{1}, |
| {{Profiles[1].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
| EXPECT_FALSE(errorToBool(Profiles[1].addBlock(Profile::Block{ |
| Profile::ThreadID{2}, |
| {{Profiles[1].internPath({3, 1}), Profile::Data{1, 1000}}, |
| {Profiles[1].internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
| |
| Profile Merged = std::accumulate(Profiles.begin(), Profiles.end(), Profile(), |
| mergeProfilesByThread); |
| EXPECT_THAT( |
| Merged, |
| UnorderedElementsAre( |
| // We want to see two threads after the merge. |
| AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
| Field(&Profile::Block::PathData, |
| UnorderedElementsAre( |
| Pair(Merged.internPath({2, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(2000u)))), |
| Pair(Merged.internPath({4, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(1000u))))))), |
| AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{2})), |
| Field(&Profile::Block::PathData, |
| UnorderedElementsAre( |
| Pair(Merged.internPath({3, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(2000u)))), |
| Pair(Merged.internPath({4, 1}), |
| AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
| Field(&Profile::Data::CumulativeLocalTime, |
| Eq(1000u))))))))); |
| } |
| // FIXME: Add a test creating a Trace and generating a Profile |
| // FIXME: Add tests for ranking/sorting profile blocks by dimension |
| |
| } // namespace |
| } // namespace xray |
| } // namespace llvm |