1 //===- llvm/unittest/XRay/FDRProducerConsumerTest.cpp -----------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Test for round-trip record writing and reading.
10 //
11 //===----------------------------------------------------------------------===//
12 #include "llvm/Support/DataExtractor.h"
13 #include "llvm/Support/raw_ostream.h"
14 #include "llvm/XRay/FDRLogBuilder.h"
15 #include "llvm/XRay/FDRRecordConsumer.h"
16 #include "llvm/XRay/FDRRecordProducer.h"
17 #include "llvm/XRay/FDRRecords.h"
18 #include "llvm/XRay/FDRTraceWriter.h"
19 #include "llvm/XRay/FileHeaderReader.h"
20 #include "gmock/gmock.h"
21 #include "gtest/gtest.h"
22 #include <string>
23 #include <tuple>
24 
25 namespace llvm {
26 namespace xray {
27 namespace {
28 
29 using ::testing::Eq;
30 using ::testing::IsEmpty;
31 using ::testing::Not;
32 using ::testing::SizeIs;
33 
34 template <class RecordType> std::unique_ptr<Record> MakeRecord();
35 
MakeRecord()36 template <> std::unique_ptr<Record> MakeRecord<NewBufferRecord>() {
37   return std::make_unique<NewBufferRecord>(1);
38 }
39 
MakeRecord()40 template <> std::unique_ptr<Record> MakeRecord<NewCPUIDRecord>() {
41   return std::make_unique<NewCPUIDRecord>(1, 2);
42 }
43 
MakeRecord()44 template <> std::unique_ptr<Record> MakeRecord<TSCWrapRecord>() {
45   return std::make_unique<TSCWrapRecord>(1);
46 }
47 
MakeRecord()48 template <> std::unique_ptr<Record> MakeRecord<WallclockRecord>() {
49   return std::make_unique<WallclockRecord>(1, 2);
50 }
51 
MakeRecord()52 template <> std::unique_ptr<Record> MakeRecord<CustomEventRecord>() {
53   return std::make_unique<CustomEventRecord>(4, 1, 2, "data");
54 }
55 
MakeRecord()56 template <> std::unique_ptr<Record> MakeRecord<CallArgRecord>() {
57   return std::make_unique<CallArgRecord>(1);
58 }
59 
MakeRecord()60 template <> std::unique_ptr<Record> MakeRecord<PIDRecord>() {
61   return std::make_unique<PIDRecord>(1);
62 }
63 
MakeRecord()64 template <> std::unique_ptr<Record> MakeRecord<FunctionRecord>() {
65   return std::make_unique<FunctionRecord>(RecordTypes::ENTER, 1, 2);
66 }
67 
MakeRecord()68 template <> std::unique_ptr<Record> MakeRecord<CustomEventRecordV5>() {
69   return std::make_unique<CustomEventRecordV5>(4, 1, "data");
70 }
71 
MakeRecord()72 template <> std::unique_ptr<Record> MakeRecord<TypedEventRecord>() {
73   return std::make_unique<TypedEventRecord>(4, 1, 2, "data");
74 }
75 
76 template <class T> class RoundTripTest : public ::testing::Test {
77 public:
RoundTripTest()78   RoundTripTest() : Data(), OS(Data) {
79     H.Version = 4;
80     H.Type = 1;
81     H.ConstantTSC = true;
82     H.NonstopTSC = true;
83     H.CycleFrequency = 3e9;
84 
85     Writer = std::make_unique<FDRTraceWriter>(OS, H);
86     Rec = MakeRecord<T>();
87   }
88 
89 protected:
90   std::string Data;
91   raw_string_ostream OS;
92   XRayFileHeader H;
93   std::unique_ptr<FDRTraceWriter> Writer;
94   std::unique_ptr<Record> Rec;
95 };
96 
97 TYPED_TEST_CASE_P(RoundTripTest);
98 
99 template <class T> class RoundTripTestV5 : public ::testing::Test {
100 public:
RoundTripTestV5()101   RoundTripTestV5() : Data(), OS(Data) {
102     H.Version = 5;
103     H.Type = 1;
104     H.ConstantTSC = true;
105     H.NonstopTSC = true;
106     H.CycleFrequency = 3e9;
107 
108     Writer = std::make_unique<FDRTraceWriter>(OS, H);
109     Rec = MakeRecord<T>();
110   }
111 
112 protected:
113   std::string Data;
114   raw_string_ostream OS;
115   XRayFileHeader H;
116   std::unique_ptr<FDRTraceWriter> Writer;
117   std::unique_ptr<Record> Rec;
118 };
119 
120 TYPED_TEST_CASE_P(RoundTripTestV5);
121 
122 // This test ensures that the writing and reading implementations are in sync --
123 // that given write(read(write(R))) == R.
TYPED_TEST_P(RoundTripTest,RoundTripsSingleValue)124 TYPED_TEST_P(RoundTripTest, RoundTripsSingleValue) {
125   // Always write a buffer extents record which will cover the correct size of
126   // the record, for version 3 and up.
127   BufferExtents BE(200);
128   ASSERT_FALSE(errorToBool(BE.apply(*this->Writer)));
129   auto &R = this->Rec;
130   ASSERT_FALSE(errorToBool(R->apply(*this->Writer)));
131   this->OS.flush();
132 
133   DataExtractor DE(this->Data, sys::IsLittleEndianHost, 8);
134   uint64_t OffsetPtr = 0;
135   auto HeaderOrErr = readBinaryFormatHeader(DE, OffsetPtr);
136   if (!HeaderOrErr)
137     FAIL() << HeaderOrErr.takeError();
138 
139   FileBasedRecordProducer P(HeaderOrErr.get(), DE, OffsetPtr);
140   std::vector<std::unique_ptr<Record>> Records;
141   LogBuilderConsumer C(Records);
142   while (DE.isValidOffsetForDataOfSize(OffsetPtr, 1)) {
143     auto R = P.produce();
144     if (!R)
145       FAIL() << R.takeError();
146     if (auto E = C.consume(std::move(R.get())))
147       FAIL() << E;
148   }
149   ASSERT_THAT(Records, Not(IsEmpty()));
150   std::string Data2;
151   raw_string_ostream OS2(Data2);
152   FDRTraceWriter Writer2(OS2, this->H);
153   for (auto &P : Records)
154     ASSERT_FALSE(errorToBool(P->apply(Writer2)));
155   OS2.flush();
156 
157   EXPECT_EQ(Data2.substr(sizeof(XRayFileHeader)),
158             this->Data.substr(sizeof(XRayFileHeader)));
159   ASSERT_THAT(Records, SizeIs(2));
160   EXPECT_THAT(Records[1]->getRecordType(), Eq(R->getRecordType()));
161 }
162 
163 REGISTER_TYPED_TEST_CASE_P(RoundTripTest, RoundTripsSingleValue);
164 
165 // We duplicate the above case for the V5 version using different types and
166 // encodings.
TYPED_TEST_P(RoundTripTestV5,RoundTripsSingleValue)167 TYPED_TEST_P(RoundTripTestV5, RoundTripsSingleValue) {
168   BufferExtents BE(200);
169   ASSERT_FALSE(errorToBool(BE.apply(*this->Writer)));
170   auto &R = this->Rec;
171   ASSERT_FALSE(errorToBool(R->apply(*this->Writer)));
172   this->OS.flush();
173 
174   DataExtractor DE(this->Data, sys::IsLittleEndianHost, 8);
175   uint64_t OffsetPtr = 0;
176   auto HeaderOrErr = readBinaryFormatHeader(DE, OffsetPtr);
177   if (!HeaderOrErr)
178     FAIL() << HeaderOrErr.takeError();
179 
180   FileBasedRecordProducer P(HeaderOrErr.get(), DE, OffsetPtr);
181   std::vector<std::unique_ptr<Record>> Records;
182   LogBuilderConsumer C(Records);
183   while (DE.isValidOffsetForDataOfSize(OffsetPtr, 1)) {
184     auto R = P.produce();
185     if (!R)
186       FAIL() << R.takeError();
187     if (auto E = C.consume(std::move(R.get())))
188       FAIL() << E;
189   }
190   ASSERT_THAT(Records, Not(IsEmpty()));
191   std::string Data2;
192   raw_string_ostream OS2(Data2);
193   FDRTraceWriter Writer2(OS2, this->H);
194   for (auto &P : Records)
195     ASSERT_FALSE(errorToBool(P->apply(Writer2)));
196   OS2.flush();
197 
198   EXPECT_EQ(Data2.substr(sizeof(XRayFileHeader)),
199             this->Data.substr(sizeof(XRayFileHeader)));
200   ASSERT_THAT(Records, SizeIs(2));
201   EXPECT_THAT(Records[1]->getRecordType(), Eq(R->getRecordType()));
202 }
203 
204 REGISTER_TYPED_TEST_CASE_P(RoundTripTestV5, RoundTripsSingleValue);
205 
206 // These are the record types we support for v4 and below.
207 using RecordTypes =
208     ::testing::Types<NewBufferRecord, NewCPUIDRecord, TSCWrapRecord,
209                      WallclockRecord, CustomEventRecord, CallArgRecord,
210                      PIDRecord, FunctionRecord>;
211 INSTANTIATE_TYPED_TEST_CASE_P(Records, RoundTripTest, RecordTypes);
212 
213 // For V5, we have two new types we're supporting.
214 using RecordTypesV5 =
215     ::testing::Types<NewBufferRecord, NewCPUIDRecord, TSCWrapRecord,
216                      WallclockRecord, CustomEventRecordV5, TypedEventRecord,
217                      CallArgRecord, PIDRecord, FunctionRecord>;
218 INSTANTIATE_TYPED_TEST_CASE_P(Records, RoundTripTestV5, RecordTypesV5);
219 
220 } // namespace
221 } // namespace xray
222 } // namespace llvm
223