blob: 0d7ffbea7ac131c7d55f98f9ddc3ec5eb4f9559d [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_events.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 {
bool IsTrue(bool value) {
return value;
}
// Copy a field from 'decoder' to 'message' if the field can be found. Returns
// false if the field cannot be found.
bool Passthrough(protozero::ProtoDecoder& decoder,
uint32_t field_id,
protozero::Message* message) {
auto field = decoder.FindField(field_id);
if (field.valid()) {
proto_util::AppendField(field, message);
return true;
}
return false;
}
} // namespace
int64_t InternTable::Push(const char* data, size_t size) {
std::string_view outer(data, size);
for (size_t i = 0; i < interned_comms_.size(); ++i) {
auto view = interned_comms_[i];
if (view == outer) {
return static_cast<int64_t>(i);
}
}
// No room for the new string, reject the request.
if (comms_length_ + size > comms_.size()) {
return -1;
}
auto* head = comms_.data() + comms_length_;
// Important note, the null byte is not copied.
memcpy(head, data, size);
comms_length_ += size;
size_t id = interned_comms_.size();
interned_comms_.emplace_back(head, size);
return static_cast<int64_t>(id);
}
std::string_view InternTable::Find(size_t index) const {
if (index < interned_comms_.size()) {
return interned_comms_[index];
}
return {};
}
// 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.
base::Status RedactSchedEvents::Transform(const Context& context,
std::string* packet) const {
PERFETTO_DCHECK(modifier_);
PERFETTO_DCHECK(waking_filter_);
if (!context.timeline) {
return base::ErrStatus("RedactSchedEvents: missing timeline.");
}
if (!context.package_uid.has_value()) {
return base::ErrStatus("RedactSchedEvents: missing package uid.");
}
if (!packet || packet->empty()) {
return base::ErrStatus("RedactSchedEvents: null or empty packet.");
}
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(
OnFtraceEvents(context, field, message->set_ftrace_events()));
} else {
proto_util::AppendField(field, message.get());
}
}
packet->assign(message.SerializeAsString());
return base::OkStatus();
}
base::Status RedactSchedEvents::OnFtraceEvents(
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(
"RedactSchedEvents: 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(
OnFtraceEvent(context, cpu.as_int32(), field, message->add_event()));
continue;
}
if (field.id() ==
protos::pbzero::FtraceEventBundle::kCompactSchedFieldNumber) {
protos::pbzero::FtraceEventBundle::CompactSched::Decoder comp_sched(
field.as_bytes());
RETURN_IF_ERROR(OnCompSched(context, cpu.as_int32(), comp_sched,
message->set_compact_sched()));
continue;
}
proto_util::AppendField(field, message);
}
return base::OkStatus();
}
base::Status RedactSchedEvents::OnFtraceEvent(
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(
"RedactSchedEvents: missing timestamp in ftrace event.");
}
std::string scratch_str;
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
switch (field.id()) {
case protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber: {
protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
field.as_bytes());
RETURN_IF_ERROR(OnFtraceEventSwitch(context, ts.as_uint64(), cpu,
sched_switch, &scratch_str,
message->set_sched_switch()));
break;
}
case protos::pbzero::FtraceEvent::kSchedWakingFieldNumber: {
protos::pbzero::SchedWakingFtraceEvent::Decoder sched_waking(
field.as_bytes());
RETURN_IF_ERROR(OnFtraceEventWaking(
context, ts.as_uint64(), cpu, sched_waking, &scratch_str, message));
break;
}
default: {
proto_util::AppendField(field, message);
break;
}
}
}
return base::OkStatus();
}
base::Status RedactSchedEvents::OnFtraceEventSwitch(
const Context& context,
uint64_t ts,
int32_t cpu,
protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch,
std::string* scratch_str,
protos::pbzero::SchedSwitchFtraceEvent* message) const {
PERFETTO_DCHECK(modifier_);
PERFETTO_DCHECK(scratch_str);
PERFETTO_DCHECK(message);
std::array<bool, 7> 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(
"RedactSchedEvents: 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);
modifier_->Modify(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);
modifier_->Modify(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();
}
// Redact sched waking trace events in a ftrace event bundle:
//
// event {
// timestamp: 6702093787823849
// pid: 814 <-- waker
// sched_waking {
// comm: "surfaceflinger"
// pid: 756 <-- target
// prio: 97
// success: 1
// target_cpu: 2
// }
// }
base::Status RedactSchedEvents::OnFtraceEventWaking(
const Context& context,
uint64_t ts,
int32_t cpu,
protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking,
std::string* scratch_str,
protos::pbzero::FtraceEvent* parent_message) const {
PERFETTO_DCHECK(modifier_);
PERFETTO_DCHECK(scratch_str);
PERFETTO_DCHECK(parent_message);
std::array<bool, 5> has_fields = {
sched_waking.has_comm(), sched_waking.has_pid(), sched_waking.has_prio(),
sched_waking.has_success(), sched_waking.has_target_cpu()};
if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
return base::ErrStatus(
"RedactSchedEvents: missing required SchedWakingFtraceEvent "
"field.");
}
auto pid = sched_waking.pid();
if (!waking_filter_->Includes(context, ts, pid)) {
return base::OkStatus();
}
auto comm = sched_waking.comm();
// There are 5 values in a sched switch message. Since 2 of the 5 can be
// replaced, it is easier/cleaner to go value-by-value. Go in proto-defined
// order.
scratch_str->assign(comm.data, comm.size);
modifier_->Modify(context, ts, cpu, &pid, scratch_str);
auto message = parent_message->set_sched_waking();
message->set_comm(*scratch_str); // FieldNumber = 1
message->set_pid(pid); // FieldNumber = 2
message->set_prio(sched_waking.prio()); // FieldNumber = 3
message->set_success(sched_waking.success()); // FieldNumber = 4
message->set_target_cpu(sched_waking.target_cpu()); // FieldNumber = 5
return base::OkStatus();
}
base::Status RedactSchedEvents::OnCompSched(
const Context& context,
int32_t cpu,
protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
protos::pbzero::FtraceEventBundle::CompactSched* message) const {
// Populate the intern table once; it will be used by both sched and waking.
InternTable intern_table;
for (auto it = comp_sched.intern_table(); it; ++it) {
auto chars = it->as_string();
auto index = intern_table.Push(chars.data, chars.size);
if (index < 0) {
return base::ErrStatus(
"RedactSchedEvents: failed to insert string into intern "
"table.");
}
}
std::array<bool, 5> has_switch_fields = {
comp_sched.has_switch_timestamp(),
comp_sched.has_switch_prev_state(),
comp_sched.has_switch_next_pid(),
comp_sched.has_switch_next_prio(),
comp_sched.has_switch_next_comm_index(),
};
if (std::any_of(has_switch_fields.begin(), has_switch_fields.end(), IsTrue)) {
RETURN_IF_ERROR(
OnCompSchedSwitch(context, cpu, comp_sched, &intern_table, message));
}
std::array<bool, 6> has_waking_fields = {
comp_sched.has_waking_timestamp(), comp_sched.has_waking_pid(),
comp_sched.has_waking_target_cpu(), comp_sched.has_waking_prio(),
comp_sched.has_waking_comm_index(), comp_sched.has_waking_common_flags(),
};
if (std::any_of(has_waking_fields.begin(), has_waking_fields.end(), IsTrue)) {
RETURN_IF_ERROR(
OnCompactSchedWaking(context, comp_sched, &intern_table, message));
}
// IMPORTANT: The intern table can only be added after switch and waking
// because switch and/or waking can/will modify the intern table.
for (auto view : intern_table.values()) {
message->add_intern_table(view.data(), view.size());
}
return base::OkStatus();
}
base::Status RedactSchedEvents::OnCompSchedSwitch(
const Context& context,
int32_t cpu,
protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
InternTable* intern_table,
protos::pbzero::FtraceEventBundle::CompactSched* message) const {
PERFETTO_DCHECK(modifier_);
PERFETTO_DCHECK(message);
std::array<bool, 6> has_fields = {
comp_sched.has_intern_table(),
comp_sched.has_switch_timestamp(),
comp_sched.has_switch_prev_state(),
comp_sched.has_switch_next_pid(),
comp_sched.has_switch_next_prio(),
comp_sched.has_switch_next_comm_index(),
};
if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
return base::ErrStatus(
"RedactSchedEvents: missing required FtraceEventBundle::CompactSched "
"switch field.");
}
std::string scratch_str;
protozero::PackedVarInt packed_comm;
protozero::PackedVarInt packed_pid;
// The first it_ts value is an absolute value, all other values are delta
// values.
uint64_t ts = 0;
std::array<bool, 3> parse_errors = {false, false, false};
auto it_ts = comp_sched.switch_timestamp(&parse_errors.at(0));
auto it_pid = comp_sched.switch_next_pid(&parse_errors.at(1));
auto it_comm = comp_sched.switch_next_comm_index(&parse_errors.at(2));
while (it_ts && it_pid && it_comm) {
ts += *it_ts;
auto pid = *it_pid;
auto comm_index = *it_comm;
auto comm = intern_table->Find(comm_index);
scratch_str.assign(comm);
modifier_->Modify(context, ts, cpu, &pid, &scratch_str);
auto found = intern_table->Push(scratch_str.data(), scratch_str.size());
if (found < 0) {
return base::ErrStatus(
"RedactSchedEvents: failed to insert string into intern table.");
}
packed_comm.Append(found);
packed_pid.Append(pid);
++it_ts;
++it_pid;
++it_comm;
}
if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) {
return base::ErrStatus(
"RedactSchedEvents: error reading FtraceEventBundle::CompactSched.");
}
if (it_ts || it_pid || it_comm) {
return base::ErrStatus(
"RedactSchedEvents: uneven associative arrays in "
"FtraceEventBundle::CompactSched (switch).");
}
message->set_switch_next_pid(packed_pid);
message->set_switch_next_comm_index(packed_comm);
// There's a lot of data in a compact sched message. Most of it is packed data
// and most of the data is not going to change. To avoid unpacking, doing
// nothing, and then packing... cheat. Find the fields and pass them as opaque
// blobs.
//
// kInternTableFieldNumber: The intern table will be modified by both
// switch events and waking events. It will
// be written elsewhere.
//
// kSwitchNextPidFieldNumber: The switch pid will change during thread
// merging.
//
// kSwitchNextCommIndexFieldNumber: The switch comm value will change when
// clearing thread names and replaced
// during thread merging.
auto passed_through = {
Passthrough(comp_sched,
protos::pbzero::FtraceEventBundle::CompactSched::
kSwitchTimestampFieldNumber,
message),
Passthrough(comp_sched,
protos::pbzero::FtraceEventBundle::CompactSched::
kSwitchPrevStateFieldNumber,
message),
Passthrough(comp_sched,
protos::pbzero::FtraceEventBundle::CompactSched::
kSwitchNextPrioFieldNumber,
message)};
if (!std::all_of(passed_through.begin(), passed_through.end(), IsTrue)) {
return base::ErrStatus(
"RedactSchedEvents: missing required "
"FtraceEventBundle::CompactSched switch field.");
}
return base::OkStatus();
}
base::Status RedactSchedEvents::OnCompactSchedWaking(
const Context& context,
protos::pbzero::FtraceEventBundle::CompactSched::Decoder& compact_sched,
InternTable* intern_table,
protos::pbzero::FtraceEventBundle::CompactSched* compact_sched_message)
const {
protozero::PackedVarInt var_comm_index;
protozero::PackedVarInt var_common_flags;
protozero::PackedVarInt var_pid;
protozero::PackedVarInt var_prio;
protozero::PackedVarInt var_target_cpu;
protozero::PackedVarInt var_timestamp;
// Time is expressed as delta time, for example:
//
// Event: A B C D
// Absolute Time: 20 30 35 41
// | | | |
// Delta Time: 20 10 5 6
//
// When an event is removed, for example, event B, delta times are off:
//
// Event: A * C D
// Absolute Time: 20 30 35 41
// | | | |
// Delta Time: 20 * 5 6
// | | |
// Effective Abs. Time: 20 25 31
// Error: 0 10 10
//
// To address this issue, delta times are added into a bucket. The bucket is
// drained each time an event is retained. If an event is dropped, its time
// is added to the bucket, but the bucket won't be drained until a retained
// event drains it.
uint64_t ts_bucket = 0;
uint64_t ts_absolute = 0;
std::string comm;
std::array<bool, 7> parse_errors = {!compact_sched.has_intern_table(),
false,
false,
false,
false,
false,
false};
// A note on readability, because the waking iterators are the primary focus,
// they won't have a "waking" prefix.
auto it_comm_index = compact_sched.waking_comm_index(&parse_errors.at(1));
auto it_common_flags = compact_sched.waking_common_flags(&parse_errors.at(2));
auto it_pid = compact_sched.waking_pid(&parse_errors.at(3));
auto it_prio = compact_sched.waking_prio(&parse_errors.at(4));
auto it_target_cpu = compact_sched.waking_target_cpu(&parse_errors.at(5));
auto it_timestamp = compact_sched.waking_timestamp(&parse_errors.at(6));
while (it_comm_index && it_common_flags && it_pid && it_prio &&
it_target_cpu && it_timestamp) {
ts_bucket += *it_timestamp; // add time to the bucket
ts_absolute += *it_timestamp;
if (waking_filter_->Includes(context, ts_absolute, *it_pid)) {
// Now that the waking event will be kept, it can be modified using the
// same rules as switch events.
auto pid = *it_pid;
comm.assign(intern_table->Find(*it_comm_index));
modifier_->Modify(context, ts_absolute, *it_target_cpu, &pid, &comm);
auto comm_it = intern_table->Push(comm.data(), comm.size());
var_comm_index.Append(comm_it);
var_common_flags.Append(*it_common_flags);
var_pid.Append(pid);
var_prio.Append(*it_prio);
var_target_cpu.Append(*it_target_cpu);
var_timestamp.Append(ts_bucket);
ts_bucket = 0; // drain the whole bucket.
}
++it_comm_index;
++it_common_flags;
++it_pid;
++it_prio;
++it_target_cpu;
++it_timestamp;
}
if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) {
return base::ErrStatus(
"RedactSchedEvents: failed to parse FtraceEventBundle::CompactSched.");
}
if (it_comm_index || it_common_flags || it_pid || it_prio || it_target_cpu ||
it_timestamp) {
return base::ErrStatus(
"RedactSchedEvents: uneven associative arrays in "
"FtraceEventBundle::CompactSched (waking).");
}
compact_sched_message->set_waking_comm_index(var_comm_index);
compact_sched_message->set_waking_common_flags(var_common_flags);
compact_sched_message->set_waking_pid(var_pid);
compact_sched_message->set_waking_prio(var_prio);
compact_sched_message->set_waking_target_cpu(var_target_cpu);
compact_sched_message->set_waking_timestamp(var_timestamp);
return base::OkStatus();
}
} // namespace perfetto::trace_redaction