Home | History | Annotate | Download | only in simpleperf
      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 <android-base/stringprintf.h>
     20 #include <android-base/test_utils.h>
     21 
     22 #include <map>
     23 #include <memory>
     24 #include <thread>
     25 
     26 #include "command.h"
     27 #include "environment.h"
     28 #include "event_selection_set.h"
     29 #include "get_test_data.h"
     30 #include "record.h"
     31 #include "record_file.h"
     32 #include "test_util.h"
     33 #include "thread_tree.h"
     34 
     35 using namespace PerfFileFormat;
     36 
     37 static std::unique_ptr<Command> RecordCmd() {
     38   return CreateCommandInstance("record");
     39 }
     40 
     41 static bool RunRecordCmd(std::vector<std::string> v,
     42                          const char* output_file = nullptr) {
     43   std::unique_ptr<TemporaryFile> tmpfile;
     44   std::string out_file;
     45   if (output_file != nullptr) {
     46     out_file = output_file;
     47   } else {
     48     tmpfile.reset(new TemporaryFile);
     49     out_file = tmpfile->path;
     50   }
     51   v.insert(v.end(), {"-o", out_file, "sleep", SLEEP_SEC});
     52   return RecordCmd()->Run(v);
     53 }
     54 
     55 TEST(record_cmd, no_options) { ASSERT_TRUE(RunRecordCmd({})); }
     56 
     57 TEST(record_cmd, system_wide_option) {
     58   TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a"})));
     59 }
     60 
     61 TEST(record_cmd, sample_period_option) {
     62   ASSERT_TRUE(RunRecordCmd({"-c", "100000"}));
     63 }
     64 
     65 TEST(record_cmd, event_option) {
     66   ASSERT_TRUE(RunRecordCmd({"-e", "cpu-clock"}));
     67 }
     68 
     69 TEST(record_cmd, freq_option) {
     70   ASSERT_TRUE(RunRecordCmd({"-f", "99"}));
     71   ASSERT_TRUE(RunRecordCmd({"-F", "99"}));
     72 }
     73 
     74 TEST(record_cmd, output_file_option) {
     75   TemporaryFile tmpfile;
     76   ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "sleep", SLEEP_SEC}));
     77 }
     78 
     79 TEST(record_cmd, dump_kernel_mmap) {
     80   TemporaryFile tmpfile;
     81   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
     82   std::unique_ptr<RecordFileReader> reader =
     83       RecordFileReader::CreateInstance(tmpfile.path);
     84   ASSERT_TRUE(reader != nullptr);
     85   std::vector<std::unique_ptr<Record>> records = reader->DataSection();
     86   ASSERT_GT(records.size(), 0U);
     87   bool have_kernel_mmap = false;
     88   for (auto& record : records) {
     89     if (record->type() == PERF_RECORD_MMAP) {
     90       const MmapRecord* mmap_record =
     91           static_cast<const MmapRecord*>(record.get());
     92       if (strcmp(mmap_record->filename, DEFAULT_KERNEL_MMAP_NAME) == 0 ||
     93           strcmp(mmap_record->filename, DEFAULT_KERNEL_MMAP_NAME_PERF) == 0) {
     94         have_kernel_mmap = true;
     95         break;
     96       }
     97     }
     98   }
     99   ASSERT_TRUE(have_kernel_mmap);
    100 }
    101 
    102 TEST(record_cmd, dump_build_id_feature) {
    103   TemporaryFile tmpfile;
    104   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
    105   std::unique_ptr<RecordFileReader> reader =
    106       RecordFileReader::CreateInstance(tmpfile.path);
    107   ASSERT_TRUE(reader != nullptr);
    108   const FileHeader& file_header = reader->FileHeader();
    109   ASSERT_TRUE(file_header.features[FEAT_BUILD_ID / 8] &
    110               (1 << (FEAT_BUILD_ID % 8)));
    111   ASSERT_GT(reader->FeatureSectionDescriptors().size(), 0u);
    112 }
    113 
    114 TEST(record_cmd, tracepoint_event) {
    115   TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "-e", "sched:sched_switch"})));
    116 }
    117 
    118 TEST(record_cmd, branch_sampling) {
    119   if (IsBranchSamplingSupported()) {
    120     ASSERT_TRUE(RunRecordCmd({"-b"}));
    121     ASSERT_TRUE(RunRecordCmd({"-j", "any,any_call,any_ret,ind_call"}));
    122     ASSERT_TRUE(RunRecordCmd({"-j", "any,k"}));
    123     ASSERT_TRUE(RunRecordCmd({"-j", "any,u"}));
    124     ASSERT_FALSE(RunRecordCmd({"-j", "u"}));
    125   } else {
    126     GTEST_LOG_(INFO) << "This test does nothing as branch stack sampling is "
    127                         "not supported on this device.";
    128   }
    129 }
    130 
    131 TEST(record_cmd, event_modifier) {
    132   ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles:u"}));
    133 }
    134 
    135 TEST(record_cmd, fp_callchain_sampling) {
    136   ASSERT_TRUE(RunRecordCmd({"--call-graph", "fp"}));
    137 }
    138 
    139 TEST(record_cmd, system_wide_fp_callchain_sampling) {
    140   TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "--call-graph", "fp"})));
    141 }
    142 
    143 TEST(record_cmd, dwarf_callchain_sampling) {
    144   if (IsDwarfCallChainSamplingSupported()) {
    145     std::vector<std::unique_ptr<Workload>> workloads;
    146     CreateProcesses(1, &workloads);
    147     std::string pid = std::to_string(workloads[0]->GetPid());
    148     ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf"}));
    149     ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,16384"}));
    150     ASSERT_FALSE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,65536"}));
    151     ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}));
    152   } else {
    153     GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
    154                         "not supported on this device.";
    155   }
    156 }
    157 
    158 TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
    159   if (IsDwarfCallChainSamplingSupported()) {
    160     TEST_IN_ROOT(RunRecordCmd({"-a", "--call-graph", "dwarf"}));
    161   } else {
    162     GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
    163                         "not supported on this device.";
    164   }
    165 }
    166 
    167 TEST(record_cmd, no_unwind_option) {
    168   if (IsDwarfCallChainSamplingSupported()) {
    169     ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--no-unwind"}));
    170   } else {
    171     GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
    172                         "not supported on this device.";
    173   }
    174   ASSERT_FALSE(RunRecordCmd({"--no-unwind"}));
    175 }
    176 
    177 TEST(record_cmd, post_unwind_option) {
    178   if (IsDwarfCallChainSamplingSupported()) {
    179     std::vector<std::unique_ptr<Workload>> workloads;
    180     CreateProcesses(1, &workloads);
    181     std::string pid = std::to_string(workloads[0]->GetPid());
    182     ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf", "--post-unwind"}));
    183   } else {
    184     GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
    185                         "not supported on this device.";
    186   }
    187   ASSERT_FALSE(RunRecordCmd({"--post-unwind"}));
    188   ASSERT_FALSE(
    189       RunRecordCmd({"--call-graph", "dwarf", "--no-unwind", "--post-unwind"}));
    190 }
    191 
    192 TEST(record_cmd, existing_processes) {
    193   std::vector<std::unique_ptr<Workload>> workloads;
    194   CreateProcesses(2, &workloads);
    195   std::string pid_list = android::base::StringPrintf(
    196       "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
    197   ASSERT_TRUE(RunRecordCmd({"-p", pid_list}));
    198 }
    199 
    200 TEST(record_cmd, existing_threads) {
    201   std::vector<std::unique_ptr<Workload>> workloads;
    202   CreateProcesses(2, &workloads);
    203   // Process id can also be used as thread id in linux.
    204   std::string tid_list = android::base::StringPrintf(
    205       "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
    206   ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
    207 }
    208 
    209 TEST(record_cmd, no_monitored_threads) { ASSERT_FALSE(RecordCmd()->Run({""})); }
    210 
    211 TEST(record_cmd, more_than_one_event_types) {
    212   ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles,cpu-clock"}));
    213   ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles", "-e", "cpu-clock"}));
    214 }
    215 
    216 TEST(record_cmd, mmap_page_option) {
    217   ASSERT_TRUE(RunRecordCmd({"-m", "1"}));
    218   ASSERT_FALSE(RunRecordCmd({"-m", "0"}));
    219   ASSERT_FALSE(RunRecordCmd({"-m", "7"}));
    220 }
    221 
    222 static void CheckKernelSymbol(const std::string& path, bool need_kallsyms,
    223                               bool* success) {
    224   *success = false;
    225   std::unique_ptr<RecordFileReader> reader =
    226       RecordFileReader::CreateInstance(path);
    227   ASSERT_TRUE(reader != nullptr);
    228   std::vector<std::unique_ptr<Record>> records = reader->DataSection();
    229   bool has_kernel_symbol_records = false;
    230   for (const auto& record : records) {
    231     if (record->type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
    232       has_kernel_symbol_records = true;
    233     }
    234   }
    235   bool require_kallsyms = need_kallsyms && CheckKernelSymbolAddresses();
    236   ASSERT_EQ(require_kallsyms, has_kernel_symbol_records);
    237   *success = true;
    238 }
    239 
    240 TEST(record_cmd, kernel_symbol) {
    241   TemporaryFile tmpfile;
    242   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
    243   bool success;
    244   CheckKernelSymbol(tmpfile.path, true, &success);
    245   ASSERT_TRUE(success);
    246   ASSERT_TRUE(RunRecordCmd({"--no-dump-kernel-symbols"}, tmpfile.path));
    247   CheckKernelSymbol(tmpfile.path, false, &success);
    248   ASSERT_TRUE(success);
    249 }
    250 
    251 // Check if the dso/symbol records in perf.data matches our expectation.
    252 static void CheckDsoSymbolRecords(const std::string& path,
    253                                   bool can_have_dso_symbol_records,
    254                                   bool* success) {
    255   *success = false;
    256   std::unique_ptr<RecordFileReader> reader =
    257       RecordFileReader::CreateInstance(path);
    258   ASSERT_TRUE(reader != nullptr);
    259   std::vector<std::unique_ptr<Record>> records = reader->DataSection();
    260   bool has_dso_record = false;
    261   bool has_symbol_record = false;
    262   std::map<uint64_t, bool> dso_hit_map;
    263   for (const auto& record : records) {
    264     if (record->type() == SIMPLE_PERF_RECORD_DSO) {
    265       has_dso_record = true;
    266       uint64_t dso_id = static_cast<const DsoRecord*>(record.get())->dso_id;
    267       ASSERT_EQ(dso_hit_map.end(), dso_hit_map.find(dso_id));
    268       dso_hit_map.insert(std::make_pair(dso_id, false));
    269     } else if (record->type() == SIMPLE_PERF_RECORD_SYMBOL) {
    270       has_symbol_record = true;
    271       uint64_t dso_id = static_cast<const SymbolRecord*>(record.get())->dso_id;
    272       auto it = dso_hit_map.find(dso_id);
    273       ASSERT_NE(dso_hit_map.end(), it);
    274       it->second = true;
    275     }
    276   }
    277   if (can_have_dso_symbol_records) {
    278     // It is possible that there are no samples hitting functions having symbol.
    279     // In that case, there are no dso/symbol records.
    280     ASSERT_EQ(has_dso_record, has_symbol_record);
    281     for (auto& pair : dso_hit_map) {
    282       ASSERT_TRUE(pair.second);
    283     }
    284   } else {
    285     ASSERT_FALSE(has_dso_record);
    286     ASSERT_FALSE(has_symbol_record);
    287   }
    288   *success = true;
    289 }
    290 
    291 TEST(record_cmd, dump_symbols) {
    292   TemporaryFile tmpfile;
    293   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
    294   bool success;
    295   CheckDsoSymbolRecords(tmpfile.path, false, &success);
    296   ASSERT_TRUE(success);
    297   ASSERT_TRUE(RunRecordCmd({"--dump-symbols"}, tmpfile.path));
    298   CheckDsoSymbolRecords(tmpfile.path, true, &success);
    299   ASSERT_TRUE(success);
    300   if (IsDwarfCallChainSamplingSupported()) {
    301     std::vector<std::unique_ptr<Workload>> workloads;
    302     CreateProcesses(1, &workloads);
    303     std::string pid = std::to_string(workloads[0]->GetPid());
    304     ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}, tmpfile.path));
    305     bool success;
    306     CheckDsoSymbolRecords(tmpfile.path, false, &success);
    307     ASSERT_TRUE(success);
    308     ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g", "--dump-symbols"}, tmpfile.path));
    309     CheckDsoSymbolRecords(tmpfile.path, true, &success);
    310     ASSERT_TRUE(success);
    311   }
    312 }
    313 
    314 TEST(record_cmd, dump_kernel_symbols) {
    315   if (!IsRoot()) {
    316     GTEST_LOG_(INFO) << "Test requires root privilege";
    317     return;
    318   }
    319   system("echo 0 >/proc/sys/kernel/kptr_restrict");
    320   TemporaryFile tmpfile;
    321   ASSERT_TRUE(RunRecordCmd({"--dump-symbols", "-a", "-o", tmpfile.path, "sleep", "1"}));
    322   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
    323   ASSERT_TRUE(reader != nullptr);
    324   std::map<int, SectionDesc> section_map = reader->FeatureSectionDescriptors();
    325   ASSERT_NE(section_map.find(FEAT_FILE), section_map.end());
    326   std::string file_path;
    327   uint32_t file_type;
    328   uint64_t min_vaddr;
    329   std::vector<Symbol> symbols;
    330   size_t read_pos = 0;
    331   bool has_kernel_symbols = false;
    332   while (reader->ReadFileFeature(read_pos, &file_path, &file_type, &min_vaddr, &symbols)) {
    333     if (file_type == DSO_KERNEL && !symbols.empty()) {
    334       has_kernel_symbols = true;
    335     }
    336   }
    337   ASSERT_TRUE(has_kernel_symbols);
    338 }
    339 
    340 TEST(record_cmd, group_option) {
    341   ASSERT_TRUE(RunRecordCmd({"--group", "cpu-cycles,cpu-clock", "-m", "16"}));
    342   ASSERT_TRUE(RunRecordCmd({"--group", "cpu-cycles,cpu-clock", "--group",
    343                             "cpu-cycles:u,cpu-clock:u", "--group",
    344                             "cpu-cycles:k,cpu-clock:k", "-m", "16"}));
    345 }
    346 
    347 TEST(record_cmd, symfs_option) { ASSERT_TRUE(RunRecordCmd({"--symfs", "/"})); }
    348 
    349 TEST(record_cmd, duration_option) {
    350   TemporaryFile tmpfile;
    351   ASSERT_TRUE(RecordCmd()->Run({"--duration", "1.2", "-p",
    352                                 std::to_string(getpid()), "-o", tmpfile.path}));
    353   ASSERT_TRUE(
    354       RecordCmd()->Run({"--duration", "1", "-o", tmpfile.path, "sleep", "2"}));
    355 }
    356 
    357 TEST(record_cmd, support_modifier_for_clock_events) {
    358   for (const std::string& e : {"cpu-clock", "task-clock"}) {
    359     for (const std::string& m : {"u", "k"}) {
    360       ASSERT_TRUE(RunRecordCmd({"-e", e + ":" + m})) << "event " << e << ":"
    361                                                      << m;
    362     }
    363   }
    364 }
    365 
    366 TEST(record_cmd, handle_SIGHUP) {
    367   TemporaryFile tmpfile;
    368   std::thread thread([]() {
    369     sleep(1);
    370     kill(getpid(), SIGHUP);
    371   });
    372   thread.detach();
    373   ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "sleep", "1000000"}));
    374 }
    375 
    376 TEST(record_cmd, stop_when_no_more_targets) {
    377   TemporaryFile tmpfile;
    378   std::atomic<int> tid(0);
    379   std::thread thread([&]() {
    380     tid = gettid();
    381     sleep(1);
    382   });
    383   thread.detach();
    384   while (tid == 0);
    385   ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "-t", std::to_string(tid)}));
    386 }
    387 
    388 TEST(record_cmd, donot_stop_when_having_targets) {
    389   std::vector<std::unique_ptr<Workload>> workloads;
    390   CreateProcesses(1, &workloads);
    391   std::string pid = std::to_string(workloads[0]->GetPid());
    392   uint64_t start_time_in_ns = GetSystemClock();
    393   TemporaryFile tmpfile;
    394   ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "-p", pid, "--duration", "3"}));
    395   uint64_t end_time_in_ns = GetSystemClock();
    396   ASSERT_GT(end_time_in_ns - start_time_in_ns, static_cast<uint64_t>(2e9));
    397 }
    398