blob: 39c3ea7374d45f94b3c22e696b0a0c9bda46f6b7 [file] [log] [blame]
/*
* Copyright (C) 2018 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 "src/traced/probes/ftrace/ftrace_config_muxer.h"
#include <memory>
#include "ftrace_config_muxer.h"
#include "perfetto/ext/base/utils.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "src/traced/probes/ftrace/atrace_wrapper.h"
#include "src/traced/probes/ftrace/compact_sched.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"
#include "src/traced/probes/ftrace/ftrace_stats.h"
#include "src/traced/probes/ftrace/proto_translation_table.h"
#include "test/gtest_and_gmock.h"
using testing::_;
using testing::AnyNumber;
using testing::Contains;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Eq;
using testing::Invoke;
using testing::IsEmpty;
using testing::IsSupersetOf;
using testing::MatchesRegex;
using testing::NiceMock;
using testing::Not;
using testing::Return;
using testing::UnorderedElementsAre;
namespace perfetto {
namespace {
constexpr int kFakeSchedSwitchEventId = 1;
constexpr int kCgroupMkdirEventId = 12;
constexpr int kFakePrintEventId = 20;
constexpr int kSysEnterId = 329;
struct FakeSyscallTable {
static constexpr char names[] =
"sys_open\0"
"sys_read\0";
static constexpr SyscallTable::OffT offsets[]{0, 9};
};
std::string PageSizeKb() {
return std::to_string(base::GetSysPageSize() / 1024);
}
class MockFtraceProcfs : public FtraceProcfs {
public:
MockFtraceProcfs() : FtraceProcfs("/root/") {
ON_CALL(*this, NumberOfCpus()).WillByDefault(Return(1));
ON_CALL(*this, WriteToFile(_, _)).WillByDefault(Return(true));
ON_CALL(*this, AppendToFile(_, _)).WillByDefault(Return(true));
ON_CALL(*this, ClearFile(_)).WillByDefault(Return(true));
EXPECT_CALL(*this, NumberOfCpus()).Times(AnyNumber());
}
MOCK_METHOD(bool,
WriteToFile,
(const std::string& path, const std::string& str),
(override));
MOCK_METHOD(bool,
AppendToFile,
(const std::string& path, const std::string& str),
(override));
MOCK_METHOD(char, ReadOneCharFromFile, (const std::string& path), (override));
MOCK_METHOD(bool, ClearFile, (const std::string& path), (override));
MOCK_METHOD(std::string,
ReadFileIntoString,
(const std::string& path),
(const, override));
MOCK_METHOD(size_t, NumberOfCpus, (), (const, override));
MOCK_METHOD(const std::set<std::string>,
GetEventNamesForGroup,
(const std::string& path),
(const, override));
MOCK_METHOD(std::string,
ReadEventFormat,
(const std::string& group, const std::string& name),
(const, override));
};
class MockAtraceWrapper : public AtraceWrapper {
public:
MOCK_METHOD(bool, RunAtrace, (const std::vector<std::string>&, std::string*));
MOCK_METHOD(bool, SupportsUserspaceOnly, ());
MOCK_METHOD(bool, SupportsPreferSdk, ());
};
class MockProtoTranslationTable : public ProtoTranslationTable {
public:
MockProtoTranslationTable(NiceMock<MockFtraceProcfs>* ftrace_procfs,
const std::vector<Event>& events,
std::vector<Field> common_fields,
FtracePageHeaderSpec ftrace_page_header_spec,
CompactSchedEventFormat compact_sched_format)
: ProtoTranslationTable(ftrace_procfs,
events,
common_fields,
ftrace_page_header_spec,
compact_sched_format,
PrintkMap()) {}
MOCK_METHOD(Event*,
GetOrCreateEvent,
(const GroupAndName& group_and_name),
(override));
MOCK_METHOD(Event*,
GetOrCreateKprobeEvent,
(const GroupAndName& group_and_name),
(override));
MOCK_METHOD(const Event*,
GetEvent,
(const GroupAndName& group_and_name),
(const, override));
};
TEST(ComputeCpuBufferSizeInPagesTest, DifferentCases) {
constexpr auto test = ComputeCpuBufferSizeInPages;
auto KbToPages = [](uint64_t kb) {
return kb * 1024 / base::GetSysPageSize();
};
int64_t kNoRamInfo = 0;
bool kExactSize = false;
bool kLowerBoundSize = true;
int64_t kLowRamPages =
static_cast<int64_t>(KbToPages(3 * (1ULL << 20) + 512 * (1ULL << 10)));
int64_t kHighRamPages =
static_cast<int64_t>(KbToPages(7 * (1ULL << 20) + 512 * (1ULL << 10)));
// No buffer size given: good default.
EXPECT_EQ(test(0, kExactSize, kNoRamInfo), KbToPages(2048));
// Default depends on device ram size.
EXPECT_EQ(test(0, kExactSize, kLowRamPages), KbToPages(2048));
EXPECT_EQ(test(0, kExactSize, kHighRamPages), KbToPages(8192));
// buffer_size_lower_bound lets us choose a higher default than given.
// default > requested:
EXPECT_EQ(test(4096, kExactSize, kHighRamPages), KbToPages(4096));
EXPECT_EQ(test(4096, kLowerBoundSize, kHighRamPages), KbToPages(8192));
// requested > default:
EXPECT_EQ(test(4096, kExactSize, kLowRamPages), KbToPages(4096));
EXPECT_EQ(test(4096, kLowerBoundSize, kLowRamPages), KbToPages(4096));
// requested > default:
EXPECT_EQ(test(16384, kExactSize, kHighRamPages), KbToPages(16384));
EXPECT_EQ(test(16384, kLowerBoundSize, kHighRamPages), KbToPages(16384));
// Your size ends up with less than 1 page per cpu -> 1 page.
EXPECT_EQ(test(3, kExactSize, kNoRamInfo), 1u);
// You picked a good size -> your size rounded to nearest page.
EXPECT_EQ(test(42, kExactSize, kNoRamInfo), KbToPages(42));
// Sysconf returning an error is ok.
EXPECT_EQ(test(0, kExactSize, -1), KbToPages(2048));
EXPECT_EQ(test(4096, kExactSize, -1), KbToPages(4096));
}
// Base fixture that provides some dependencies but doesn't construct a
// FtraceConfigMuxer.
class FtraceConfigMuxerTest : public ::testing::Test {
protected:
FtraceConfigMuxerTest() {
ON_CALL(atrace_wrapper_, RunAtrace).WillByDefault(Return(true));
ON_CALL(atrace_wrapper_, SupportsUserspaceOnly).WillByDefault(Return(true));
ON_CALL(atrace_wrapper_, SupportsPreferSdk).WillByDefault(Return(true));
}
std::unique_ptr<MockProtoTranslationTable> GetMockTable() {
std::vector<Field> common_fields;
std::vector<Event> events;
return std::unique_ptr<MockProtoTranslationTable>(
new MockProtoTranslationTable(
&ftrace_, events, std::move(common_fields),
ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
InvalidCompactSchedEventFormatForTesting()));
}
SyscallTable GetSyscallTable() {
return SyscallTable::Load<FakeSyscallTable>();
}
std::unique_ptr<ProtoTranslationTable> CreateFakeTable(
CompactSchedEventFormat compact_format =
InvalidCompactSchedEventFormatForTesting()) {
std::vector<Field> common_fields;
std::vector<Event> events;
{
Event event = {};
event.name = "sched_switch";
event.group = "sched";
event.ftrace_event_id = kFakeSchedSwitchEventId;
events.push_back(event);
}
{
Event event = {};
event.name = "sched_wakeup";
event.group = "sched";
event.ftrace_event_id = 10;
events.push_back(event);
}
{
Event event = {};
event.name = "sched_new";
event.group = "sched";
event.ftrace_event_id = 11;
events.push_back(event);
}
{
Event event = {};
event.name = "cgroup_mkdir";
event.group = "cgroup";
event.ftrace_event_id = kCgroupMkdirEventId;
events.push_back(event);
}
{
Event event = {};
event.name = "mm_vmscan_direct_reclaim_begin";
event.group = "vmscan";
event.ftrace_event_id = 13;
events.push_back(event);
}
{
Event event = {};
event.name = "lowmemory_kill";
event.group = "lowmemorykiller";
event.ftrace_event_id = 14;
events.push_back(event);
}
{
Event event = {};
event.name = "print";
event.group = "ftrace";
event.ftrace_event_id = kFakePrintEventId;
events.push_back(event);
}
{
Event event = {};
event.name = "sys_enter";
event.group = "raw_syscalls";
event.ftrace_event_id = kSysEnterId;
events.push_back(event);
}
return std::unique_ptr<ProtoTranslationTable>(new ProtoTranslationTable(
&ftrace_, events, std::move(common_fields),
ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
compact_format, PrintkMap()));
}
NiceMock<MockFtraceProcfs> ftrace_;
NiceMock<MockAtraceWrapper> atrace_wrapper_;
};
TEST_F(FtraceConfigMuxerTest, SecondaryInstanceDoNotSupportAtrace) {
auto fake_table = CreateFakeTable();
FtraceConfigMuxer model(&ftrace_, &atrace_wrapper_, fake_table.get(),
GetSyscallTable(), {},
/* secondary_instance= */ true);
FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
*config.add_atrace_categories() = "sched";
ASSERT_FALSE(model.SetupConfig(/* id= */ 73, config));
}
TEST_F(FtraceConfigMuxerTest, CompactSchedConfig) {
// Set scheduling event format as validated. The pre-parsed format itself
// doesn't need to be sensible, as the tests won't use it.
auto format_with_id = CompactSchedSwitchFormat{};
format_with_id.event_id = kFakeSchedSwitchEventId;
auto valid_compact_format = CompactSchedEventFormat{
/*format_valid=*/true, format_with_id, CompactSchedWakingFormat{}};
std::unique_ptr<ProtoTranslationTable> table =
CreateFakeTable(valid_compact_format);
FtraceConfigMuxer muxer(&ftrace_, &atrace_wrapper_, table.get(),
GetSyscallTable(), {});
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
{
// Explicitly enabled.
FtraceConfig cfg = CreateFtraceConfig({"sched/sched_switch"});
cfg.mutable_compact_sched()->set_enabled(true);
FtraceConfigId id = 42;
ASSERT_TRUE(muxer.SetupConfig(id, cfg));
const FtraceDataSourceConfig* ds_config = muxer.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_TRUE(ds_config->compact_sched.enabled);
}
{
// Implicitly enabled (default).
FtraceConfig cfg = CreateFtraceConfig({"sched/sched_switch"});
FtraceConfigId id = 43;
ASSERT_TRUE(muxer.SetupConfig(id, cfg));
const FtraceDataSourceConfig* ds_config = muxer.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_TRUE(ds_config->compact_sched.enabled);
}
{
// Explicitly disabled.
FtraceConfig cfg = CreateFtraceConfig({"sched/sched_switch"});
cfg.mutable_compact_sched()->set_enabled(false);
FtraceConfigId id = 44;
ASSERT_TRUE(muxer.SetupConfig(id, cfg));
const FtraceDataSourceConfig* ds_config = muxer.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_FALSE(ds_config->compact_sched.enabled);
}
{
// Disabled if not recording sched_switch.
FtraceConfig cfg = CreateFtraceConfig({});
FtraceConfigId id = 45;
ASSERT_TRUE(muxer.SetupConfig(id, cfg));
const FtraceDataSourceConfig* ds_config = muxer.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Not(Contains(kFakeSchedSwitchEventId)));
EXPECT_FALSE(ds_config->compact_sched.enabled);
}
}
// Fixture that constructs a FtraceConfigMuxer with a fake
// ProtoTranslationTable.
class FtraceConfigMuxerFakeTableTest : public FtraceConfigMuxerTest {
protected:
std::unique_ptr<ProtoTranslationTable> table_ = CreateFakeTable();
FtraceConfigMuxer model_ = FtraceConfigMuxer(&ftrace_,
&atrace_wrapper_,
table_.get(),
GetSyscallTable(),
{});
};
TEST_F(FtraceConfigMuxerFakeTableTest, GenericSyscallFiltering) {
FtraceConfig config = CreateFtraceConfig({"raw_syscalls/sys_enter"});
*config.add_syscall_events() = "sys_open";
*config.add_syscall_events() = "sys_read";
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
EXPECT_CALL(ftrace_, WriteToFile(_, _)).WillRepeatedly(Return(true));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/raw_syscalls/sys_enter/filter",
"id == 0 || id == 1"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/raw_syscalls/sys_exit/filter",
"id == 0 || id == 1"));
FtraceConfigId id = 37;
ASSERT_TRUE(model_.SetupConfig(id, config));
ASSERT_TRUE(model_.ActivateConfig(id));
const std::set<size_t>& filter = model_.GetSyscallFilterForTesting();
ASSERT_THAT(filter, UnorderedElementsAre(0, 1));
}
TEST_F(FtraceConfigMuxerFakeTableTest, UnknownSyscallFilter) {
FtraceConfig config = CreateFtraceConfig({"raw_syscalls/sys_enter"});
config.add_syscall_events("sys_open");
config.add_syscall_events("sys_not_a_call");
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
// Unknown syscall is ignored.
ASSERT_TRUE(model_.SetupConfig(/*id = */ 73, config));
ASSERT_THAT(model_.GetSyscallFilterForTesting(), UnorderedElementsAre(0));
}
TEST_F(FtraceConfigMuxerFakeTableTest, SyscallFilterMuxing) {
FtraceConfig empty_config = CreateFtraceConfig({});
FtraceConfig syscall_config = empty_config;
syscall_config.add_ftrace_events("raw_syscalls/sys_enter");
FtraceConfig syscall_open_config = syscall_config;
syscall_open_config.add_syscall_events("sys_open");
FtraceConfig syscall_read_config = syscall_config;
syscall_read_config.add_syscall_events("sys_read");
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
// Expect no filter for non-syscall config.
ASSERT_TRUE(model_.SetupConfig(/* id= */ 179239, empty_config));
ASSERT_THAT(model_.GetSyscallFilterForTesting(), UnorderedElementsAre());
// Expect no filter for syscall config with no specified events.
FtraceConfigId syscall_id = 73;
ASSERT_TRUE(model_.SetupConfig(syscall_id, syscall_config));
ASSERT_THAT(model_.GetSyscallFilterForTesting(), UnorderedElementsAre());
// Still expect no filter to satisfy this and the above.
FtraceConfigId syscall_open_id = 101;
ASSERT_TRUE(model_.SetupConfig(syscall_open_id, syscall_open_config));
ASSERT_THAT(model_.GetSyscallFilterForTesting(), UnorderedElementsAre());
// After removing the generic syscall trace, only the one with filter is left.
ASSERT_TRUE(model_.RemoveConfig(syscall_id));
ASSERT_THAT(model_.GetSyscallFilterForTesting(), UnorderedElementsAre(0));
// With sys_read and sys_open traced separately, filter includes both.
FtraceConfigId syscall_read_id = 57;
ASSERT_TRUE(model_.SetupConfig(syscall_read_id, syscall_read_config));
ASSERT_THAT(model_.GetSyscallFilterForTesting(), UnorderedElementsAre(0, 1));
// After removing configs with filters, filter is reset to empty.
ASSERT_TRUE(model_.RemoveConfig(syscall_open_id));
ASSERT_TRUE(model_.RemoveConfig(syscall_read_id));
ASSERT_THAT(model_.GetSyscallFilterForTesting(), UnorderedElementsAre());
}
TEST_F(FtraceConfigMuxerFakeTableTest, TurnFtraceOnOff) {
FtraceConfig config = CreateFtraceConfig({"sched_switch", "foo"});
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/sched/sched_switch/enable", "1"));
FtraceConfigId id = 97;
ASSERT_TRUE(model_.SetupConfig(id, config));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
ElementsAreArray({kFakeSchedSwitchEventId}));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
ASSERT_THAT(central_filter->GetEnabledEvents(),
ElementsAreArray({kFakeSchedSwitchEventId}));
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&ftrace_));
EXPECT_CALL(ftrace_, NumberOfCpus()).Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_percent", _))
.WillRepeatedly(Return(true));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/sched/sched_switch/enable", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", PageSizeKb()));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.RemoveConfig(id));
}
TEST_F(FtraceConfigMuxerFakeTableTest, FtraceIsAlreadyOn) {
FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
// If someone is using ftrace already don't stomp on what they are doing.
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("function"));
ASSERT_FALSE(model_.SetupConfig(/* id= */ 123, config));
}
TEST_F(FtraceConfigMuxerFakeTableTest, Atrace) {
FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
*config.add_atrace_categories() = "sched";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
EXPECT_CALL(atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "sched"}),
_))
.WillOnce(Return(true));
FtraceConfigId id = 57;
ASSERT_TRUE(model_.SetupConfig(id, config));
// "ftrace" group events are always enabled, and therefore the "print" event
// will show up in the per data source event filter (as we want to record it),
// but not the central filter (as we're not enabling/disabling it).
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakePrintEventId));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
EXPECT_THAT(central_filter->GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id));
}
TEST_F(FtraceConfigMuxerFakeTableTest, AtraceTwoApps) {
FtraceConfig config = CreateFtraceConfig({});
*config.add_atrace_apps() = "com.google.android.gms.persistent";
*config.add_atrace_apps() = "com.google.android.gms";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray(
{"atrace", "--async_start", "--only_userspace", "-a",
"com.google.android.gms,com.google.android.gms.persistent"}),
_))
.WillOnce(Return(true));
FtraceConfigId id = 97;
ASSERT_TRUE(model_.SetupConfig(id, config));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakePrintEventId));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id));
}
TEST_F(FtraceConfigMuxerFakeTableTest, AtraceMultipleConfigs) {
FtraceConfig config_a = CreateFtraceConfig({});
*config_a.add_atrace_apps() = "app_a";
*config_a.add_atrace_categories() = "cat_a";
FtraceConfig config_b = CreateFtraceConfig({});
*config_b.add_atrace_apps() = "app_b";
*config_b.add_atrace_categories() = "cat_b";
FtraceConfig config_c = CreateFtraceConfig({});
*config_c.add_atrace_apps() = "app_c";
*config_c.add_atrace_categories() = "cat_c";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_a", "-a", "app_a"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_a = 3;
ASSERT_TRUE(model_.SetupConfig(id_a, config_a));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_a", "cat_b", "-a", "app_a,app_b"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_b = 13;
ASSERT_TRUE(model_.SetupConfig(id_b, config_b));
EXPECT_CALL(atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "cat_a", "cat_b",
"cat_c", "-a", "app_a,app_b,app_c"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_c = 23;
ASSERT_TRUE(model_.SetupConfig(id_c, config_c));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_a", "cat_c", "-a", "app_a,app_c"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_b));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_c", "-a", "app_c"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_a));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_c));
}
TEST_F(FtraceConfigMuxerFakeTableTest, AtraceFailedConfig) {
FtraceConfig config_a = CreateFtraceConfig({});
*config_a.add_atrace_apps() = "app_1";
*config_a.add_atrace_apps() = "app_2";
*config_a.add_atrace_categories() = "cat_1";
*config_a.add_atrace_categories() = "cat_2";
FtraceConfig config_b = CreateFtraceConfig({});
*config_b.add_atrace_apps() = "app_fail";
*config_b.add_atrace_categories() = "cat_fail";
FtraceConfig config_c = CreateFtraceConfig({});
*config_c.add_atrace_apps() = "app_1";
*config_c.add_atrace_apps() = "app_3";
*config_c.add_atrace_categories() = "cat_1";
*config_c.add_atrace_categories() = "cat_3";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "cat_2", "-a", "app_1,app_2"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_a = 7;
ASSERT_TRUE(model_.SetupConfig(id_a, config_a));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "cat_2", "cat_fail", "-a",
"app_1,app_2,app_fail"}),
_))
.WillOnce(Return(false));
FtraceConfigId id_b = 17;
ASSERT_TRUE(model_.SetupConfig(id_b, config_b));
EXPECT_CALL(atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "cat_1", "cat_2",
"cat_3", "-a", "app_1,app_2,app_3"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_c = 47;
ASSERT_TRUE(model_.SetupConfig(id_c, config_c));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "cat_2", "-a", "app_1,app_2"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_c));
// Removing the config we failed to enable doesn't change the atrace state
// so we don't expect a call here.
ASSERT_TRUE(model_.RemoveConfig(id_b));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_a));
}
TEST_F(FtraceConfigMuxerFakeTableTest, AtraceDuplicateConfigs) {
FtraceConfig config_a = CreateFtraceConfig({});
*config_a.add_atrace_apps() = "app_1";
*config_a.add_atrace_categories() = "cat_1";
FtraceConfig config_b = CreateFtraceConfig({});
*config_b.add_atrace_apps() = "app_1";
*config_b.add_atrace_categories() = "cat_1";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "-a", "app_1"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_a = 19;
ASSERT_TRUE(model_.SetupConfig(id_a, config_a));
FtraceConfigId id_b = 29;
ASSERT_TRUE(model_.SetupConfig(id_b, config_b));
ASSERT_TRUE(model_.RemoveConfig(id_a));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_b));
}
TEST_F(FtraceConfigMuxerFakeTableTest, AtraceAndFtraceConfigs) {
FtraceConfig config_a = CreateFtraceConfig({"sched/sched_cpu_hotplug"});
FtraceConfig config_b = CreateFtraceConfig({"sched/sched_switch"});
*config_b.add_atrace_categories() = "b";
FtraceConfig config_c = CreateFtraceConfig({"sched/sched_switch"});
FtraceConfig config_d = CreateFtraceConfig({"sched/sched_cpu_hotplug"});
*config_d.add_atrace_categories() = "d";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
FtraceConfigId id_a = 179;
ASSERT_TRUE(model_.SetupConfig(id_a, config_a));
EXPECT_CALL(atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "b"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_b = 239;
ASSERT_TRUE(model_.SetupConfig(id_b, config_b));
FtraceConfigId id_c = 101;
ASSERT_TRUE(model_.SetupConfig(id_c, config_c));
EXPECT_CALL(atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "b", "d"}),
_))
.WillOnce(Return(true));
FtraceConfigId id_d = 47;
ASSERT_TRUE(model_.SetupConfig(id_d, config_d));
EXPECT_CALL(atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "b"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_d));
ASSERT_TRUE(model_.RemoveConfig(id_c));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_b));
ASSERT_TRUE(model_.RemoveConfig(id_a));
}
TEST_F(FtraceConfigMuxerFakeTableTest, AtraceErrorsPropagated) {
FtraceConfig config = CreateFtraceConfig({});
*config.add_atrace_categories() = "cat_1";
*config.add_atrace_categories() = "cat_2";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "cat_2"}),
_))
.WillOnce(Invoke([](const std::vector<std::string>&, std::string* err) {
EXPECT_NE(err, nullptr);
if (err)
err->append("foo\nbar\n");
return true;
}));
FtraceSetupErrors errors{};
FtraceConfigId id_a = 23;
ASSERT_TRUE(model_.SetupConfig(id_a, config, &errors));
EXPECT_EQ(errors.atrace_errors, "foo\nbar\n");
}
TEST_F(FtraceConfigMuxerFakeTableTest, AtracePreferTrackEvent) {
const FtraceConfigId id_a = 3;
FtraceConfig config_a = CreateFtraceConfig({});
*config_a.add_atrace_categories() = "cat_1";
*config_a.add_atrace_categories() = "cat_2";
*config_a.add_atrace_categories() = "cat_3";
*config_a.add_atrace_categories_prefer_sdk() = "cat_1";
*config_a.add_atrace_categories_prefer_sdk() = "cat_2";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "cat_2", "cat_3"}),
_))
.WillOnce(Return(true));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_2"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.SetupConfig(id_a, config_a));
const FtraceConfigId id_b = 13;
FtraceConfig config_b = CreateFtraceConfig({});
*config_b.add_atrace_categories() = "cat_1";
*config_b.add_atrace_categories() = "cat_2";
*config_b.add_atrace_categories() = "cat_3";
*config_b.add_atrace_categories_prefer_sdk() = "cat_2";
*config_b.add_atrace_categories_prefer_sdk() = "cat_3";
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_2"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.SetupConfig(id_b, config_b));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_2"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_b));
const FtraceConfigId id_c = 13;
FtraceConfig config_c = CreateFtraceConfig({});
*config_c.add_atrace_categories() = "cat_1";
*config_c.add_atrace_categories() = "cat_2";
*config_c.add_atrace_categories() = "cat_3";
*config_c.add_atrace_categories() = "cat_4";
*config_c.add_atrace_categories_prefer_sdk() = "cat_1";
*config_c.add_atrace_categories_prefer_sdk() = "cat_3";
*config_c.add_atrace_categories_prefer_sdk() = "cat_4";
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "cat_2", "cat_3", "cat_4"}),
_))
.WillOnce(Return(true));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_4"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.SetupConfig(id_c, config_c));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
"cat_1", "cat_2", "cat_3"}),
_))
.WillOnce(Return(true));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_2"}),
_))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_c));
EXPECT_CALL(
atrace_wrapper_,
RunAtrace(
ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
EXPECT_CALL(atrace_wrapper_,
RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model_.RemoveConfig(id_a));
}
TEST_F(FtraceConfigMuxerFakeTableTest, SetupClockForTesting) {
FtraceConfig config;
namespace pb0 = protos::pbzero;
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
model_.SetupClockForTesting(config);
// unspecified = boot.
EXPECT_EQ(model_.ftrace_clock(),
static_cast<int>(pb0::FTRACE_CLOCK_UNSPECIFIED));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global"));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "global"));
model_.SetupClockForTesting(config);
EXPECT_EQ(model_.ftrace_clock(), static_cast<int>(pb0::FTRACE_CLOCK_GLOBAL));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return(""));
model_.SetupClockForTesting(config);
EXPECT_EQ(model_.ftrace_clock(), static_cast<int>(pb0::FTRACE_CLOCK_UNKNOWN));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("local [global]"));
model_.SetupClockForTesting(config);
EXPECT_EQ(model_.ftrace_clock(), static_cast<int>(pb0::FTRACE_CLOCK_GLOBAL));
}
TEST_F(FtraceConfigMuxerFakeTableTest, GetFtraceEvents) {
FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
std::set<GroupAndName> events =
model_.GetFtraceEventsForTesting(config, table_.get());
EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
EXPECT_THAT(events, Not(Contains(GroupAndName("ftrace", "print"))));
}
TEST_F(FtraceConfigMuxerFakeTableTest, GetFtraceEventsAtrace) {
FtraceConfig config = CreateFtraceConfig({});
*config.add_atrace_categories() = "sched";
std::set<GroupAndName> events =
model_.GetFtraceEventsForTesting(config, table_.get());
EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_cpu_hotplug")));
EXPECT_THAT(events, Contains(GroupAndName("ftrace", "print")));
}
TEST_F(FtraceConfigMuxerFakeTableTest, GetFtraceEventsAtraceCategories) {
FtraceConfig config = CreateFtraceConfig({});
*config.add_atrace_categories() = "sched";
*config.add_atrace_categories() = "memreclaim";
std::set<GroupAndName> events =
model_.GetFtraceEventsForTesting(config, table_.get());
EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_cpu_hotplug")));
EXPECT_THAT(events, Contains(GroupAndName("cgroup", "cgroup_mkdir")));
EXPECT_THAT(events, Contains(GroupAndName("vmscan",
"mm_vmscan_direct_reclaim_begin")));
EXPECT_THAT(events,
Contains(GroupAndName("lowmemorykiller", "lowmemory_kill")));
EXPECT_THAT(events, Contains(GroupAndName("ftrace", "print")));
}
// Tests the enabling fallback logic that tries to use the "set_event" interface
// if writing the individual xxx/enable file fails.
TEST_F(FtraceConfigMuxerFakeTableTest, FallbackOnSetEvent) {
FtraceConfig config =
CreateFtraceConfig({"sched/sched_switch", "cgroup/cgroup_mkdir"});
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_percent", _))
.WillRepeatedly(Return(true));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/sched/sched_switch/enable", "1"));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/cgroup/cgroup_mkdir/enable", "1"))
.WillOnce(Return(false));
EXPECT_CALL(ftrace_, AppendToFile("/root/set_event", "cgroup:cgroup_mkdir"))
.WillOnce(Return(true));
FtraceConfigId id = 97;
ASSERT_TRUE(model_.SetupConfig(id, config));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kCgroupMkdirEventId));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
EXPECT_THAT(central_filter->GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_THAT(central_filter->GetEnabledEvents(),
Contains(kCgroupMkdirEventId));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/sched/sched_switch/enable", "0"));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/cgroup/cgroup_mkdir/enable", "0"))
.WillOnce(Return(false));
EXPECT_CALL(ftrace_, AppendToFile("/root/set_event", "!cgroup:cgroup_mkdir"))
.WillOnce(Return(true));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", PageSizeKb()));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.RemoveConfig(id));
}
TEST_F(FtraceConfigMuxerFakeTableTest, CompactSchedConfigWithInvalidFormat) {
// Request compact encoding.
FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
config.mutable_compact_sched()->set_enabled(true);
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
FtraceConfigId id = 67;
ASSERT_TRUE(model_.SetupConfig(id, config));
// The translation table says that the scheduling events' format didn't match
// compile-time assumptions, so we won't enable compact events even if
// requested.
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(kFakeSchedSwitchEventId));
EXPECT_FALSE(ds_config->compact_sched.enabled);
}
TEST_F(FtraceConfigMuxerFakeTableTest, SkipGenericEventsOption) {
static constexpr int kFtraceGenericEventId = 42;
ON_CALL(ftrace_, ReadEventFormat("sched", "generic"))
.WillByDefault(Return(R"(name: generic
ID: 42
format:
field:int common_pid; offset:0; size:4; signed:1;
field:u32 field_a; offset:4; size:4; signed:0;
field:int field_b; offset:8; size:4; signed:1;
print fmt: "unused")"));
// Data source asking for one known and one generic event.
FtraceConfig config_default =
CreateFtraceConfig({"sched/sched_switch", "sched/generic"});
// As above, but with an option to suppress generic events.
FtraceConfig config_with_disable =
CreateFtraceConfig({"sched/sched_switch", "sched/generic"});
config_with_disable.set_disable_generic_events(true);
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
{
FtraceConfigId id = 123;
ASSERT_TRUE(model_.SetupConfig(id, config_default));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
// Both events enabled for the data source by default.
EXPECT_THAT(
ds_config->event_filter.GetEnabledEvents(),
UnorderedElementsAre(kFakeSchedSwitchEventId, kFtraceGenericEventId));
}
{
FtraceConfigId id = 321;
ASSERT_TRUE(model_.SetupConfig(id, config_with_disable));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
// Only the statically known event is enabled.
EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
UnorderedElementsAre(kFakeSchedSwitchEventId));
}
}
TEST_F(FtraceConfigMuxerFakeTableTest, Funcgraph) {
FtraceConfig config;
config.set_enable_function_graph(true);
*config.add_function_filters() = "sched*";
*config.add_function_filters() = "handle_mm_fault";
*config.add_function_graph_roots() = "sched*";
*config.add_function_graph_roots() = "*mm_fault";
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
EXPECT_CALL(ftrace_, WriteToFile(_, _)).WillRepeatedly(Return(true));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
// Set up config, assert that the tracefs writes happened:
EXPECT_CALL(ftrace_, ClearFile("/root/set_ftrace_filter"));
EXPECT_CALL(ftrace_, ClearFile("/root/set_graph_function"));
EXPECT_CALL(ftrace_, AppendToFile("/root/set_ftrace_filter",
"sched*\nhandle_mm_fault"))
.WillOnce(Return(true));
EXPECT_CALL(ftrace_,
AppendToFile("/root/set_graph_function", "sched*\n*mm_fault"))
.WillOnce(Return(true));
EXPECT_CALL(ftrace_, WriteToFile("/root/current_tracer", "function_graph"))
.WillOnce(Return(true));
FtraceConfigId id = 43;
ASSERT_TRUE(model_.SetupConfig(id, config));
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&ftrace_));
// Toggle config on and off, tracer won't be reset yet:
ASSERT_TRUE(model_.ActivateConfig(id));
ASSERT_TRUE(model_.RemoveConfig(id));
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&ftrace_));
// Emulate ftrace_controller's call to ResetCurrentTracer (see impl comments
// for why RemoveConfig is insufficient).
EXPECT_CALL(ftrace_, ClearFile("/root/set_ftrace_filter"));
EXPECT_CALL(ftrace_, ClearFile("/root/set_graph_function"));
EXPECT_CALL(ftrace_, WriteToFile("/root/current_tracer", "nop"))
.WillOnce(Return(true));
ASSERT_TRUE(model_.ResetCurrentTracer());
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&ftrace_));
}
TEST_F(FtraceConfigMuxerFakeTableTest, PreserveFtraceBufferNotSetBufferSizeKb) {
FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
config.set_preserve_ftrace_buffer(true);
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _)).Times(0);
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/sched/sched_switch/enable", "1"));
FtraceConfigId id = 44;
ASSERT_TRUE(model_.SetupConfig(id, config));
}
TEST_F(FtraceConfigMuxerFakeTableTest, KprobeNamesReserved) {
FtraceConfig config = CreateFtraceConfig(
{"perfetto_kprobes/fuse_file_write_iter",
"perfetto_kretprobes/fuse_file_write_iter", "unknown/unknown"});
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
FtraceSetupErrors errors{};
FtraceConfigId id_a = 23;
ASSERT_TRUE(model_.SetupConfig(id_a, config, &errors));
// These event fail because the names "perfetto_kprobes" and
// "perfetto_kretprobes" are used internally by perfetto.
EXPECT_THAT(errors.failed_ftrace_events,
IsSupersetOf({"perfetto_kprobes/fuse_file_write_iter",
"perfetto_kretprobes/fuse_file_write_iter"}));
// This event is just unknown
EXPECT_THAT(errors.unknown_ftrace_events, ElementsAre("unknown/unknown"));
}
// Fixture that constructs a FtraceConfigMuxer with a mock
// ProtoTranslationTable.
class FtraceConfigMuxerMockTableTest : public FtraceConfigMuxerTest {
protected:
std::unique_ptr<MockProtoTranslationTable> mock_table_ = GetMockTable();
FtraceConfigMuxer model_ = FtraceConfigMuxer(&ftrace_,
&atrace_wrapper_,
mock_table_.get(),
GetSyscallTable(),
{});
};
TEST_F(FtraceConfigMuxerMockTableTest, AddGenericEvent) {
FtraceConfig config = CreateFtraceConfig({"power/cpu_frequency"});
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/power/cpu_frequency/enable", "1"));
EXPECT_CALL(*mock_table_, GetEvent(GroupAndName("power", "cpu_frequency")))
.Times(AnyNumber());
static constexpr int kExpectedEventId = 77;
Event event_to_return;
event_to_return.name = "cpu_frequency";
event_to_return.group = "power";
event_to_return.ftrace_event_id = kExpectedEventId;
ON_CALL(*mock_table_,
GetOrCreateEvent(GroupAndName("power", "cpu_frequency")))
.WillByDefault(Return(&event_to_return));
EXPECT_CALL(*mock_table_,
GetOrCreateEvent(GroupAndName("power", "cpu_frequency")));
FtraceConfigId id = 7;
ASSERT_TRUE(model_.SetupConfig(id, config));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
ElementsAreArray({kExpectedEventId}));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
ASSERT_THAT(central_filter->GetEnabledEvents(),
ElementsAreArray({kExpectedEventId}));
}
class FtraceConfigMuxerMockTableParamTest
: public FtraceConfigMuxerMockTableTest,
public testing::WithParamInterface<
std::pair<perfetto::protos::gen::FtraceConfig_KprobeEvent_KprobeType,
std::string>> {};
TEST_P(FtraceConfigMuxerMockTableParamTest, AddKprobeEvent) {
auto kprobe_type = std::get<0>(GetParam());
std::string group_name(std::get<1>(GetParam()));
FtraceConfig config;
FtraceConfig::KprobeEvent kprobe_event;
kprobe_event.set_probe("fuse_file_write_iter");
kprobe_event.set_type(kprobe_type);
*config.add_kprobe_events() = kprobe_event;
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/" + group_name +
"/fuse_file_write_iter/enable",
"1"));
EXPECT_CALL(*mock_table_, GetEvent(GroupAndName("power", "cpu_frequency")))
.Times(AnyNumber());
static constexpr int kExpectedEventId = 77;
Event event_to_return_kprobe;
event_to_return_kprobe.name = "fuse_file_write_iter";
event_to_return_kprobe.group = group_name.c_str();
event_to_return_kprobe.ftrace_event_id = kExpectedEventId;
EXPECT_CALL(*mock_table_, GetOrCreateKprobeEvent(GroupAndName(
group_name, "fuse_file_write_iter")))
.WillOnce(Return(&event_to_return_kprobe));
FtraceConfigId id = 7;
ASSERT_TRUE(model_.SetupConfig(id, config));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
ElementsAre(kExpectedEventId));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
ASSERT_THAT(central_filter->GetEnabledEvents(),
ElementsAre(kExpectedEventId));
}
INSTANTIATE_TEST_SUITE_P(
KprobeTypes,
FtraceConfigMuxerMockTableParamTest,
testing::Values(
std::make_pair(
protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_KPROBE,
kKprobeGroup),
std::make_pair(
protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_KRETPROBE,
kKretprobeGroup)));
TEST_F(FtraceConfigMuxerMockTableTest, AddKprobeBothEvent) {
FtraceConfig config;
FtraceConfig::KprobeEvent kprobe_event;
kprobe_event.set_probe("fuse_file_write_iter");
kprobe_event.set_type(
protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_BOTH);
*config.add_kprobe_events() = kprobe_event;
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
EXPECT_CALL(
ftrace_,
WriteToFile("/root/events/perfetto_kprobes/fuse_file_write_iter/enable",
"1"));
EXPECT_CALL(
ftrace_,
WriteToFile(
"/root/events/perfetto_kretprobes/fuse_file_write_iter/enable", "1"));
EXPECT_CALL(
ftrace_,
AppendToFile(
"/root/kprobe_events",
"p:perfetto_kprobes/fuse_file_write_iter fuse_file_write_iter"));
EXPECT_CALL(
ftrace_,
AppendToFile("/root/kprobe_events",
std::string("r") + std::string(kKretprobeDefaultMaxactives) +
":perfetto_kretprobes/fuse_file_write_iter "
"fuse_file_write_iter"));
std::string g1(kKprobeGroup);
static constexpr int kExpectedEventId = 77;
Event event_to_return_kprobe;
event_to_return_kprobe.name = "fuse_file_write_iter";
event_to_return_kprobe.group = g1.c_str();
event_to_return_kprobe.ftrace_event_id = kExpectedEventId;
EXPECT_CALL(*mock_table_, GetOrCreateKprobeEvent(GroupAndName(
"perfetto_kprobes", "fuse_file_write_iter")))
.WillOnce(Return(&event_to_return_kprobe));
std::string g2(kKretprobeGroup);
static constexpr int kExpectedEventId2 = 78;
Event event_to_return_kretprobe;
event_to_return_kretprobe.name = "fuse_file_write_iter";
event_to_return_kretprobe.group = g2.c_str();
event_to_return_kretprobe.ftrace_event_id = kExpectedEventId2;
EXPECT_CALL(*mock_table_, GetOrCreateKprobeEvent(GroupAndName(
"perfetto_kretprobes", "fuse_file_write_iter")))
.WillOnce(Return(&event_to_return_kretprobe));
FtraceConfigId id = 7;
ASSERT_TRUE(model_.SetupConfig(id, config));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
UnorderedElementsAre(kExpectedEventId, kExpectedEventId2));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
ASSERT_THAT(central_filter->GetEnabledEvents(),
UnorderedElementsAre(kExpectedEventId, kExpectedEventId2));
}
TEST_F(FtraceConfigMuxerMockTableTest, AddAllEvents) {
FtraceConfig config = CreateFtraceConfig({"sched/*"});
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('1'));
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.WillByDefault(Return("[local] global boot"));
EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
.Times(AnyNumber());
EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/sched/sched_switch/enable", "1"));
EXPECT_CALL(ftrace_,
WriteToFile("/root/events/sched/sched_new_event/enable", "1"));
std::set<std::string> n = {"sched_switch", "sched_new_event"};
ON_CALL(ftrace_, GetEventNamesForGroup("events/sched"))
.WillByDefault(Return(n));
EXPECT_CALL(ftrace_, GetEventNamesForGroup("events/sched")).Times(1);
// Non-generic event.
static constexpr int kSchedSwitchEventId = 1;
Event sched_switch = {"sched_switch", "sched", {}, 0, 0, 0};
sched_switch.ftrace_event_id = kSchedSwitchEventId;
ON_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("sched", "sched_switch")))
.WillByDefault(Return(&sched_switch));
EXPECT_CALL(*mock_table_,
GetOrCreateEvent(GroupAndName("sched", "sched_switch")))
.Times(AnyNumber());
// Generic event.
static constexpr int kGenericEventId = 2;
Event event_to_return;
event_to_return.name = "sched_new_event";
event_to_return.group = "sched";
event_to_return.ftrace_event_id = kGenericEventId;
ON_CALL(*mock_table_,
GetOrCreateEvent(GroupAndName("sched", "sched_new_event")))
.WillByDefault(Return(&event_to_return));
EXPECT_CALL(*mock_table_,
GetOrCreateEvent(GroupAndName("sched", "sched_new_event")));
FtraceConfigId id = 13;
ASSERT_TRUE(model_.SetupConfig(id, config));
ASSERT_TRUE(id);
EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
ElementsAreArray({kSchedSwitchEventId, kGenericEventId}));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
ASSERT_THAT(central_filter->GetEnabledEvents(),
ElementsAreArray({kSchedSwitchEventId, kGenericEventId}));
}
TEST_F(FtraceConfigMuxerMockTableTest, TwoWildcardGroups) {
FtraceConfig config = CreateFtraceConfig({"group_one/*", "group_two/*"});
std::set<std::string> event_names = {"foo"};
ON_CALL(ftrace_, GetEventNamesForGroup("events/group_one"))
.WillByDefault(Return(event_names));
EXPECT_CALL(ftrace_, GetEventNamesForGroup("events/group_one"))
.Times(AnyNumber());
ON_CALL(ftrace_, GetEventNamesForGroup("events/group_two"))
.WillByDefault(Return(event_names));
EXPECT_CALL(ftrace_, GetEventNamesForGroup("events/group_two"))
.Times(AnyNumber());
static constexpr int kEventId1 = 1;
Event event1;
event1.name = "foo";
event1.group = "group_one";
event1.ftrace_event_id = kEventId1;
ON_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_one", "foo")))
.WillByDefault(Return(&event1));
EXPECT_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_one", "foo")));
static constexpr int kEventId2 = 2;
Event event2;
event2.name = "foo";
event2.group = "group_two";
event2.ftrace_event_id = kEventId2;
ON_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_two", "foo")))
.WillByDefault(Return(&event2));
EXPECT_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_two", "foo")));
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
FtraceConfigId id = 23;
ASSERT_TRUE(model_.SetupConfig(id, config));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_TRUE(ds_config);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
ElementsAreArray({kEventId1, kEventId2}));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
ASSERT_THAT(central_filter->GetEnabledEvents(),
ElementsAreArray({kEventId1, kEventId2}));
}
TEST_F(FtraceConfigMuxerMockTableTest, AddSameNameEvents) {
FtraceConfig config = CreateFtraceConfig({"group_one/foo", "group_two/foo"});
static constexpr int kEventId1 = 1;
Event event1;
event1.name = "foo";
event1.group = "group_one";
event1.ftrace_event_id = kEventId1;
ON_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_one", "foo")))
.WillByDefault(Return(&event1));
EXPECT_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_one", "foo")));
static constexpr int kEventId2 = 2;
Event event2;
event2.name = "foo";
event2.group = "group_two";
event2.ftrace_event_id = kEventId2;
ON_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_two", "foo")))
.WillByDefault(Return(&event2));
EXPECT_CALL(*mock_table_, GetOrCreateEvent(GroupAndName("group_two", "foo")));
ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
.WillByDefault(Return("nop"));
ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
.WillByDefault(Return("0"));
FtraceConfigId id = 5;
ASSERT_TRUE(model_.SetupConfig(id, config));
ASSERT_TRUE(model_.ActivateConfig(id));
const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
ElementsAreArray({kEventId1, kEventId2}));
const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
ASSERT_THAT(central_filter->GetEnabledEvents(),
ElementsAreArray({kEventId1, kEventId2}));
}
} // namespace
} // namespace perfetto