blob: 1d5d1a067b02f7d1bdcc4c10045bc1d651d34856 [file] [log] [blame]
/*
* 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 <gtest/gtest.h>
#if defined(__linux__)
#include <unistd.h>
#endif // defined(__linux__)
#include <memory>
#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<SampleRecord> 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<uint32_t> 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<uint32_t> 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<std::string>& args) override {
const auto option_formats = GetRecordFilterOptionFormats(for_recording);
OptionValueMap options;
std::vector<std::pair<OptionName, OptionValue>> 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));
}