Home | History | Annotate | Line # | Download | only in unit
      1 //===-- profile_collector_test.cc -----------------------------------------===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 // This file is a part of XRay, a function call tracing system.
     11 //
     12 //===----------------------------------------------------------------------===//
     13 #include "gtest/gtest.h"
     14 
     15 #include "xray_profile_collector.h"
     16 #include "xray_profiling_flags.h"
     17 #include <cstdint>
     18 #include <cstring>
     19 #include <memory>
     20 #include <thread>
     21 #include <utility>
     22 #include <vector>
     23 
     24 namespace __xray {
     25 namespace {
     26 
     27 static constexpr auto kHeaderSize = 16u;
     28 
     29 constexpr uptr ExpectedProfilingVersion = 0x20180424;
     30 
     31 struct ExpectedProfilingFileHeader {
     32   const u64 MagicBytes = 0x7872617970726f66; // Identifier for XRay profiling
     33                                              // files 'xrayprof' in hex.
     34   const u64 Version = ExpectedProfilingVersion;
     35   u64 Timestamp = 0;
     36   u64 PID = 0;
     37 };
     38 
     39 void ValidateFileHeaderBlock(XRayBuffer B) {
     40   ASSERT_NE(static_cast<const void *>(B.Data), nullptr);
     41   ASSERT_EQ(B.Size, sizeof(ExpectedProfilingFileHeader));
     42   typename std::aligned_storage<sizeof(ExpectedProfilingFileHeader)>::type
     43       FileHeaderStorage;
     44   ExpectedProfilingFileHeader ExpectedHeader;
     45   std::memcpy(&FileHeaderStorage, B.Data, B.Size);
     46   auto &FileHeader =
     47       *reinterpret_cast<ExpectedProfilingFileHeader *>(&FileHeaderStorage);
     48   ASSERT_EQ(ExpectedHeader.MagicBytes, FileHeader.MagicBytes);
     49   ASSERT_EQ(ExpectedHeader.Version, FileHeader.Version);
     50 }
     51 
     52 void ValidateBlock(XRayBuffer B) {
     53   profilingFlags()->setDefaults();
     54   ASSERT_NE(static_cast<const void *>(B.Data), nullptr);
     55   ASSERT_NE(B.Size, 0u);
     56   ASSERT_GE(B.Size, kHeaderSize);
     57   // We look at the block size, the block number, and the thread ID to ensure
     58   // that none of them are zero (or that the header data is laid out as we
     59   // expect).
     60   char LocalBuffer[kHeaderSize] = {};
     61   internal_memcpy(LocalBuffer, B.Data, kHeaderSize);
     62   u32 BlockSize = 0;
     63   u32 BlockNumber = 0;
     64   u64 ThreadId = 0;
     65   internal_memcpy(&BlockSize, LocalBuffer, sizeof(u32));
     66   internal_memcpy(&BlockNumber, LocalBuffer + sizeof(u32), sizeof(u32));
     67   internal_memcpy(&ThreadId, LocalBuffer + (2 * sizeof(u32)), sizeof(u64));
     68   ASSERT_NE(BlockSize, 0u);
     69   ASSERT_GE(BlockNumber, 0u);
     70   ASSERT_NE(ThreadId, 0u);
     71 }
     72 
     73 std::tuple<u32, u32, u64> ParseBlockHeader(XRayBuffer B) {
     74   char LocalBuffer[kHeaderSize] = {};
     75   internal_memcpy(LocalBuffer, B.Data, kHeaderSize);
     76   u32 BlockSize = 0;
     77   u32 BlockNumber = 0;
     78   u64 ThreadId = 0;
     79   internal_memcpy(&BlockSize, LocalBuffer, sizeof(u32));
     80   internal_memcpy(&BlockNumber, LocalBuffer + sizeof(u32), sizeof(u32));
     81   internal_memcpy(&ThreadId, LocalBuffer + (2 * sizeof(u32)), sizeof(u64));
     82   return std::make_tuple(BlockSize, BlockNumber, ThreadId);
     83 }
     84 
     85 struct Profile {
     86   int64_t CallCount;
     87   int64_t CumulativeLocalTime;
     88   std::vector<int32_t> Path;
     89 };
     90 
     91 std::tuple<Profile, const char *> ParseProfile(const char *P) {
     92   Profile Result;
     93   // Read the path first, until we find a sentinel 0.
     94   int32_t F;
     95   do {
     96     internal_memcpy(&F, P, sizeof(int32_t));
     97     P += sizeof(int32_t);
     98     Result.Path.push_back(F);
     99   } while (F != 0);
    100 
    101   // Then read the CallCount.
    102   internal_memcpy(&Result.CallCount, P, sizeof(int64_t));
    103   P += sizeof(int64_t);
    104 
    105   // Then read the CumulativeLocalTime.
    106   internal_memcpy(&Result.CumulativeLocalTime, P, sizeof(int64_t));
    107   P += sizeof(int64_t);
    108   return std::make_tuple(std::move(Result), P);
    109 }
    110 
    111 TEST(profileCollectorServiceTest, PostSerializeCollect) {
    112   profilingFlags()->setDefaults();
    113   bool Success = false;
    114   BufferQueue BQ(profilingFlags()->per_thread_allocator_max,
    115                  profilingFlags()->buffers_max, Success);
    116   ASSERT_EQ(Success, true);
    117   FunctionCallTrie::Allocators::Buffers Buffers;
    118   ASSERT_EQ(BQ.getBuffer(Buffers.NodeBuffer), BufferQueue::ErrorCode::Ok);
    119   ASSERT_EQ(BQ.getBuffer(Buffers.RootsBuffer), BufferQueue::ErrorCode::Ok);
    120   ASSERT_EQ(BQ.getBuffer(Buffers.ShadowStackBuffer),
    121             BufferQueue::ErrorCode::Ok);
    122   ASSERT_EQ(BQ.getBuffer(Buffers.NodeIdPairBuffer), BufferQueue::ErrorCode::Ok);
    123   auto Allocators = FunctionCallTrie::InitAllocatorsFromBuffers(Buffers);
    124   FunctionCallTrie T(Allocators);
    125 
    126   // Populate the trie with some data.
    127   T.enterFunction(1, 1, 0);
    128   T.enterFunction(2, 2, 0);
    129   T.exitFunction(2, 3, 0);
    130   T.exitFunction(1, 4, 0);
    131 
    132   // Reset the collector data structures.
    133   profileCollectorService::reset();
    134 
    135   // Then we post the data to the global profile collector service.
    136   profileCollectorService::post(&BQ, std::move(T), std::move(Allocators),
    137                                 std::move(Buffers), 1);
    138 
    139   // Then we serialize the data.
    140   profileCollectorService::serialize();
    141 
    142   // Then we go through two buffers to see whether we're getting the data we
    143   // expect. The first block must always be as large as a file header, which
    144   // will have a fixed size.
    145   auto B = profileCollectorService::nextBuffer({nullptr, 0});
    146   ValidateFileHeaderBlock(B);
    147 
    148   B = profileCollectorService::nextBuffer(B);
    149   ValidateBlock(B);
    150   u32 BlockSize;
    151   u32 BlockNum;
    152   u64 ThreadId;
    153   std::tie(BlockSize, BlockNum, ThreadId) = ParseBlockHeader(B);
    154 
    155   // We look at the serialized buffer to see whether the Trie we're expecting
    156   // to see is there.
    157   auto DStart = static_cast<const char *>(B.Data) + kHeaderSize;
    158   std::vector<char> D(DStart, DStart + BlockSize);
    159   B = profileCollectorService::nextBuffer(B);
    160   ASSERT_EQ(B.Data, nullptr);
    161   ASSERT_EQ(B.Size, 0u);
    162 
    163   Profile Profile1, Profile2;
    164   auto P = static_cast<const char *>(D.data());
    165   std::tie(Profile1, P) = ParseProfile(P);
    166   std::tie(Profile2, P) = ParseProfile(P);
    167 
    168   ASSERT_NE(Profile1.Path.size(), Profile2.Path.size());
    169   auto &P1 = Profile1.Path.size() < Profile2.Path.size() ? Profile2 : Profile1;
    170   auto &P2 = Profile1.Path.size() < Profile2.Path.size() ? Profile1 : Profile2;
    171   std::vector<int32_t> P1Expected = {2, 1, 0};
    172   std::vector<int32_t> P2Expected = {1, 0};
    173   ASSERT_EQ(P1.Path.size(), P1Expected.size());
    174   ASSERT_EQ(P2.Path.size(), P2Expected.size());
    175   ASSERT_EQ(P1.Path, P1Expected);
    176   ASSERT_EQ(P2.Path, P2Expected);
    177 }
    178 
    179 // We break out a function that will be run in multiple threads, one that will
    180 // use a thread local allocator, and will post the FunctionCallTrie to the
    181 // profileCollectorService. This simulates what the threads being profiled would
    182 // be doing anyway, but through the XRay logging implementation.
    183 void threadProcessing() {
    184   static bool Success = false;
    185   static BufferQueue BQ(profilingFlags()->per_thread_allocator_max,
    186                         profilingFlags()->buffers_max, Success);
    187   thread_local FunctionCallTrie::Allocators::Buffers Buffers = [] {
    188     FunctionCallTrie::Allocators::Buffers B;
    189     BQ.getBuffer(B.NodeBuffer);
    190     BQ.getBuffer(B.RootsBuffer);
    191     BQ.getBuffer(B.ShadowStackBuffer);
    192     BQ.getBuffer(B.NodeIdPairBuffer);
    193     return B;
    194   }();
    195 
    196   thread_local auto Allocators =
    197       FunctionCallTrie::InitAllocatorsFromBuffers(Buffers);
    198 
    199   FunctionCallTrie T(Allocators);
    200 
    201   T.enterFunction(1, 1, 0);
    202   T.enterFunction(2, 2, 0);
    203   T.exitFunction(2, 3, 0);
    204   T.exitFunction(1, 4, 0);
    205 
    206   profileCollectorService::post(&BQ, std::move(T), std::move(Allocators),
    207                                 std::move(Buffers), GetTid());
    208 }
    209 
    210 TEST(profileCollectorServiceTest, PostSerializeCollectMultipleThread) {
    211   profilingFlags()->setDefaults();
    212 
    213   profileCollectorService::reset();
    214 
    215   std::thread t1(threadProcessing);
    216   std::thread t2(threadProcessing);
    217 
    218   t1.join();
    219   t2.join();
    220 
    221   // At this point, t1 and t2 are already done with what they were doing.
    222   profileCollectorService::serialize();
    223 
    224   // Ensure that we see two buffers.
    225   auto B = profileCollectorService::nextBuffer({nullptr, 0});
    226   ValidateFileHeaderBlock(B);
    227 
    228   B = profileCollectorService::nextBuffer(B);
    229   ValidateBlock(B);
    230 
    231   B = profileCollectorService::nextBuffer(B);
    232   ValidateBlock(B);
    233 }
    234 
    235 } // namespace
    236 } // namespace __xray
    237