cmd_report_test.cpp revision eafa7188ac41584555d36ac0c19f3c7fc66d42cc
1/*
2 * Copyright (C) 2015 The Android Open Source Project
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 <gtest/gtest.h>
18
19#include <set>
20#include <unordered_map>
21
22#include <android-base/file.h>
23#include <android-base/strings.h>
24#include <android-base/test_utils.h>
25
26#include "command.h"
27#include "event_selection_set.h"
28#include "get_test_data.h"
29#include "perf_regs.h"
30#include "read_apk.h"
31#include "test_util.h"
32
33static std::unique_ptr<Command> ReportCmd() {
34  return CreateCommandInstance("report");
35}
36
37class ReportCommandTest : public ::testing::Test {
38 protected:
39  void Report(
40      const std::string& perf_data,
41      const std::vector<std::string>& add_args = std::vector<std::string>()) {
42    ReportRaw(GetTestData(perf_data), add_args);
43  }
44
45  void ReportRaw(
46      const std::string& perf_data,
47      const std::vector<std::string>& add_args = std::vector<std::string>()) {
48    success = false;
49    std::vector<std::string> args = {
50        "-i", perf_data, "--symfs", GetTestDataDir(), "-o", tmp_file.path};
51    args.insert(args.end(), add_args.begin(), add_args.end());
52    ASSERT_TRUE(ReportCmd()->Run(args));
53    ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &content));
54    ASSERT_TRUE(!content.empty());
55    std::vector<std::string> raw_lines = android::base::Split(content, "\n");
56    lines.clear();
57    for (const auto& line : raw_lines) {
58      std::string s = android::base::Trim(line);
59      if (!s.empty()) {
60        lines.push_back(s);
61      }
62    }
63    ASSERT_GE(lines.size(), 2u);
64    success = true;
65  }
66
67  TemporaryFile tmp_file;
68  std::string content;
69  std::vector<std::string> lines;
70  bool success;
71};
72
73TEST_F(ReportCommandTest, no_option) {
74  Report(PERF_DATA);
75  ASSERT_TRUE(success);
76  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
77}
78
79TEST_F(ReportCommandTest, report_symbol_from_elf_file_with_mini_debug_info) {
80  Report(PERF_DATA_WITH_MINI_DEBUG_INFO);
81  ASSERT_TRUE(success);
82  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
83}
84
85TEST_F(ReportCommandTest, sort_option_pid) {
86  Report(PERF_DATA, {"--sort", "pid"});
87  ASSERT_TRUE(success);
88  size_t line_index = 0;
89  while (line_index < lines.size() &&
90         lines[line_index].find("Pid") == std::string::npos) {
91    line_index++;
92  }
93  ASSERT_LT(line_index + 2, lines.size());
94}
95
96TEST_F(ReportCommandTest, sort_option_more_than_one) {
97  Report(PERF_DATA, {"--sort", "comm,pid,dso,symbol"});
98  ASSERT_TRUE(success);
99  size_t line_index = 0;
100  while (line_index < lines.size() &&
101         lines[line_index].find("Overhead") == std::string::npos) {
102    line_index++;
103  }
104  ASSERT_LT(line_index + 1, lines.size());
105  ASSERT_NE(lines[line_index].find("Command"), std::string::npos);
106  ASSERT_NE(lines[line_index].find("Pid"), std::string::npos);
107  ASSERT_NE(lines[line_index].find("Shared Object"), std::string::npos);
108  ASSERT_NE(lines[line_index].find("Symbol"), std::string::npos);
109  ASSERT_EQ(lines[line_index].find("Tid"), std::string::npos);
110}
111
112TEST_F(ReportCommandTest, children_option) {
113  Report(CALLGRAPH_FP_PERF_DATA, {"--children", "--sort", "symbol"});
114  ASSERT_TRUE(success);
115  std::unordered_map<std::string, std::pair<double, double>> map;
116  for (size_t i = 0; i < lines.size(); ++i) {
117    char name[1024];
118    std::pair<double, double> pair;
119    if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second,
120               name) == 3) {
121      map.insert(std::make_pair(name, pair));
122    }
123  }
124  ASSERT_NE(map.find("GlobalFunc"), map.end());
125  ASSERT_NE(map.find("main"), map.end());
126  auto func_pair = map["GlobalFunc"];
127  auto main_pair = map["main"];
128  ASSERT_GE(main_pair.first, func_pair.first);
129  ASSERT_GE(func_pair.first, func_pair.second);
130  ASSERT_GE(func_pair.second, main_pair.second);
131}
132
133static bool CheckCalleeMode(std::vector<std::string>& lines) {
134  bool found = false;
135  for (size_t i = 0; i + 2 < lines.size(); ++i) {
136    if (lines[i].find("GlobalFunc") != std::string::npos &&
137        lines[i + 1].find('|') != std::string::npos &&
138        lines[i + 2].find("main") != std::string::npos) {
139      found = true;
140      break;
141    }
142  }
143  return found;
144}
145
146static bool CheckCallerMode(std::vector<std::string>& lines) {
147  bool found = false;
148  for (size_t i = 0; i + 2 < lines.size(); ++i) {
149    if (lines[i].find("main") != std::string::npos &&
150        lines[i + 1].find('|') != std::string::npos &&
151        lines[i + 2].find("GlobalFunc") != std::string::npos) {
152      found = true;
153      break;
154    }
155  }
156  return found;
157}
158
159TEST_F(ReportCommandTest, callgraph_option) {
160  Report(CALLGRAPH_FP_PERF_DATA, {"-g"});
161  ASSERT_TRUE(success);
162  ASSERT_TRUE(CheckCallerMode(lines));
163  Report(CALLGRAPH_FP_PERF_DATA, {"-g", "callee"});
164  ASSERT_TRUE(success);
165  ASSERT_TRUE(CheckCalleeMode(lines));
166  Report(CALLGRAPH_FP_PERF_DATA, {"-g", "caller"});
167  ASSERT_TRUE(success);
168  ASSERT_TRUE(CheckCallerMode(lines));
169}
170
171static bool AllItemsWithString(std::vector<std::string>& lines,
172                               const std::vector<std::string>& strs) {
173  size_t line_index = 0;
174  while (line_index < lines.size() &&
175         lines[line_index].find("Overhead") == std::string::npos) {
176    line_index++;
177  }
178  if (line_index == lines.size() || line_index + 1 == lines.size()) {
179    return false;
180  }
181  line_index++;
182  for (; line_index < lines.size(); ++line_index) {
183    bool exist = false;
184    for (auto& s : strs) {
185      if (lines[line_index].find(s) != std::string::npos) {
186        exist = true;
187        break;
188      }
189    }
190    if (!exist) {
191      return false;
192    }
193  }
194  return true;
195}
196
197TEST_F(ReportCommandTest, pid_filter_option) {
198  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "pid"});
199  ASSERT_TRUE(success);
200  ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
201  ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17443"}));
202  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
203         {"--sort", "pid", "--pids", "17441"});
204  ASSERT_TRUE(success);
205  ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
206  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
207         {"--sort", "pid", "--pids", "17441,17443"});
208  ASSERT_TRUE(success);
209  ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17443"}));
210
211  // Test that --pids option is not the same as --tids option.
212  // Thread 17445 and 17441 are in process 17441.
213  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
214         {"--sort", "tid", "--pids", "17441"});
215  ASSERT_TRUE(success);
216  ASSERT_NE(content.find("17441"), std::string::npos);
217  ASSERT_NE(content.find("17445"), std::string::npos);
218}
219
220TEST_F(ReportCommandTest, wrong_pid_filter_option) {
221  ASSERT_EXIT(
222      {
223        Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--pids", "2,bogus"});
224        exit(success ? 0 : 1);
225      },
226      testing::ExitedWithCode(1), "invalid id in --pids option: bogus");
227}
228
229TEST_F(ReportCommandTest, tid_filter_option) {
230  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "tid"});
231  ASSERT_TRUE(success);
232  ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
233  ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17445"}));
234  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
235         {"--sort", "tid", "--tids", "17441"});
236  ASSERT_TRUE(success);
237  ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
238  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
239         {"--sort", "tid", "--tids", "17441,17445"});
240  ASSERT_TRUE(success);
241  ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17445"}));
242}
243
244TEST_F(ReportCommandTest, wrong_tid_filter_option) {
245  ASSERT_EXIT(
246      {
247        Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--tids", "2,bogus"});
248        exit(success ? 0 : 1);
249      },
250      testing::ExitedWithCode(1), "invalid id in --tids option: bogus");
251}
252
253TEST_F(ReportCommandTest, comm_filter_option) {
254  Report(PERF_DATA, {"--sort", "comm"});
255  ASSERT_TRUE(success);
256  ASSERT_FALSE(AllItemsWithString(lines, {"t1"}));
257  ASSERT_FALSE(AllItemsWithString(lines, {"t1", "t2"}));
258  Report(PERF_DATA, {"--sort", "comm", "--comms", "t1"});
259  ASSERT_TRUE(success);
260  ASSERT_TRUE(AllItemsWithString(lines, {"t1"}));
261  Report(PERF_DATA, {"--sort", "comm", "--comms", "t1,t2"});
262  ASSERT_TRUE(success);
263  ASSERT_TRUE(AllItemsWithString(lines, {"t1", "t2"}));
264}
265
266TEST_F(ReportCommandTest, dso_filter_option) {
267  Report(PERF_DATA, {"--sort", "dso"});
268  ASSERT_TRUE(success);
269  ASSERT_FALSE(AllItemsWithString(lines, {"/t1"}));
270  ASSERT_FALSE(AllItemsWithString(lines, {"/t1", "/t2"}));
271  Report(PERF_DATA, {"--sort", "dso", "--dsos", "/t1"});
272  ASSERT_TRUE(success);
273  ASSERT_TRUE(AllItemsWithString(lines, {"/t1"}));
274  Report(PERF_DATA, {"--sort", "dso", "--dsos", "/t1,/t2"});
275  ASSERT_TRUE(success);
276  ASSERT_TRUE(AllItemsWithString(lines, {"/t1", "/t2"}));
277}
278
279TEST_F(ReportCommandTest, symbol_filter_option) {
280  Report(PERF_DATA_WITH_SYMBOLS, {"--sort", "symbol"});
281  ASSERT_TRUE(success);
282  ASSERT_FALSE(AllItemsWithString(lines, {"func2(int, int)"}));
283  ASSERT_FALSE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
284  Report(PERF_DATA_WITH_SYMBOLS,
285         {"--sort", "symbol", "--symbols", "func2(int, int)"});
286  ASSERT_TRUE(success);
287  ASSERT_TRUE(AllItemsWithString(lines, {"func2(int, int)"}));
288  Report(PERF_DATA_WITH_SYMBOLS,
289         {"--sort", "symbol", "--symbols", "main;func2(int, int)"});
290  ASSERT_TRUE(success);
291  ASSERT_TRUE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
292}
293
294TEST_F(ReportCommandTest, use_branch_address) {
295  Report(BRANCH_PERF_DATA, {"-b", "--sort", "symbol_from,symbol_to"});
296  std::set<std::pair<std::string, std::string>> hit_set;
297  bool after_overhead = false;
298  for (const auto& line : lines) {
299    if (!after_overhead && line.find("Overhead") != std::string::npos) {
300      after_overhead = true;
301    } else if (after_overhead) {
302      char from[80];
303      char to[80];
304      if (sscanf(line.c_str(), "%*f%%%s%s", from, to) == 2) {
305        hit_set.insert(std::make_pair<std::string, std::string>(from, to));
306      }
307    }
308  }
309  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
310                "GlobalFunc", "CalledFunc")),
311            hit_set.end());
312  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
313                "CalledFunc", "GlobalFunc")),
314            hit_set.end());
315}
316
317TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
318  Report(NATIVELIB_IN_APK_PERF_DATA);
319  ASSERT_TRUE(success);
320  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
321            std::string::npos);
322  ASSERT_NE(content.find("Func2"), std::string::npos);
323}
324
325TEST_F(ReportCommandTest, report_more_than_one_event_types) {
326  Report(PERF_DATA_WITH_TWO_EVENT_TYPES);
327  ASSERT_TRUE(success);
328  ASSERT_NE(content.find("cpu-cycles"), std::string::npos);
329  ASSERT_NE(content.find("cpu-clock"), std::string::npos);
330}
331
332TEST_F(ReportCommandTest, report_kernel_symbol) {
333  Report(PERF_DATA_WITH_KERNEL_SYMBOL);
334  ASSERT_TRUE(success);
335  ASSERT_NE(content.find("perf_event_aux"), std::string::npos);
336}
337
338TEST_F(ReportCommandTest, report_dumped_symbols) {
339  Report(PERF_DATA_WITH_SYMBOLS);
340  ASSERT_TRUE(success);
341  ASSERT_NE(content.find("main"), std::string::npos);
342  Report(PERF_DATA_WITH_SYMBOLS_FOR_NONZERO_MINVADDR_DSO);
343  ASSERT_TRUE(success);
344  ASSERT_NE(content.find("main"), std::string::npos);
345}
346
347TEST_F(ReportCommandTest, report_sort_vaddr_in_file) {
348  Report(PERF_DATA, {"--sort", "vaddr_in_file"});
349  ASSERT_TRUE(success);
350  ASSERT_NE(content.find("VaddrInFile"), std::string::npos);
351}
352
353TEST_F(ReportCommandTest, check_build_id) {
354  Report(PERF_DATA_FOR_BUILD_ID_CHECK,
355         {"--symfs", GetTestData(CORRECT_SYMFS_FOR_BUILD_ID_CHECK)});
356  ASSERT_TRUE(success);
357  ASSERT_NE(content.find("main"), std::string::npos);
358  ASSERT_EXIT(
359      {
360        Report(PERF_DATA_FOR_BUILD_ID_CHECK,
361               {"--symfs", GetTestData(WRONG_SYMFS_FOR_BUILD_ID_CHECK)});
362        if (!success) {
363          exit(1);
364        }
365        if (content.find("main") != std::string::npos) {
366          exit(2);
367        }
368        exit(0);
369      },
370      testing::ExitedWithCode(0), "Build id mismatch");
371}
372
373TEST_F(ReportCommandTest, no_show_ip_option) {
374  Report(PERF_DATA);
375  ASSERT_TRUE(success);
376  ASSERT_EQ(content.find("unknown"), std::string::npos);
377  Report(PERF_DATA, {"--no-show-ip"});
378  ASSERT_TRUE(success);
379  ASSERT_NE(content.find("unknown"), std::string::npos);
380}
381
382TEST_F(ReportCommandTest, no_symbol_table_warning) {
383  ASSERT_EXIT(
384      {
385        Report(PERF_DATA,
386               {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
387        if (!success) {
388          exit(1);
389        }
390        if (content.find("GlobalFunc") != std::string::npos) {
391          exit(2);
392        }
393        exit(0);
394      },
395      testing::ExitedWithCode(0), "elf doesn't contain symbol table");
396}
397
398TEST_F(ReportCommandTest, read_elf_file_warning) {
399  ASSERT_EXIT(
400      {
401        Report(PERF_DATA,
402               {"--symfs", GetTestData(SYMFS_FOR_READ_ELF_FILE_WARNING)});
403        if (!success) {
404          exit(1);
405        }
406        if (content.find("GlobalFunc") != std::string::npos) {
407          exit(2);
408        }
409        exit(0);
410      },
411      testing::ExitedWithCode(0), "elf: Read failed");
412}
413
414TEST_F(ReportCommandTest, report_data_generated_by_linux_perf) {
415  Report(PERF_DATA_GENERATED_BY_LINUX_PERF);
416  ASSERT_TRUE(success);
417}
418
419#if defined(__linux__)
420
421static std::unique_ptr<Command> RecordCmd() {
422  return CreateCommandInstance("record");
423}
424
425TEST_F(ReportCommandTest, dwarf_callgraph) {
426  if (IsDwarfCallChainSamplingSupported()) {
427    TemporaryFile tmp_file;
428    ASSERT_TRUE(
429        RecordCmd()->Run({"-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
430    ReportRaw(tmp_file.path, {"-g"});
431    ASSERT_TRUE(success);
432  } else {
433    GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
434                        "not supported on this device.";
435  }
436}
437
438TEST_F(ReportCommandTest, report_dwarf_callgraph_of_nativelib_in_apk) {
439  // NATIVELIB_IN_APK_PERF_DATA is recorded on arm64, so can only report
440  // callgraph on arm64.
441  if (GetBuildArch() == ARCH_ARM64) {
442    Report(NATIVELIB_IN_APK_PERF_DATA, {"-g"});
443    ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
444              std::string::npos);
445    ASSERT_NE(content.find("Func2"), std::string::npos);
446    ASSERT_NE(content.find("Func1"), std::string::npos);
447    ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
448  } else {
449    GTEST_LOG_(INFO)
450        << "This test does nothing as it is only run on arm64 devices";
451  }
452}
453
454#endif
455