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