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 "get_test_data.h"
28#include "perf_regs.h"
29#include "read_apk.h"
30#include "test_util.h"
31
32static std::unique_ptr<Command> ReportCmd() {
33  return CreateCommandInstance("report");
34}
35
36class ReportCommandTest : public ::testing::Test {
37 protected:
38  void Report(
39      const std::string& perf_data,
40      const std::vector<std::string>& add_args = std::vector<std::string>()) {
41    ReportRaw(GetTestData(perf_data), add_args);
42  }
43
44  void ReportRaw(
45      const std::string& perf_data,
46      const std::vector<std::string>& add_args = std::vector<std::string>()) {
47    success = false;
48    TemporaryFile tmp_file;
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  std::string content;
68  std::vector<std::string> lines;
69  bool success;
70};
71
72TEST_F(ReportCommandTest, no_option) {
73  Report(PERF_DATA);
74  ASSERT_TRUE(success);
75  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
76}
77
78TEST_F(ReportCommandTest, report_symbol_from_elf_file_with_mini_debug_info) {
79  Report(PERF_DATA_WITH_MINI_DEBUG_INFO);
80  ASSERT_TRUE(success);
81  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
82}
83
84TEST_F(ReportCommandTest, sort_option_pid) {
85  Report(PERF_DATA, {"--sort", "pid"});
86  ASSERT_TRUE(success);
87  size_t line_index = 0;
88  while (line_index < lines.size() &&
89         lines[line_index].find("Pid") == std::string::npos) {
90    line_index++;
91  }
92  ASSERT_LT(line_index + 2, lines.size());
93}
94
95TEST_F(ReportCommandTest, sort_option_more_than_one) {
96  Report(PERF_DATA, {"--sort", "comm,pid,dso,symbol"});
97  ASSERT_TRUE(success);
98  size_t line_index = 0;
99  while (line_index < lines.size() &&
100         lines[line_index].find("Overhead") == std::string::npos) {
101    line_index++;
102  }
103  ASSERT_LT(line_index + 1, lines.size());
104  ASSERT_NE(lines[line_index].find("Command"), std::string::npos);
105  ASSERT_NE(lines[line_index].find("Pid"), std::string::npos);
106  ASSERT_NE(lines[line_index].find("Shared Object"), std::string::npos);
107  ASSERT_NE(lines[line_index].find("Symbol"), std::string::npos);
108  ASSERT_EQ(lines[line_index].find("Tid"), std::string::npos);
109}
110
111TEST_F(ReportCommandTest, children_option) {
112  Report(CALLGRAPH_FP_PERF_DATA, {"--children", "--sort", "symbol"});
113  ASSERT_TRUE(success);
114  std::unordered_map<std::string, std::pair<double, double>> map;
115  for (size_t i = 0; i < lines.size(); ++i) {
116    char name[1024];
117    std::pair<double, double> pair;
118    if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second,
119               name) == 3) {
120      map.insert(std::make_pair(name, pair));
121    }
122  }
123  ASSERT_NE(map.find("GlobalFunc"), map.end());
124  ASSERT_NE(map.find("main"), map.end());
125  auto func_pair = map["GlobalFunc"];
126  auto main_pair = map["main"];
127  ASSERT_GE(main_pair.first, func_pair.first);
128  ASSERT_GE(func_pair.first, func_pair.second);
129  ASSERT_GE(func_pair.second, main_pair.second);
130}
131
132static bool CheckCalleeMode(std::vector<std::string>& lines) {
133  bool found = false;
134  for (size_t i = 0; i + 1 < lines.size(); ++i) {
135    if (lines[i].find("GlobalFunc") != std::string::npos &&
136        lines[i + 1].find("main") != std::string::npos) {
137      found = true;
138      break;
139    }
140  }
141  return found;
142}
143
144static bool CheckCallerMode(std::vector<std::string>& lines) {
145  bool found = false;
146  for (size_t i = 0; i + 1 < lines.size(); ++i) {
147    if (lines[i].find("main") != std::string::npos &&
148        lines[i + 1].find("GlobalFunc") != std::string::npos) {
149      found = true;
150      break;
151    }
152  }
153  return found;
154}
155
156TEST_F(ReportCommandTest, callgraph_option) {
157  Report(CALLGRAPH_FP_PERF_DATA, {"-g"});
158  ASSERT_TRUE(success);
159  ASSERT_TRUE(CheckCallerMode(lines));
160  Report(CALLGRAPH_FP_PERF_DATA, {"-g", "callee"});
161  ASSERT_TRUE(success);
162  ASSERT_TRUE(CheckCalleeMode(lines));
163  Report(CALLGRAPH_FP_PERF_DATA, {"-g", "caller"});
164  ASSERT_TRUE(success);
165  ASSERT_TRUE(CheckCallerMode(lines));
166}
167
168static bool AllItemsWithString(std::vector<std::string>& lines,
169                               const std::vector<std::string>& strs) {
170  size_t line_index = 0;
171  while (line_index < lines.size() &&
172         lines[line_index].find("Overhead") == std::string::npos) {
173    line_index++;
174  }
175  if (line_index == lines.size() || line_index + 1 == lines.size()) {
176    return false;
177  }
178  line_index++;
179  for (; line_index < lines.size(); ++line_index) {
180    bool exist = false;
181    for (auto& s : strs) {
182      if (lines[line_index].find(s) != std::string::npos) {
183        exist = true;
184        break;
185      }
186    }
187    if (!exist) {
188      return false;
189    }
190  }
191  return true;
192}
193
194TEST_F(ReportCommandTest, pid_filter_option) {
195  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "pid"});
196  ASSERT_TRUE(success);
197  ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
198  ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17443"}));
199  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
200         {"--sort", "pid", "--pids", "17441"});
201  ASSERT_TRUE(success);
202  ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
203  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
204         {"--sort", "pid", "--pids", "17441,17443"});
205  ASSERT_TRUE(success);
206  ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17443"}));
207
208  // Test that --pids option is not the same as --tids option.
209  // Thread 17445 and 17441 are in process 17441.
210  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
211         {"--sort", "tid", "--pids", "17441"});
212  ASSERT_TRUE(success);
213  ASSERT_NE(content.find("17441"), std::string::npos);
214  ASSERT_NE(content.find("17445"), std::string::npos);
215}
216
217TEST_F(ReportCommandTest, wrong_pid_filter_option) {
218  ASSERT_EXIT(
219      {
220        Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--pids", "2,bogus"});
221        exit(success ? 0 : 1);
222      },
223      testing::ExitedWithCode(1), "invalid id in --pids option: bogus");
224}
225
226TEST_F(ReportCommandTest, tid_filter_option) {
227  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "tid"});
228  ASSERT_TRUE(success);
229  ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
230  ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17445"}));
231  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
232         {"--sort", "tid", "--tids", "17441"});
233  ASSERT_TRUE(success);
234  ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
235  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
236         {"--sort", "tid", "--tids", "17441,17445"});
237  ASSERT_TRUE(success);
238  ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17445"}));
239}
240
241TEST_F(ReportCommandTest, wrong_tid_filter_option) {
242  ASSERT_EXIT(
243      {
244        Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--tids", "2,bogus"});
245        exit(success ? 0 : 1);
246      },
247      testing::ExitedWithCode(1), "invalid id in --tids option: bogus");
248}
249
250TEST_F(ReportCommandTest, comm_filter_option) {
251  Report(PERF_DATA, {"--sort", "comm"});
252  ASSERT_TRUE(success);
253  ASSERT_FALSE(AllItemsWithString(lines, {"t1"}));
254  ASSERT_FALSE(AllItemsWithString(lines, {"t1", "t2"}));
255  Report(PERF_DATA, {"--sort", "comm", "--comms", "t1"});
256  ASSERT_TRUE(success);
257  ASSERT_TRUE(AllItemsWithString(lines, {"t1"}));
258  Report(PERF_DATA, {"--sort", "comm", "--comms", "t1,t2"});
259  ASSERT_TRUE(success);
260  ASSERT_TRUE(AllItemsWithString(lines, {"t1", "t2"}));
261}
262
263TEST_F(ReportCommandTest, dso_filter_option) {
264  Report(PERF_DATA, {"--sort", "dso"});
265  ASSERT_TRUE(success);
266  ASSERT_FALSE(AllItemsWithString(lines, {"/t1"}));
267  ASSERT_FALSE(AllItemsWithString(lines, {"/t1", "/t2"}));
268  Report(PERF_DATA, {"--sort", "dso", "--dsos", "/t1"});
269  ASSERT_TRUE(success);
270  ASSERT_TRUE(AllItemsWithString(lines, {"/t1"}));
271  Report(PERF_DATA, {"--sort", "dso", "--dsos", "/t1,/t2"});
272  ASSERT_TRUE(success);
273  ASSERT_TRUE(AllItemsWithString(lines, {"/t1", "/t2"}));
274}
275
276TEST_F(ReportCommandTest, symbol_filter_option) {
277  Report(PERF_DATA_WITH_SYMBOLS, {"--sort", "symbol"});
278  ASSERT_TRUE(success);
279  ASSERT_FALSE(AllItemsWithString(lines, {"func2(int, int)"}));
280  ASSERT_FALSE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
281  Report(PERF_DATA_WITH_SYMBOLS,
282         {"--sort", "symbol", "--symbols", "func2(int, int)"});
283  ASSERT_TRUE(success);
284  ASSERT_TRUE(AllItemsWithString(lines, {"func2(int, int)"}));
285  Report(PERF_DATA_WITH_SYMBOLS,
286         {"--sort", "symbol", "--symbols", "main;func2(int, int)"});
287  ASSERT_TRUE(success);
288  ASSERT_TRUE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
289}
290
291TEST_F(ReportCommandTest, use_branch_address) {
292  Report(BRANCH_PERF_DATA, {"-b", "--sort", "symbol_from,symbol_to"});
293  std::set<std::pair<std::string, std::string>> hit_set;
294  bool after_overhead = false;
295  for (const auto& line : lines) {
296    if (!after_overhead && line.find("Overhead") != std::string::npos) {
297      after_overhead = true;
298    } else if (after_overhead) {
299      char from[80];
300      char to[80];
301      if (sscanf(line.c_str(), "%*f%%%s%s", from, to) == 2) {
302        hit_set.insert(std::make_pair<std::string, std::string>(from, to));
303      }
304    }
305  }
306  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
307                "GlobalFunc", "CalledFunc")),
308            hit_set.end());
309  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
310                "CalledFunc", "GlobalFunc")),
311            hit_set.end());
312}
313
314TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
315  Report(NATIVELIB_IN_APK_PERF_DATA);
316  ASSERT_TRUE(success);
317  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
318            std::string::npos);
319  ASSERT_NE(content.find("Func2"), std::string::npos);
320}
321
322TEST_F(ReportCommandTest, report_more_than_one_event_types) {
323  Report(PERF_DATA_WITH_TWO_EVENT_TYPES);
324  ASSERT_TRUE(success);
325  size_t pos = 0;
326  ASSERT_NE(pos = content.find("cpu-cycles", pos), std::string::npos);
327  ASSERT_NE(pos = content.find("Samples:", pos), std::string::npos);
328  ASSERT_NE(pos = content.find("cpu-clock", pos), std::string::npos);
329  ASSERT_NE(pos = content.find("Samples:", pos), 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("memcpy"), std::string::npos);
345}
346
347TEST_F(ReportCommandTest, report_dumped_symbols_with_symfs_dir) {
348  // Check if we can report symbols when they appear both in perf.data and symfs dir.
349  Report(PERF_DATA_WITH_SYMBOLS, {"--symfs", GetTestDataDir()});
350  ASSERT_TRUE(success);
351  ASSERT_NE(content.find("main"), std::string::npos);
352}
353
354TEST_F(ReportCommandTest, report_sort_vaddr_in_file) {
355  Report(PERF_DATA, {"--sort", "vaddr_in_file"});
356  ASSERT_TRUE(success);
357  ASSERT_NE(content.find("VaddrInFile"), std::string::npos);
358}
359
360TEST_F(ReportCommandTest, check_build_id) {
361  Report(PERF_DATA_FOR_BUILD_ID_CHECK,
362         {"--symfs", GetTestData(CORRECT_SYMFS_FOR_BUILD_ID_CHECK)});
363  ASSERT_TRUE(success);
364  ASSERT_NE(content.find("main"), std::string::npos);
365  ASSERT_EXIT(
366      {
367        Report(PERF_DATA_FOR_BUILD_ID_CHECK,
368               {"--symfs", GetTestData(WRONG_SYMFS_FOR_BUILD_ID_CHECK)});
369        if (!success) {
370          exit(1);
371        }
372        if (content.find("main") != std::string::npos) {
373          exit(2);
374        }
375        exit(0);
376      },
377      testing::ExitedWithCode(0), "Build id mismatch");
378}
379
380TEST_F(ReportCommandTest, no_show_ip_option) {
381  Report(PERF_DATA);
382  ASSERT_TRUE(success);
383  ASSERT_EQ(content.find("unknown"), std::string::npos);
384  Report(PERF_DATA, {"--no-show-ip"});
385  ASSERT_TRUE(success);
386  ASSERT_NE(content.find("unknown"), std::string::npos);
387}
388
389TEST_F(ReportCommandTest, no_symbol_table_warning) {
390  ASSERT_EXIT(
391      {
392        Report(PERF_DATA,
393               {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
394        if (!success) {
395          exit(1);
396        }
397        if (content.find("GlobalFunc") != std::string::npos) {
398          exit(2);
399        }
400        exit(0);
401      },
402      testing::ExitedWithCode(0), "elf doesn't contain symbol table");
403}
404
405TEST_F(ReportCommandTest, read_elf_file_warning) {
406  ASSERT_EXIT(
407      {
408        Report(PERF_DATA,
409               {"--symfs", GetTestData(SYMFS_FOR_READ_ELF_FILE_WARNING)});
410        if (!success) {
411          exit(1);
412        }
413        if (content.find("GlobalFunc") != std::string::npos) {
414          exit(2);
415        }
416        exit(0);
417      },
418      testing::ExitedWithCode(0), "elf: Read failed");
419}
420
421TEST_F(ReportCommandTest, report_data_generated_by_linux_perf) {
422  Report(PERF_DATA_GENERATED_BY_LINUX_PERF);
423  ASSERT_TRUE(success);
424}
425
426TEST_F(ReportCommandTest, max_stack_and_percent_limit_option) {
427  Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g"});
428  ASSERT_TRUE(success);
429  ASSERT_NE(content.find("89.03"), std::string::npos);
430
431  Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g", "--max-stack", "0"});
432  ASSERT_TRUE(success);
433  ASSERT_EQ(content.find("89.03"), std::string::npos);
434  Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g", "--max-stack", "2"});
435  ASSERT_TRUE(success);
436  ASSERT_NE(content.find("89.03"), std::string::npos);
437
438  Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT,
439         {"-g", "--percent-limit", "90"});
440  ASSERT_TRUE(success);
441  ASSERT_EQ(content.find("89.03"), std::string::npos);
442  Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT,
443         {"-g", "--percent-limit", "70"});
444  ASSERT_TRUE(success);
445  ASSERT_NE(content.find("89.03"), std::string::npos);
446}
447
448TEST_F(ReportCommandTest, kallsyms_option) {
449  Report(PERF_DATA, {"--kallsyms", GetTestData("kallsyms")});
450  ASSERT_TRUE(success);
451  ASSERT_NE(content.find("FakeKernelSymbol"), std::string::npos);
452}
453
454TEST_F(ReportCommandTest, invalid_perf_data) {
455  ASSERT_FALSE(ReportCmd()->Run({"-i", GetTestData(INVALID_PERF_DATA)}));
456}
457
458TEST_F(ReportCommandTest, raw_period_option) {
459  Report(PERF_DATA, {"--raw-period"});
460  ASSERT_TRUE(success);
461  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
462  ASSERT_EQ(content.find('%'), std::string::npos);
463}
464
465TEST_F(ReportCommandTest, full_callgraph_option) {
466  Report(CALLGRAPH_FP_PERF_DATA, {"-g"});
467  ASSERT_TRUE(success);
468  ASSERT_NE(content.find("skipped in brief callgraph mode"), std::string::npos);
469  Report(CALLGRAPH_FP_PERF_DATA, {"-g", "--full-callgraph"});
470  ASSERT_TRUE(success);
471  ASSERT_EQ(content.find("skipped in brief callgraph mode"), std::string::npos);
472}
473
474TEST_F(ReportCommandTest, report_offcpu_time) {
475  Report(PERF_DATA_WITH_TRACE_OFFCPU, {"--children"});
476  ASSERT_TRUE(success);
477  ASSERT_NE(content.find("Time in ns"), std::string::npos);
478  bool found = false;
479  for (auto& line : lines) {
480    if (line.find("SleepFunction") != std::string::npos) {
481      ASSERT_NE(line.find("38.77%"), std::string::npos);
482      found = true;
483      break;
484    }
485  }
486  ASSERT_TRUE(found);
487}
488
489#if defined(__linux__)
490#include "event_selection_set.h"
491
492static std::unique_ptr<Command> RecordCmd() {
493  return CreateCommandInstance("record");
494}
495
496TEST_F(ReportCommandTest, dwarf_callgraph) {
497  OMIT_TEST_ON_NON_NATIVE_ABIS();
498  ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
499  std::vector<std::unique_ptr<Workload>> workloads;
500  CreateProcesses(1, &workloads);
501  std::string pid = std::to_string(workloads[0]->GetPid());
502  TemporaryFile tmp_file;
503  ASSERT_TRUE(
504      RecordCmd()->Run({"-p", pid, "-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
505  ReportRaw(tmp_file.path, {"-g"});
506  ASSERT_TRUE(success);
507}
508
509TEST_F(ReportCommandTest, report_dwarf_callgraph_of_nativelib_in_apk) {
510  Report(NATIVELIB_IN_APK_PERF_DATA, {"-g"});
511  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
512            std::string::npos);
513  ASSERT_NE(content.find("Func2"), std::string::npos);
514  ASSERT_NE(content.find("Func1"), std::string::npos);
515  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
516}
517
518TEST_F(ReportCommandTest, exclude_kernel_callchain) {
519  TEST_REQUIRE_HOST_ROOT();
520  OMIT_TEST_ON_NON_NATIVE_ABIS();
521  std::vector<std::unique_ptr<Workload>> workloads;
522  CreateProcesses(1, &workloads);
523  std::string pid = std::to_string(workloads[0]->GetPid());
524  TemporaryFile tmpfile;
525  ASSERT_TRUE(RecordCmd()->Run({"--trace-offcpu", "-e", "cpu-cycles:u", "-p", pid,
526                                "--duration", "2", "-o", tmpfile.path, "-g"}));
527  ReportRaw(tmpfile.path, {"-g"});
528  ASSERT_TRUE(success);
529  ASSERT_EQ(content.find("[kernel.kallsyms]"), std::string::npos);
530}
531
532#endif
533