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