blob: 7bd4ea494cce95578eb6e412969c5cf71bd7ddab [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/process_thread_timeline.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
namespace perfetto::trace_redaction {
namespace {
// Limit the number of iterations to avoid an infinite loop. 10 is a generous
// number of iterations.
constexpr size_t kMaxSearchDepth = 10;
bool OrderByPid(const ProcessThreadTimeline::Event& left,
const ProcessThreadTimeline::Event& right) {
return left.pid() < right.pid();
}
} // namespace
void ProcessThreadTimeline::Append(const Event& event) {
write_only_events_.push_back(event);
}
void ProcessThreadTimeline::Sort() {
write_only_events_.sort(OrderByPid);
// Copy all events that don't match adjacent events. This should reduce the
// number of events because process trees may contain the same data
// back-to-back.
read_only_events_.reserve(write_only_events_.size());
for (auto event : write_only_events_) {
if (read_only_events_.empty() || event != read_only_events_.back()) {
read_only_events_.push_back(event);
}
}
// Events have been moved from the write-only list to the read-only vector.
// The resources backing the write-only list can be release.
write_only_events_.clear();
}
void ProcessThreadTimeline::Flatten() {
// Union-find-like action to collapse the tree.
for (auto& event : read_only_events_) {
if (event.type() != Event::Type::kOpen) {
continue;
}
auto event_with_package = Search(0, event.ts(), event.pid());
if (event_with_package.has_value()) {
event = Event::Open(event.ts(), event.pid(), event.ppid(),
event_with_package->uid());
}
}
}
void ProcessThreadTimeline::Reduce(uint64_t package_uid) {
auto remove_open_events = [package_uid](const Event& event) {
return event.uid() != package_uid && event.type() == Event::Type::kOpen;
};
read_only_events_.erase(
std::remove_if(read_only_events_.begin(), read_only_events_.end(),
remove_open_events),
read_only_events_.end());
}
ProcessThreadTimeline::Slice ProcessThreadTimeline::Search(uint64_t ts,
int32_t pid) const {
Slice s;
s.pid = pid;
s.uid = 0;
auto e = Search(0, ts, pid);
if (e.has_value()) {
s.uid = e->uid();
}
return s;
}
std::optional<ProcessThreadTimeline::Event>
ProcessThreadTimeline::Search(size_t depth, uint64_t ts, int32_t pid) const {
if (depth >= kMaxSearchDepth) {
return std::nullopt;
}
auto event = FindPreviousEvent(ts, pid);
if (!TestEvent(event)) {
return event;
}
if (event->uid() != 0) {
return event;
}
return Search(depth + 1, ts, event->ppid());
}
std::optional<size_t> ProcessThreadTimeline::GetDepth(uint64_t ts,
int32_t pid) const {
return GetDepth(0, ts, pid);
}
std::optional<size_t> ProcessThreadTimeline::GetDepth(size_t depth,
uint64_t ts,
int32_t pid) const {
if (depth >= kMaxSearchDepth) {
return std::nullopt;
}
auto event = FindPreviousEvent(ts, pid);
if (!TestEvent(event)) {
return std::nullopt;
}
if (event->uid() != 0) {
return depth;
}
return GetDepth(depth + 1, ts, event->ppid());
}
std::optional<ProcessThreadTimeline::Event>
ProcessThreadTimeline::FindPreviousEvent(uint64_t ts, int32_t pid) const {
Event fake = Event::Close(ts, pid);
// Events are in ts-order within each pid-group. See Optimize(), Because each
// group is small (the vast majority will have two events [start + event, no
// reuse]).
//
// Find the first process event. Then perform a linear search. There won't be
// many events per process.
auto at = std::lower_bound(read_only_events_.begin(), read_only_events_.end(),
fake, OrderByPid);
// `pid` was not found in `read_only_events_`.
if (at == read_only_events_.end()) {
return std::nullopt;
}
// "no best option".
std::optional<Event> best;
// Run through all events (related to this pid) and find the last event that
// comes before ts. If the events were in order by time, the search could be
// more efficient, but the gains are margin because:
//
// 1. The number of edge cases go up.
//
// 2. The code is harder to read.
//
// 3. The performance gains are minimal or non-existant because of the small
// number of events.
for (; at != read_only_events_.end() && at->pid() == pid; ++at) {
if (at->ts() > ts) {
continue; // Ignore events in the future.
}
// All ts values are positive. However, ts_at and ts_best are both less than
// ts (see early condition), meaning they can be considered negative values.
//
// at best ts
// <---+-----------+-------------+---->
// 31 64 93
//
// at best ts
// <---+-----------+-------------+---->
// -62 -29 0
//
// This means that the latest ts value under ts is the closest to ts.
if (!best.has_value() || at->ts() > best->ts()) {
best = *at;
}
}
if (best.has_value() &&
best->type() != ProcessThreadTimeline::Event::Type::kOpen) {
return std::nullopt;
}
return best;
}
bool ProcessThreadTimeline::TestEvent(std::optional<Event> event) const {
if (!event.has_value()) {
return false;
}
// The thread/process was freed. It won't exist until a new open event.
if (event->type() != Event::Type::kOpen) {
return false;
}
// It is a rare case in production, but a common case in tests, the top-level
// event will have no parent but will have the uid. So, to avoid make the
// tests fragile and without taking on any risk, the uid should be checked
// before the ppid.
if (event->uid() != 0) {
return true;
}
return event->ppid() != 0;
}
} // namespace perfetto::trace_redaction