Move folly/Bits.h to folly/lang/
[folly.git] / folly / tracing / test / StaticTracepointTest.cpp
1 /*
2  * Copyright 2017 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <algorithm>
18 #include <array>
19 #include <iterator>
20 #include <sstream>
21 #include <string>
22 #include <vector>
23
24 #include <boost/filesystem.hpp>
25 #include <folly/Conv.h>
26 #include <folly/Format.h>
27 #include <folly/Random.h>
28 #include <folly/String.h>
29 #include <folly/Subprocess.h>
30 #include <folly/lang/Bits.h>
31 #include <folly/portability/GTest.h>
32 #include <folly/portability/Unistd.h>
33 #include <folly/tracing/StaticTracepoint.h>
34
35 static const std::string kUSDTSubsectionName = FOLLY_SDT_NOTE_NAME;
36 static const int kUSDTNoteType = FOLLY_SDT_NOTE_TYPE;
37 static const size_t kAddrWidth = sizeof(void*);
38
39 static uint8_t hexToInt(const std::string& hex) {
40   std::stringstream converter(hex);
41   int value;
42   converter >> std::hex >> value;
43   return uint8_t(value);
44 }
45
46 static int get4BytesValue(const std::vector<uint8_t>& v, size_t& pos) {
47   pos += 4;
48   return folly::Endian::little(folly::loadUnaligned<int>(v.data() + pos - 4));
49 }
50
51 static void align4Bytes(size_t& pos) {
52   if (pos % 4 != 0) {
53     pos += 4 - pos % 4;
54   }
55 }
56
57 static int getNextZero(
58     const std::vector<uint8_t>& v,
59     const size_t curPos,
60     const size_t limit) {
61   auto pos = std::find(v.begin() + curPos, v.begin() + limit, 0);
62   if (pos == v.begin() + limit) {
63     return -1;
64   }
65   return std::distance(v.begin(), pos);
66 }
67
68 static intptr_t getAddr(const std::vector<uint8_t>& v, size_t& pos) {
69   pos += kAddrWidth;
70   return folly::Endian::little(
71       folly::loadUnaligned<intptr_t>(v.data() + pos - kAddrWidth));
72 }
73
74 static std::string
75 getStr(const std::vector<uint8_t>& v, size_t& pos, const size_t len) {
76   CHECK_GE(len, 1);
77   std::string res;
78   res.resize(len - 1);
79   for (size_t i = 0; i < len - 1; i++) {
80     CHECK_NE(v[pos + i], 0);
81     res[i] = char(v[pos + i]);
82   }
83   CHECK_EQ(0, v[pos + len - 1]);
84   pos += len;
85   return res;
86 }
87
88 static std::string getExe() {
89   auto path = folly::sformat("/proc/{}/exe", getpid());
90   return boost::filesystem::read_symlink(path).string();
91 }
92
93 static std::string getNoteRawContent(const std::string& fileName) {
94   auto subProc = folly::Subprocess(
95       std::vector<std::string>{
96           "objdump",
97           "--full-content",
98           "--section=.note." + kUSDTSubsectionName,
99           fileName,
100       },
101       folly::Subprocess::Options().pipeStdout().usePath());
102   auto output = subProc.communicate();
103   auto retCode = subProc.wait();
104   CHECK(retCode.exited());
105   CHECK(output.second.empty());
106   return output.first;
107 }
108
109 static std::vector<uint8_t> readNote(const std::string& fileName) {
110   std::vector<uint8_t> res;
111   std::string rawContent = getNoteRawContent(fileName);
112   CHECK(!rawContent.empty());
113   // Strip out the part of output containing raw content, and split by line.
114   std::string contentStart =
115       "Contents of section .note." + kUSDTSubsectionName + ":";
116   auto pos = rawContent.find(contentStart);
117   CHECK_NE(pos, std::string::npos);
118   pos = rawContent.find("\n", pos + 1);
119   CHECK_NE(pos, std::string::npos);
120   rawContent = rawContent.substr(pos + 1);
121   std::vector<std::string> lines;
122   folly::split('\n', rawContent, lines, true);
123   CHECK_GT(lines.size(), 0);
124   // Parse each line.
125   for (auto line : lines) {
126     // Empty segments or ASCIIified content after two spaces.
127     auto endPos = line.find("  ");
128     CHECK_NE(endPos, std::string::npos);
129     line = line.substr(0, endPos);
130     std::vector<std::string> segments;
131     folly::split(' ', line, segments, true);
132     CHECK_GE(segments.size(), 2);
133     // First segment is address offset.
134     for (size_t i = 1; i < segments.size(); i++) {
135       CHECK_EQ(8, segments[i].size());
136       for (size_t j = 0; j < 8; j += 2) {
137         std::string hex = segments[i].substr(j, 2);
138         res.push_back(hexToInt(hex));
139       }
140     }
141   }
142   CHECK_EQ(0, res.size() % 4);
143   return res;
144 }
145
146 template <std::size_t SIZE>
147 static void checkTracepointArguments(
148     const std::string& arguments,
149     std::array<int, SIZE>& expectedSize) {
150   std::vector<std::string> args;
151   folly::split(' ', arguments, args);
152   EXPECT_EQ(expectedSize.size(), args.size());
153   for (size_t i = 0; i < args.size(); i++) {
154     EXPECT_FALSE(args[i].empty());
155     auto pos = args[i].find("@");
156     EXPECT_NE(pos, std::string::npos);
157     EXPECT_LT(pos, args[i].size() - 1);
158     std::string argSize = args[i].substr(0, pos);
159     EXPECT_EQ(expectedSize[i], abs(folly::to<int>(argSize)));
160   }
161 }
162
163 /**
164  * This helper reads the .note.stapsdt section of the currently running binary,
165  * checks if the tracepoints listed there are properly formatted, and return the
166  * arguments layout description string for the expected provider and probe
167  * combination if it exists.
168  */
169 static bool getTracepointArguments(
170     const std::string& expectedProvider,
171     const std::string& expectedProbe,
172     std::string& arguments) {
173   // Read the note and check if it's non-empty.
174   std::string exe = getExe();
175   auto note = readNote(exe);
176   auto len = note.size();
177   CHECK_GT(len, 0);
178   // The loop to read tracepoints one by one.
179   size_t pos = 0;
180   while (pos < len) {
181     // Check size information of the tracepoint.
182     CHECK_LE(pos + 12, len);
183
184     int headerSize = get4BytesValue(note, pos);
185     CHECK_EQ(kUSDTSubsectionName.size() + 1, headerSize);
186
187     int contentSize = get4BytesValue(note, pos);
188     size_t remaining = contentSize;
189     CHECK_GE(contentSize, kAddrWidth * 3);
190
191     int noteType = get4BytesValue(note, pos);
192     CHECK_EQ(kUSDTNoteType, noteType);
193
194     CHECK_LE(pos + headerSize + contentSize, len);
195
196     // Check header of the tracepoint.
197     std::string header = getStr(note, pos, headerSize);
198     CHECK_EQ(kUSDTSubsectionName, header);
199     align4Bytes(pos);
200
201     // Check address information of the tracepoint
202     intptr_t probeAddr = getAddr(note, pos);
203     CHECK_GT(probeAddr, 0);
204     remaining -= kAddrWidth;
205
206     intptr_t semaphoreAddr = getAddr(note, pos);
207     CHECK_EQ(0, semaphoreAddr);
208     remaining -= kAddrWidth;
209
210     intptr_t semaphoreBase = getAddr(note, pos);
211     CHECK_EQ(0, semaphoreBase);
212     remaining -= kAddrWidth;
213
214     // Read tracepoint provider, probe and argument layout description.
215     int providerEnd = getNextZero(note, pos, pos + remaining - 1);
216     CHECK_GE(providerEnd, 0);
217     size_t providerLen = providerEnd - pos + 1;
218     std::string provider = getStr(note, pos, providerLen);
219     remaining -= providerLen;
220
221     int probeEnd = getNextZero(note, pos, pos + remaining - 1);
222     CHECK_GE(probeEnd, 0);
223     size_t probeLen = probeEnd - pos + 1;
224     std::string probe = getStr(note, pos, probeLen);
225     remaining -= probeLen;
226
227     arguments = getStr(note, pos, remaining);
228     align4Bytes(pos);
229
230     if (provider == expectedProvider && probe == expectedProbe) {
231       return true;
232     }
233   }
234   return false;
235 }
236
237 static int arrayTestFunc() {
238   int v1 = folly::Random::rand32();
239   int v2 = folly::Random::rand32();
240   int64_t v3 = v1 + v2;
241   int a[4] = {v1, v2, v1, v2};
242   FOLLY_SDT(folly, test_static_tracepoint_array, a, v1, v3);
243   return v1 + v2;
244 }
245
246 TEST(StaticTracepoint, TestArray) {
247   arrayTestFunc();
248
249   std::string arguments;
250   ASSERT_TRUE(getTracepointArguments(
251       "folly", "test_static_tracepoint_array", arguments));
252   std::array<int, 3> expected{{sizeof(void*), sizeof(int), sizeof(int64_t)}};
253   checkTracepointArguments(arguments, expected);
254 }
255
256 static int pointerTestFunc() {
257   int v1 = folly::Random::rand32();
258   int v2 = folly::Random::rand32();
259   std::string str = "test string";
260   const char* a = str.c_str();
261   FOLLY_SDT(folly, test_static_tracepoint_pointer, a, v2, &v1);
262   return v1 + v2;
263 }
264
265 TEST(StaticTracepoint, TestPointer) {
266   pointerTestFunc();
267
268   std::string arguments;
269   ASSERT_TRUE(getTracepointArguments(
270       "folly", "test_static_tracepoint_array", arguments));
271   std::array<int, 3> expected{{sizeof(void*), sizeof(int), sizeof(void*)}};
272   checkTracepointArguments(arguments, expected);
273 }
274
275 static void emptyTestFunc() {
276   FOLLY_SDT(folly, test_static_tracepoint_empty);
277 }
278
279 TEST(StaticTracepoint, TestEmpty) {
280   emptyTestFunc();
281
282   std::string arguments;
283   ASSERT_TRUE(getTracepointArguments(
284       "folly", "test_static_tracepoint_empty", arguments));
285   EXPECT_TRUE(arguments.empty());
286 }
287
288 static int manyArgTypesTestFunc() {
289   uint32_t a = folly::Random::rand32();
290   uint32_t b = folly::Random::rand32();
291   bool bool_ = (a % 2) == (b % 2);
292   char char_ = a & 255;
293   short short_ = b & 32767;
294   long long_ = a;
295   float float_ = float(a) / float(b);
296   double double_ = double(a) / double(b);
297   FOLLY_SDT(
298       folly,
299       test_static_tracepoint_many_arg_types,
300       a,
301       b,
302       bool_,
303       char_,
304       short_,
305       long_,
306       float_,
307       double_);
308   return a + b;
309 }
310
311 TEST(StaticTracepoint, TestManyArgTypes) {
312   manyArgTypesTestFunc();
313
314   std::string arguments;
315   ASSERT_TRUE(getTracepointArguments(
316       "folly", "test_static_tracepoint_many_arg_types", arguments));
317   std::array<int, 8> expected{{
318       sizeof(uint32_t),
319       sizeof(uint32_t),
320       sizeof(bool),
321       sizeof(char),
322       sizeof(short),
323       sizeof(long),
324       sizeof(float),
325       sizeof(double),
326   }};
327   checkTracepointArguments(arguments, expected);
328 }
329
330 FOLLY_ALWAYS_INLINE static int alwaysInlineTestFunc() {
331   uint32_t a = folly::Random::rand32();
332   uint32_t b = folly::Random::rand32();
333   FOLLY_SDT(folly, test_static_tracepoint_always_inline, a, b);
334   return a + b;
335 }
336
337 TEST(StaticTracepoint, TestAlwaysInline) {
338   alwaysInlineTestFunc();
339
340   std::string arguments;
341   ASSERT_TRUE(getTracepointArguments(
342       "folly", "test_static_tracepoint_always_inline", arguments));
343   std::array<int, 2> expected{{sizeof(uint32_t), sizeof(uint32_t)}};
344   checkTracepointArguments(arguments, expected);
345 }
346
347 static void branchTestFunc() {
348   uint32_t a = folly::Random::rand32();
349   uint32_t b = folly::Random::rand32();
350   if (a > b) {
351     FOLLY_SDT(folly, test_static_tracepoint_branch_1, a / b);
352   } else {
353     FOLLY_SDT(folly, test_static_tracepoint_branch_2, double(a) / double(b));
354   }
355 }
356
357 TEST(StaticTracepoint, TestBranch) {
358   branchTestFunc();
359
360   std::string arguments1;
361   ASSERT_TRUE(getTracepointArguments(
362       "folly", "test_static_tracepoint_branch_1", arguments1));
363   std::array<int, 1> expected1{{sizeof(uint32_t)}};
364   checkTracepointArguments(arguments1, expected1);
365
366   std::string arguments2;
367   ASSERT_TRUE(getTracepointArguments(
368       "folly", "test_static_tracepoint_branch_2", arguments2));
369   std::array<int, 1> expected2{{sizeof(double)}};
370   checkTracepointArguments(arguments2, expected2);
371 }
372
373 struct testStruct {
374   int a;
375   int64_t b;
376   char c[32];
377 };
378
379 static void structTestFunc() {
380   testStruct s, t;
381   s.a = folly::Random::rand32();
382   s.b = folly::Random::rand32();
383   t.a = folly::Random::rand32();
384   t.b = folly::Random::rand32();
385   FOLLY_SDT(folly, test_static_tracepoint_struct, s, t);
386 }
387
388 TEST(StaticTracepoint, TestStruct) {
389   structTestFunc();
390
391   std::string arguments;
392   ASSERT_TRUE(getTracepointArguments(
393       "folly", "test_static_tracepoint_struct", arguments));
394   std::array<int, 2> expected{{sizeof(testStruct), sizeof(testStruct)}};
395   checkTracepointArguments(arguments, expected);
396 }