/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "RecordFilter.h" #include #if defined(__linux__) #include #endif // defined(__linux__) #include #include "event_attr.h" #include "event_type.h" #include "record.h" using namespace simpleperf; // @CddTest = 6.1/C-0-2 class RecordFilterTest : public ::testing::Test { public: RecordFilterTest() : filter(thread_tree) {} protected: void SetUp() override { const EventType* event_type = FindEventTypeByName("cpu-clock"); attr = CreateDefaultPerfEventAttr(*event_type); record.reset(new SampleRecord(attr, 0, 0, 0, 0, 0, 0, 0, {}, {}, {}, 0)); } SampleRecord& GetRecord(uint32_t pid, uint32_t tid) { record->tid_data.pid = pid; record->tid_data.tid = tid; return *record; } bool SetFilterData(const std::string& data) { TemporaryFile tmpfile; return android::base::WriteStringToFd(data, tmpfile.fd) && filter.SetFilterFile(tmpfile.path); } ThreadTree thread_tree; perf_event_attr attr; RecordFilter filter; std::unique_ptr record; }; // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, no_filter) { ASSERT_TRUE(filter.Check(GetRecord(0, 0))); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, cpu) { filter.AddCpus({1}); SampleRecord& r = GetRecord(0, 0); r.cpu_data.cpu = 1; ASSERT_TRUE(filter.Check(r)); r.cpu_data.cpu = 2; ASSERT_FALSE(filter.Check(r)); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, exclude_pid) { filter.AddPids({1}, true); ASSERT_FALSE(filter.Check(GetRecord(1, 1))); ASSERT_TRUE(filter.Check(GetRecord(2, 2))); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, exclude_tid) { filter.AddTids({1}, true); ASSERT_FALSE(filter.Check(GetRecord(1, 1))); ASSERT_TRUE(filter.Check(GetRecord(1, 2))); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, exclude_process_name_regex) { ASSERT_TRUE(filter.AddProcessNameRegex("processA", true)); thread_tree.SetThreadName(1, 1, "processA1"); thread_tree.SetThreadName(2, 2, "processB1"); ASSERT_FALSE(filter.Check(GetRecord(1, 1))); ASSERT_TRUE(filter.Check(GetRecord(2, 2))); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, exclude_thread_name_regex) { ASSERT_TRUE(filter.AddThreadNameRegex("threadA", true)); thread_tree.SetThreadName(1, 1, "processA_threadA"); thread_tree.SetThreadName(1, 2, "processA_threadB"); ASSERT_FALSE(filter.Check(GetRecord(1, 1))); ASSERT_TRUE(filter.Check(GetRecord(1, 2))); } #if defined(__linux__) // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, exclude_uid) { pid_t pid = getpid(); std::optional uid = GetProcessUid(pid); ASSERT_TRUE(uid.has_value()); filter.AddUids({uid.value()}, true); ASSERT_FALSE(filter.Check(GetRecord(pid, pid))); // The check fails if a process can't find its corresponding uid. uint32_t pid_not_exist = UINT32_MAX; ASSERT_FALSE(filter.Check(GetRecord(pid_not_exist, pid_not_exist))); } #endif // defined(__linux__) // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, include_pid) { filter.AddPids({1}, false); ASSERT_TRUE(filter.Check(GetRecord(1, 1))); ASSERT_FALSE(filter.Check(GetRecord(2, 2))); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, include_tid) { filter.AddTids({1}, false); ASSERT_TRUE(filter.Check(GetRecord(1, 1))); ASSERT_FALSE(filter.Check(GetRecord(1, 2))); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, include_process_name_regex) { ASSERT_TRUE(filter.AddProcessNameRegex("processA", false)); thread_tree.SetThreadName(1, 1, "processA1"); thread_tree.SetThreadName(2, 2, "processB1"); ASSERT_TRUE(filter.Check(GetRecord(1, 1))); ASSERT_FALSE(filter.Check(GetRecord(2, 2))); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, include_thread_name_regex) { ASSERT_TRUE(filter.AddThreadNameRegex("threadA", false)); thread_tree.SetThreadName(1, 1, "processA_threadA"); thread_tree.SetThreadName(1, 2, "processA_threadB"); ASSERT_TRUE(filter.Check(GetRecord(1, 1))); ASSERT_FALSE(filter.Check(GetRecord(1, 2))); } #if defined(__linux__) // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, include_uid) { pid_t pid = getpid(); std::optional uid = GetProcessUid(pid); ASSERT_TRUE(uid.has_value()); filter.AddUids({uid.value()}, false); ASSERT_TRUE(filter.Check(GetRecord(pid, pid))); uint32_t pid_not_exist = UINT32_MAX; ASSERT_FALSE(filter.Check(GetRecord(pid_not_exist, pid_not_exist))); } #endif // defined(__linux__) // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, global_time_filter) { ASSERT_TRUE( SetFilterData("GLOBAL_BEGIN 1000\n" "GLOBAL_END 2000\n" "GLOBAL_BEGIN 3000\n" "GLOBAL_END 4000")); SampleRecord& r = GetRecord(1, 1); r.time_data.time = 0; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 999; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 1000; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 1001; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 1999; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 2000; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 2001; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 3000; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 4000; ASSERT_FALSE(filter.Check(r)); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, process_time_filter) { ASSERT_TRUE( SetFilterData("PROCESS_BEGIN 1 1000\n" "PROCESS_END 1 2000")); SampleRecord& r = GetRecord(1, 1); r.time_data.time = 0; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 999; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 1000; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 1001; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 1999; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 2000; ASSERT_FALSE(filter.Check(r)); // When process time filters are used, not mentioned processes should be filtered. r.tid_data.pid = 2; r.time_data.time = 1000; ASSERT_FALSE(filter.Check(r)); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, thread_time_filter) { ASSERT_TRUE( SetFilterData("THREAD_BEGIN 1 1000\n" "THREAD_END 1 2000")); SampleRecord& r = GetRecord(1, 1); r.time_data.time = 0; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 999; ASSERT_FALSE(filter.Check(r)); r.time_data.time = 1000; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 1001; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 1999; ASSERT_TRUE(filter.Check(r)); r.time_data.time = 2000; ASSERT_FALSE(filter.Check(r)); // When thread time filters are used, not mentioned threads should be filtered. r.tid_data.tid = 2; r.time_data.time = 1000; ASSERT_FALSE(filter.Check(r)); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, clock_in_time_filter) { // If there is no filter data, any clock is fine. ASSERT_TRUE(filter.CheckClock("monotonic")); ASSERT_TRUE(filter.CheckClock("perf")); // If there is no clock command, monotonic clock is used. ASSERT_TRUE(SetFilterData("")); ASSERT_TRUE(filter.CheckClock("monotonic")); ASSERT_FALSE(filter.CheckClock("perf")); // If there is a clock command, use that clock. ASSERT_TRUE(SetFilterData("CLOCK realtime")); ASSERT_TRUE(filter.CheckClock("realtime")); ASSERT_FALSE(filter.CheckClock("monotonic")); } // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, error_in_time_filter) { // no timestamp error ASSERT_FALSE(SetFilterData("GLOBAL_BEGIN")); // time range error ASSERT_FALSE( SetFilterData("GLOBAL_BEGIN 1000\n" "GLOBAL_END 999")); // time range error ASSERT_FALSE( SetFilterData("GLOBAL_BEGIN 1000\n" "GLOBAL_END 1000")); // no timestamp error ASSERT_FALSE(SetFilterData("PROCESS_BEGIN 1")); // time range error ASSERT_FALSE( SetFilterData("PROCESS_BEGIN 1 1000\n" "PROCESS_END 1 999")); // no timestamp error ASSERT_FALSE(SetFilterData("THREAD_BEGIN 1")); // time range error ASSERT_FALSE( SetFilterData("THREAD_BEGIN 1 1000\n" "THREAD_END 1 999")); } namespace { class ParseRecordFilterCommand : public Command { public: ParseRecordFilterCommand(RecordFilter& filter) : Command("", "", ""), filter_(filter) {} bool Run(const std::vector& args) override { const auto option_formats = GetRecordFilterOptionFormats(for_recording); OptionValueMap options; std::vector> ordered_options; if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) { return false; } filter_.Clear(); return filter_.ParseOptions(options); } bool for_recording = true; private: RecordFilter& filter_; }; } // namespace // @CddTest = 6.1/C-0-2 TEST_F(RecordFilterTest, parse_options) { ParseRecordFilterCommand filter_cmd(filter); for (bool exclude : {true, false}) { std::string prefix = exclude ? "--exclude-" : "--include-"; ASSERT_TRUE(filter_cmd.Run({prefix + "pid", "1,2", prefix + "pid", "3"})); ASSERT_EQ(filter.Check(GetRecord(1, 1)), !exclude); ASSERT_EQ(filter.Check(GetRecord(2, 2)), !exclude); ASSERT_EQ(filter.Check(GetRecord(3, 3)), !exclude); ASSERT_TRUE(filter_cmd.Run({prefix + "tid", "1,2", prefix + "tid", "3"})); ASSERT_EQ(filter.Check(GetRecord(1, 1)), !exclude); ASSERT_EQ(filter.Check(GetRecord(1, 2)), !exclude); ASSERT_EQ(filter.Check(GetRecord(1, 3)), !exclude); ASSERT_TRUE( filter_cmd.Run({prefix + "process-name", "processA", prefix + "process-name", "processB"})); thread_tree.SetThreadName(1, 1, "processA"); thread_tree.SetThreadName(2, 2, "processB"); ASSERT_EQ(filter.Check(GetRecord(1, 1)), !exclude); ASSERT_EQ(filter.Check(GetRecord(2, 2)), !exclude); ASSERT_TRUE( filter_cmd.Run({prefix + "thread-name", "threadA", prefix + "thread-name", "threadB"})); thread_tree.SetThreadName(1, 11, "threadA"); thread_tree.SetThreadName(1, 12, "threadB"); ASSERT_EQ(filter.Check(GetRecord(1, 11)), !exclude); ASSERT_EQ(filter.Check(GetRecord(2, 12)), !exclude); ASSERT_TRUE(filter_cmd.Run({prefix + "uid", "1,2", prefix + "uid", "3"})); #if defined(__linux__) pid_t pid = getpid(); uid_t uid = getuid(); ASSERT_TRUE(filter_cmd.Run({prefix + "uid", std::to_string(uid)})); ASSERT_EQ(filter.Check(GetRecord(pid, pid)), !exclude); #endif // defined(__linux__) } filter_cmd.for_recording = false; ASSERT_TRUE(filter_cmd.Run({"--cpu", "0", "--cpu", "1-3"})); SampleRecord& r = GetRecord(0, 0); r.cpu_data.cpu = 0; ASSERT_TRUE(filter.Check(r)); r.cpu_data.cpu = 2; ASSERT_TRUE(filter.Check(r)); r.cpu_data.cpu = 4; ASSERT_FALSE(filter.Check(r)); }