blob: 87412c18ea262a0b0e90f30d6b64784d881cef14 [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/redact_sched_switch.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "src/trace_processor/util/status_macros.h"
#include "src/trace_redaction/proto_util.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
namespace perfetto::trace_redaction {
namespace {
// TODO(vaage): While simple, this function saves us from declaring the sample
// lambda each time we use the has_fields pattern. Once its usage increases, and
// its value is obvious, remove this comment.
bool IsTrue(bool value) {
return value;
}
} // namespace
// Redact sched switch trace events in an ftrace event bundle:
//
// event {
// timestamp: 6702093744772646
// pid: 0
// sched_switch {
// prev_comm: "swapper/0"
// prev_pid: 0
// prev_prio: 120
// prev_state: 0
// next_comm: "writer"
// next_pid: 23020
// next_prio: 96
// }
// }
//
// In the above message, it should be noted that "event.pid" will always be
// equal to "event.sched_switch.prev_pid".
//
// "ftrace_event_bundle_message" is the ftrace event bundle (contains a
// collection of ftrace event messages) because data in a sched_switch message
// is needed in order to know if the event should be added to the bundle.
SchedSwitchTransform::~SchedSwitchTransform() = default;
base::Status RedactSchedSwitchHarness::Transform(const Context& context,
std::string* packet) const {
protozero::HeapBuffered<protos::pbzero::TracePacket> message;
protozero::ProtoDecoder decoder(*packet);
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
if (field.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) {
RETURN_IF_ERROR(
TransformFtraceEvents(context, field, message->set_ftrace_events()));
} else {
proto_util::AppendField(field, message.get());
}
}
packet->assign(message.SerializeAsString());
return base::OkStatus();
}
base::Status RedactSchedSwitchHarness::TransformFtraceEvents(
const Context& context,
protozero::Field ftrace_events,
protos::pbzero::FtraceEventBundle* message) const {
PERFETTO_DCHECK(ftrace_events.id() ==
protos::pbzero::TracePacket::kFtraceEventsFieldNumber);
protozero::ProtoDecoder decoder(ftrace_events.as_bytes());
auto cpu =
decoder.FindField(protos::pbzero::FtraceEventBundle::kCpuFieldNumber);
if (!cpu.valid()) {
return base::ErrStatus(
"RedactSchedSwitchHarness: missing cpu in ftrace event bundle.");
}
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
if (field.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) {
RETURN_IF_ERROR(TransformFtraceEvent(context, cpu.as_int32(), field,
message->add_event()));
continue;
}
if (field.id() ==
protos::pbzero::FtraceEventBundle::kCompactSchedFieldNumber) {
// TODO(vaage): Replace this with logic specific to the comp sched data
// type.
proto_util::AppendField(field, message);
continue;
}
proto_util::AppendField(field, message);
}
return base::OkStatus();
}
base::Status RedactSchedSwitchHarness::TransformFtraceEvent(
const Context& context,
int32_t cpu,
protozero::Field ftrace_event,
protos::pbzero::FtraceEvent* message) const {
PERFETTO_DCHECK(ftrace_event.id() ==
protos::pbzero::FtraceEventBundle::kEventFieldNumber);
protozero::ProtoDecoder decoder(ftrace_event.as_bytes());
auto ts =
decoder.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
if (!ts.valid()) {
return base::ErrStatus(
"RedactSchedSwitchHarness: missing timestamp in ftrace event.");
}
std::string scratch_str;
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
if (field.id() == protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber) {
protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
field.as_bytes());
RETURN_IF_ERROR(TransformFtraceEventSchedSwitch(
context, ts.as_uint64(), cpu, sched_switch, &scratch_str,
message->set_sched_switch()));
} else {
proto_util::AppendField(field, message);
}
}
return base::OkStatus();
}
base::Status RedactSchedSwitchHarness::TransformFtraceEventSchedSwitch(
const Context& context,
uint64_t ts,
int32_t cpu,
protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch,
std::string* scratch_str,
protos::pbzero::SchedSwitchFtraceEvent* message) const {
auto has_fields = {
sched_switch.has_prev_comm(), sched_switch.has_prev_pid(),
sched_switch.has_prev_prio(), sched_switch.has_prev_state(),
sched_switch.has_next_comm(), sched_switch.has_next_pid(),
sched_switch.has_next_prio()};
if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
return base::ErrStatus(
"RedactSchedSwitchHarness: missing required SchedSwitchFtraceEvent "
"field.");
}
auto prev_pid = sched_switch.prev_pid();
auto prev_comm = sched_switch.prev_comm();
auto next_pid = sched_switch.next_pid();
auto next_comm = sched_switch.next_comm();
// There are 7 values in a sched switch message. Since 4 of the 7 can be
// replaced, it is easier/cleaner to go value-by-value. Go in proto-defined
// order.
scratch_str->assign(prev_comm.data, prev_comm.size);
for (const auto& transform : transforms_) {
RETURN_IF_ERROR(
transform->Transform(context, ts, cpu, &prev_pid, scratch_str));
}
message->set_prev_comm(*scratch_str); // FieldNumber = 1
message->set_prev_pid(prev_pid); // FieldNumber = 2
message->set_prev_prio(sched_switch.prev_prio()); // FieldNumber = 3
message->set_prev_state(sched_switch.prev_state()); // FieldNumber = 4
scratch_str->assign(next_comm.data, next_comm.size);
for (const auto& transform : transforms_) {
RETURN_IF_ERROR(
transform->Transform(context, ts, cpu, &next_pid, scratch_str));
}
message->set_next_comm(*scratch_str); // FieldNumber = 5
message->set_next_pid(next_pid); // FieldNumber = 6
message->set_next_prio(sched_switch.next_prio()); // FieldNumber = 7
return base::OkStatus();
}
// Switch event transformation: Clear the comm value if the thread/process is
// not part of the target packet.
base::Status ClearComms::Transform(const Context& context,
uint64_t ts,
int32_t,
int32_t* pid,
std::string* comm) const {
PERFETTO_DCHECK(pid);
PERFETTO_DCHECK(comm);
if (!context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) {
comm->clear();
}
return base::OkStatus();
}
} // namespace perfetto::trace_redaction