blob: c76c3d08324441373fc0dc4cf5b15cbb7427ff3a [file] [log] [blame]
/*
* Copyright (C) 2025 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 "perfetto/base/build_config.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX)
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/thread_utils.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/ext/tracing/core/tracing_service.h"
#include "perfetto/tracing/core/data_source_config.h"
#include "src/base/test/test_task_runner.h"
#include "src/traced/probes/ftrace/ftrace_controller.h"
#include "test/gtest_and_gmock.h"
#include "test/test_helper.h"
#include "protos/perfetto/config/priority_boost/priority_boost_config.gen.h"
#include "protos/perfetto/config/trace_config.gen.h"
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
#include "test/android_test_utils.h"
#endif
namespace perfetto {
namespace base {
// For ASSERT_EQ()
inline bool operator==(const SchedOsHooks::SchedOsConfig& lhs,
const SchedOsHooks::SchedOsConfig& rhs) {
return std::tie(lhs.policy, lhs.rt_prio, lhs.nice) ==
std::tie(rhs.policy, rhs.rt_prio, rhs.nice);
}
// For ASSERT_EQ()
inline std::ostream& operator<<(std::ostream& os,
const SchedOsHooks::SchedOsConfig& s) {
return os << "SchedOsConfig{policy: " << s.policy << ", prio: " << s.rt_prio
<< ", nice: " << s.nice << "}";
}
inline std::string ToString(const SchedOsHooks::SchedOsConfig& cfg) {
std::stringstream ss;
ss << cfg;
return ss.str();
}
} // namespace base
namespace {
using ::testing::Eq;
using ::testing::NiceMock;
using ::testing::Return;
// We have two quite different code flows for this test, depending on the way it
// is being built and run.
//
// 1. When running as part of Android Tree (#if
// PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)), we test that the external,
// started by Android, traced and traced_probes daemons can change their
// priorities In this case we read /proc/%d/stat to query their state.
//
// 2. When running on Linux (#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)),
// we use MockSchedOsManager to check that the traced/traced_probes code changes
// their priority as expected.
// In this case, the traced/traced_probes are just the separate threads of the
// test binary. 'base::ScopedSchedBoost' doesn't expect that the priority is
// updated for the single thread and not the whole app, so we test only one
// thread at a time.
class PerfettoPriorityBoostIntegrationTest : public ::testing::Test {
public:
void SetUp() override {
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
base::ScopedSchedBoost::ResetForTesting(&sched_manager_);
#endif
}
base::SchedOsHooks::SchedOsConfig GetSchedInfo(int tid) {
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
PERFETTO_DCHECK(tid == sched_manager_.expected_boosted_thread);
return sched_manager_.current_config;
#else
return GetRealSchedInfo(tid);
#endif
}
static base::SchedOsHooks::SchedOsConfig GetRealSchedInfo(int tid) {
base::StackString<128> stat_path("/proc/%d/stat", tid);
std::string line;
bool ok = base::ReadFile(stat_path.c_str(), &line);
PERFETTO_DCHECK(ok);
std::vector parts = base::SplitString(line, " ");
int nice = base::StringToInt32(parts[18]).value();
int rt_prio = base::StringToInt32(parts[39]).value();
int policy = base::StringToInt32(parts[40]).value();
return base::SchedOsHooks::SchedOsConfig{policy, rt_prio, nice};
}
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
class MockSchedOsHooks : public base::SchedOsHooks {
public:
MockSchedOsHooks() : current_config(kInitConfig) {
ON_CALL(*this, GetCurrentSchedConfig()).WillByDefault([&] {
return current_config;
});
ON_CALL(*this, SetSchedConfig)
.WillByDefault([&](const SchedOsConfig& arg) {
PERFETTO_DCHECK(base::GetThreadId() == expected_boosted_thread);
current_config = arg;
return base::OkStatus();
});
}
MOCK_METHOD(base::Status,
SetSchedConfig,
(const SchedOsConfig&),
(override));
MOCK_METHOD(base::StatusOr<base::SchedOsHooks::SchedOsConfig>,
GetCurrentSchedConfig,
(),
(const, override));
~MockSchedOsHooks() override = default;
base::PlatformThreadId expected_boosted_thread = -1;
SchedOsConfig current_config;
static constexpr SchedOsConfig kInitConfig{SCHED_OTHER, 0, 0};
};
NiceMock<MockSchedOsHooks> sched_manager_;
#endif
};
constexpr char kTestDataSourceName[] = "linux.system_info";
TraceConfig CreateTraceConfigWithDataSourcePriorityBoost(
protos::gen::PriorityBoostConfig_BoostPolicy policy,
uint32_t priority) {
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(64);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name(kTestDataSourceName);
ds_config->set_target_buffer(0);
auto* ds_priority_boost_config = ds_config->mutable_priority_boost();
ds_priority_boost_config->set_policy(policy);
ds_priority_boost_config->set_priority(priority);
return trace_config;
}
void TestHelperStartTraceAndWaitForTraced(TestHelper& helper,
const TraceConfig& trace_config) {
static bool first_time = true;
if (first_time) {
first_time = false;
helper.StartServiceIfRequired();
}
helper.ConnectConsumer();
helper.WaitForConsumerConnect();
// Wait for the traced_probes service to connect. We want to start tracing
// only after it connects, otherwise we'll start a tracing session with 0
// producers connected (which is valid but not what we want here).
helper.WaitForDataSourceConnected(kTestDataSourceName);
helper.StartTracing(trace_config);
helper.WaitForAllDataSourceStarted();
}
} // namespace
TEST_F(PerfettoPriorityBoostIntegrationTest, TestTracedProbes) {
base::TestTaskRunner task_runner;
TestHelper helper_fifo_42(&task_runner);
TestHelper helper_other_7(&task_runner);
base::PlatformThreadId traced_probes_tid = -1;
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
ProbesProducerThread probes(GetTestProducerSockName());
probes.Connect();
traced_probes_tid = probes.runner()->GetThreadIdForTesting();
sched_manager_.expected_boosted_thread = traced_probes_tid;
#elif PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
traced_probes_tid = PidForProcessName("/system/bin/traced_probes");
#else
#error "Need to start daemons for Linux test or be built on Android."
#endif
ASSERT_NE(traced_probes_tid, -1);
base::SchedOsHooks::SchedOsConfig init_traced_probes_sched_info =
GetSchedInfo(traced_probes_tid);
TestHelperStartTraceAndWaitForTraced(
helper_fifo_42,
CreateTraceConfigWithDataSourcePriorityBoost(
protos::gen::PriorityBoostConfig::POLICY_SCHED_FIFO, 42));
TestHelperStartTraceAndWaitForTraced(
helper_other_7,
CreateTraceConfigWithDataSourcePriorityBoost(
protos::gen::PriorityBoostConfig::POLICY_SCHED_OTHER, 7));
{
auto traced_probes_sched_info_boosted = GetSchedInfo(traced_probes_tid);
ASSERT_THAT(traced_probes_sched_info_boosted,
Eq(base::SchedOsHooks::SchedOsConfig{SCHED_FIFO, 42, 0}));
}
helper_fifo_42.DisableTracing();
helper_fifo_42.WaitForTracingDisabled();
{
auto traced_probes_sched_info_stopped = GetSchedInfo(traced_probes_tid);
ASSERT_THAT(traced_probes_sched_info_stopped,
Eq(base::SchedOsHooks::SchedOsConfig{SCHED_OTHER, 0, -7}));
}
helper_other_7.DisableTracing();
helper_other_7.WaitForTracingDisabled();
{
auto traced_probes_sched_info_stopped_2 = GetSchedInfo(traced_probes_tid);
ASSERT_EQ(traced_probes_sched_info_stopped_2,
init_traced_probes_sched_info);
}
}
TEST_F(PerfettoPriorityBoostIntegrationTest, TestTraced) {
base::TestTaskRunner task_runner;
TestHelper helper(&task_runner);
helper.StartServiceIfRequired();
helper.ConnectConsumer();
helper.WaitForConsumerConnect();
base::PlatformThreadId traced_tid = -1;
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
traced_tid = helper.service_thread()->GetThreadIdForTesting();
sched_manager_.expected_boosted_thread = traced_tid;
#elif PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
traced_tid = PidForProcessName("/system/bin/traced");
#else
#error "Need to start daemons for Linux test or be built on Android."
#endif
ASSERT_NE(traced_tid, -1);
base::SchedOsHooks::SchedOsConfig init_traced_sched_info =
GetSchedInfo(traced_tid);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(64);
auto* priority_boost_config = trace_config.mutable_priority_boost();
priority_boost_config->set_policy(
protos::gen::PriorityBoostConfig::POLICY_SCHED_OTHER);
priority_boost_config->set_priority(13);
helper.StartTracing(trace_config);
helper.WaitForAllDataSourceStarted();
{
auto traced_sched_info_boosted = GetSchedInfo(traced_tid);
ASSERT_THAT(traced_sched_info_boosted,
Eq(base::SchedOsHooks::SchedOsConfig{SCHED_OTHER, 0, -13}));
}
helper.FreeBuffers();
helper.WaitForTracingDisabled();
// The tracing session is destroyed at this point, and the priority is
// restored to the initial value
auto traced_sched_info_stopped = GetSchedInfo(traced_tid);
ASSERT_EQ(traced_sched_info_stopped, init_traced_sched_info);
}
} // namespace perfetto
#endif // OS_ANDROID || OS_LINUX