blob: 5cdc75373614ffaf02710c52e92770d96c54518b [file] [log] [blame]
/*
* Copyright (C) 2024 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/trace_redaction/remap_scheduling_events.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "src/base/test/status_matchers.h"
#include "src/trace_redaction/trace_redaction_framework.h"
#include "test/gtest_and_gmock.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
#include "protos/perfetto/trace/ftrace/sched.gen.h"
namespace perfetto::trace_redaction {
template <class T>
class ThreadMergeTest {
protected:
struct Process {
uint64_t uid;
int32_t ppid;
int32_t pid;
};
base::Status Redact(protos::pbzero::FtraceEvent* event_message) {
T redact;
auto bundle_str = bundle_.SerializeAsString();
protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str);
auto event_str = bundle_.event().back().SerializeAsString();
protos::pbzero::FtraceEvent::Decoder event_decoder(event_str);
return redact.Redact(context_, bundle_decoder, event_decoder,
event_message);
}
Context context_;
protos::gen::FtraceEventBundle bundle_;
};
// All ftrace events have a timestamp and a pid. This test focuses on the
// event's pid value. When that pid doesn't belong to the target package, it
// should be replaced with a synthetic thread id.
//
// event {
// timestamp: 6702093743539938
// pid: 0
// sched_switch { ... }
// }
class ThreadMergeRemapFtraceEventPidTest
: public testing::Test,
protected ThreadMergeTest<ThreadMergeRemapFtraceEventPid> {
protected:
static constexpr uint32_t kCpu = 3;
static constexpr auto kTimestamp = 123456789;
// This process will be connected to the target package.
static constexpr Process kProcess = {12, 5, 7};
// This process will not be connected to the target package.
static constexpr Process kOtherProcess = {120, 50, 70};
void SetUp() override {
bundle_.add_event();
context_.package_uid = kProcess.uid;
context_.timeline = std::make_unique<ProcessThreadTimeline>();
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kProcess.pid, kProcess.ppid, kProcess.uid));
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kOtherProcess.pid, kOtherProcess.ppid, kOtherProcess.uid));
context_.timeline->Sort();
// Because kCpu is 3, it means that there are four CPUs (id 0, id 1, ...).
context_.synthetic_threads.emplace();
context_.synthetic_threads->tids.assign({100, 101, 102, 103});
}
};
// This should never happen, a bundle should always have a cpu. If it doesn't
// have a CPU, the event field should be dropped (safest option).
//
// TODO(vaage): This will create an invalid trace. It can also leak information
// if other primitives don't strip the remaining information. To be safe, these
// cases should be replaced with errors.
TEST_F(ThreadMergeRemapFtraceEventPidTest, MissingCpuReturnsError) {
// Do not call set_cpu(uint32_t value). There should be no cpu for this case.
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_FALSE(Redact(event_message.get()).ok());
}
// This should never happen, an event should always have a timestamp. If it
// doesn't have a timestamp, the event field should be dropped (safest option).
//
// TODO(vaage): This will create an invalid trace. It can also leak information
// if other primitives don't strip the remaining information. To be safe, these
// cases should be replaced with errors.
TEST_F(ThreadMergeRemapFtraceEventPidTest, MissingTimestampReturnsError) {
bundle_.set_cpu(kCpu);
// Do not call set_timestamp(uint64_t value). There should be no timestamp for
// this case.
bundle_.mutable_event()->back().set_pid(kProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_FALSE(Redact(event_message.get()).ok());
}
TEST_F(ThreadMergeRemapFtraceEventPidTest, NoopWhenPidIsInPackage) {
bundle_.set_cpu(kCpu);
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent event;
event.ParseFromString(event_message.SerializeAsString());
ASSERT_TRUE(event.has_pid());
ASSERT_EQ(static_cast<int32_t>(event.pid()), kProcess.pid);
}
TEST_F(ThreadMergeRemapFtraceEventPidTest, ChangesPidWhenPidIsOutsidePackage) {
bundle_.set_cpu(kCpu); // The CPU is used to select the pid.
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kOtherProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent event;
event.ParseFromString(event_message.SerializeAsString());
ASSERT_TRUE(event.has_pid());
ASSERT_EQ(static_cast<int32_t>(event.pid()),
context_.synthetic_threads->tids[kCpu]);
}
// When creating a sched_switch event, the event pid and the previous pid should
// be the same pid.
//
// event {
// timestamp: 6702093743539938
// pid: 0
// sched_switch {
// prev_comm: "swapper/7"
// prev_pid: 0
// prev_prio: 120
// prev_state: 0
// next_comm: "FMOD stream thr"
// next_pid: 7174
// next_prio: 104
// }
// }
class ThreadMergeRemapSchedSwitchPidTest
: public testing::Test,
protected ThreadMergeTest<ThreadMergeRemapSchedSwitchPid> {
protected:
static constexpr uint32_t kCpu = 3;
static constexpr auto kTimestamp = 123456789;
// This process will be connected to the target package.
static constexpr Process kPrevProcess = {12, 5, 7};
static constexpr Process kNextProcess = {12, 5, 8};
// This process will not be connected to the target package.
static constexpr Process kOtherProcess = {120, 50, 70};
void SetUp() override {
bundle_.add_event();
context_.package_uid = kPrevProcess.uid;
context_.timeline = std::make_unique<ProcessThreadTimeline>();
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kPrevProcess.pid, kPrevProcess.ppid, kPrevProcess.uid));
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kNextProcess.pid, kNextProcess.ppid, kNextProcess.uid));
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kOtherProcess.pid, kOtherProcess.ppid, kOtherProcess.uid));
context_.timeline->Sort();
// Because kCpu is 3, it means that there are four CPUs (id 0, id 1, ...).
context_.synthetic_threads.emplace();
context_.synthetic_threads->tids.assign({100, 101, 102, 103});
}
};
// This should never happen, a bundle should always have a cpu. If it doesn't
// have a CPU, the event field should be dropped (safest option).
//
// TODO(vaage): This will create an invalid trace. It can also leak information
// if other primitives don't strip the remaining information. To be safe, these
// cases should be replaced with errors.
TEST_F(ThreadMergeRemapSchedSwitchPidTest, MissingCpuReturnsError) {
// Do not call set_cpu(uint32_t value). There should be no cpu for this case.
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kPrevProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_FALSE(Redact(event_message.get()).ok());
}
// This should never happen, an event should always have a timestamp. If it
// doesn't have a timestamp, the event field should be dropped (safest option).
//
// TODO(vaage): This will create an invalid trace. It can also leak information
// if other primitives don't strip the remaining information. To be safe, these
// cases should be replaced with errors.
TEST_F(ThreadMergeRemapSchedSwitchPidTest, MissingTimestampReturnsError) {
bundle_.set_cpu(kCpu);
// Do not call set_timestamp(uint64_t value). There should be no timestamp for
// this case.
bundle_.mutable_event()->back().set_pid(kPrevProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_FALSE(Redact(event_message.get()).ok());
}
TEST_F(ThreadMergeRemapSchedSwitchPidTest, NoopWhenPidIsInPackage) {
bundle_.set_cpu(kCpu);
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kPrevProcess.pid);
auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_switch();
sched_switch->set_prev_pid(kPrevProcess.pid);
sched_switch->set_next_pid(kNextProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent event;
event.ParseFromString(event_message.SerializeAsString());
ASSERT_TRUE(event.has_sched_switch());
ASSERT_TRUE(event.sched_switch().has_prev_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_switch().prev_pid()),
kPrevProcess.pid);
ASSERT_TRUE(event.sched_switch().has_next_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_switch().next_pid()),
kNextProcess.pid);
}
TEST_F(ThreadMergeRemapSchedSwitchPidTest,
ChangesPrevPidWhenPidIsOutsidePackage) {
bundle_.set_cpu(kCpu);
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kPrevProcess.pid);
auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_switch();
sched_switch->set_prev_pid(kOtherProcess.pid);
sched_switch->set_next_pid(kNextProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent event;
event.ParseFromString(event_message.SerializeAsString());
ASSERT_TRUE(event.has_sched_switch());
ASSERT_TRUE(event.sched_switch().has_prev_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_switch().prev_pid()),
context_.synthetic_threads->tids[kCpu]);
ASSERT_TRUE(event.sched_switch().has_next_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_switch().next_pid()),
kNextProcess.pid);
}
TEST_F(ThreadMergeRemapSchedSwitchPidTest,
ChangesNextPidWhenPidIsOutsidePackage) {
bundle_.set_cpu(kCpu);
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kPrevProcess.pid);
auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_switch();
sched_switch->set_prev_pid(kPrevProcess.pid);
sched_switch->set_next_pid(kOtherProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent event;
event.ParseFromString(event_message.SerializeAsString());
ASSERT_TRUE(event.has_sched_switch());
ASSERT_TRUE(event.sched_switch().has_prev_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_switch().prev_pid()),
kPrevProcess.pid);
ASSERT_TRUE(event.sched_switch().has_next_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_switch().next_pid()),
context_.synthetic_threads->tids[kCpu]);
}
// event {
// timestamp: 6702093743527386
// pid: 0
// sched_waking {
// comm: "FMOD stream thr"
// pid: 7174
// prio: 104
// success: 1
// target_cpu: 7
// }
// }
class ThreadMergeRemapSchedWakingPidTest
: public testing::Test,
protected ThreadMergeTest<ThreadMergeRemapSchedWakingPid> {
protected:
static constexpr uint32_t kCpu = 3;
static constexpr auto kTimestamp = 123456789;
// This process will be connected to the target package.
static constexpr Process kWakerProcess = {12, 5, 7};
static constexpr Process kWakeTarget = {12, 5, 8};
// This process will not be connected to the target package.
static constexpr Process kOtherProcess = {120, 50, 70};
void SetUp() override {
bundle_.add_event();
context_.package_uid = kWakerProcess.uid;
context_.timeline = std::make_unique<ProcessThreadTimeline>();
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kWakerProcess.pid, kWakerProcess.ppid, kWakerProcess.uid));
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kWakeTarget.pid, kWakeTarget.ppid, kWakeTarget.uid));
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
0, kOtherProcess.pid, kOtherProcess.ppid, kOtherProcess.uid));
context_.timeline->Sort();
// Because kCpu is 3, it means that there are four CPUs (id 0, id 1, ...).
context_.synthetic_threads.emplace();
context_.synthetic_threads->tids.assign({100, 101, 102, 103});
}
};
// This should never happen, a bundle should always have a cpu. If it doesn't
// have a CPU, the event field should be dropped (safest option).
//
// TODO(vaage): This will create an invalid trace. It can also leak information
// if other primitives don't strip the remaining information. To be safe, these
// cases should be replaced with errors.
TEST_F(ThreadMergeRemapSchedWakingPidTest, MissingCpuReturnsError) {
// Do not call set_cpu(uint32_t value). There should be no cpu for this case.
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kWakerProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_FALSE(Redact(event_message.get()).ok());
}
// This should never happen, an event should always have a timestamp. If it
// doesn't have a timestamp, the event field should be dropped (safest option).
//
// TODO(vaage): This will create an invalid trace. It can also leak information
// if other primitives don't strip the remaining information. To be safe, these
// cases should be replaced with errors.
TEST_F(ThreadMergeRemapSchedWakingPidTest, MissingTimestampReturnsError) {
bundle_.set_cpu(kCpu);
// Do not call set_timestamp(uint64_t value). There should be no timestamp for
// this case.
bundle_.mutable_event()->back().set_pid(kWakerProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_FALSE(Redact(event_message.get()).ok());
}
TEST_F(ThreadMergeRemapSchedWakingPidTest, NoopWhenPidIsInPackage) {
bundle_.set_cpu(kCpu);
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kWakerProcess.pid);
auto* sched_waking = bundle_.mutable_event()->back().mutable_sched_waking();
sched_waking->set_pid(kWakeTarget.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent event;
event.ParseFromString(event_message.SerializeAsString());
ASSERT_TRUE(event.has_sched_waking());
ASSERT_TRUE(event.sched_waking().has_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_waking().pid()), kWakeTarget.pid);
}
TEST_F(ThreadMergeRemapSchedWakingPidTest, ChangesPidWhenPidIsOutsidePackage) {
bundle_.set_cpu(kCpu);
bundle_.mutable_event()->back().set_timestamp(kTimestamp);
bundle_.mutable_event()->back().set_pid(kWakerProcess.pid);
auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_waking();
sched_switch->set_pid(kOtherProcess.pid);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent event;
event.ParseFromString(event_message.SerializeAsString());
ASSERT_TRUE(event.has_sched_waking());
ASSERT_TRUE(event.sched_waking().has_pid());
ASSERT_EQ(static_cast<int32_t>(event.sched_waking().pid()),
context_.synthetic_threads->tids[kCpu]);
}
} // namespace perfetto::trace_redaction