blob: 473c48ed386fa40d59f8b4fd04967a47e95b0f83 [file] [log] [blame]
/*
* Copyright (C) 2018 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_processor/importers/common/process_tracker.h"
#include "src/trace_processor/storage/stats.h"
#include <cinttypes>
#include <utility>
namespace perfetto {
namespace trace_processor {
ProcessTracker::ProcessTracker(TraceProcessorContext* context)
: context_(context), args_tracker_(context) {
// Reserve utid/upid 0. These are special as embedders (e.g. Perfetto UI)
// exclude them from certain views (e.g. thread state) under the assumption
// that they correspond to the idle (swapper) process. When parsing Linux
// system traces, SetPidZeroIsUpidZeroIdleProcess will be called to associate
// tid0/pid0 to utid0/upid0. If other types of traces refer to tid0/pid0,
// then they will get their own non-zero utid/upid, so that those threads are
// still surfaced in embedder UIs.
tables::ThreadTable::Row thread_row;
thread_row.tid = 0u;
thread_row.upid = 0u;
thread_row.is_main_thread = true;
context_->storage->mutable_thread_table()->Insert(thread_row);
tables::ProcessTable::Row process_row;
process_row.pid = 0u;
context_->storage->mutable_process_table()->Insert(process_row);
// An element to match the reserved tid = 0.
thread_name_priorities_.push_back(ThreadNamePriority::kOther);
}
ProcessTracker::~ProcessTracker() = default;
UniqueTid ProcessTracker::StartNewThread(std::optional<int64_t> timestamp,
uint32_t tid) {
tables::ThreadTable::Row row;
row.tid = tid;
row.start_ts = timestamp;
auto* thread_table = context_->storage->mutable_thread_table();
UniqueTid new_utid = thread_table->Insert(row).row;
tids_[tid].emplace_back(new_utid);
PERFETTO_DCHECK(thread_name_priorities_.size() == new_utid);
thread_name_priorities_.push_back(ThreadNamePriority::kOther);
return new_utid;
}
void ProcessTracker::EndThread(int64_t timestamp, uint32_t tid) {
auto* thread_table = context_->storage->mutable_thread_table();
auto* process_table = context_->storage->mutable_process_table();
// Don't bother creating a new thread if we're just going to
// end it straight away.
//
// This is useful in situations where we get a sched_process_free event for a
// worker thread in a process *after* the main thread finishes - in that case
// we would have already ended the process and we don't want to
// create a new thread here (see b/193520421 for an example of a trace
// where this happens in practice).
std::optional<UniqueTid> opt_utid = GetThreadOrNull(tid);
if (!opt_utid)
return;
UniqueTid utid = *opt_utid;
thread_table->mutable_end_ts()->Set(utid, timestamp);
// Remove the thread from the list of threads being tracked as any event after
// this one should be ignored.
auto& vector = tids_[tid];
vector.erase(std::remove(vector.begin(), vector.end(), utid));
auto opt_upid = thread_table->upid()[utid];
if (!opt_upid.has_value() || process_table->pid()[*opt_upid] != tid)
return;
// If the process pid and thread tid are equal then, as is the main thread
// of the process, we should also finish the process itself.
PERFETTO_DCHECK(thread_table->is_main_thread()[utid].value());
process_table->mutable_end_ts()->Set(*opt_upid, timestamp);
pids_.Erase(tid);
}
std::optional<UniqueTid> ProcessTracker::GetThreadOrNull(uint32_t tid) {
auto opt_utid = GetThreadOrNull(tid, std::nullopt);
if (!opt_utid)
return std::nullopt;
auto* threads = context_->storage->mutable_thread_table();
UniqueTid utid = *opt_utid;
// Ensure that the tid matches the tid we were looking for.
PERFETTO_DCHECK(threads->tid()[utid] == tid);
// If the thread is being tracked by the process tracker, it should not be
// known to have ended.
PERFETTO_DCHECK(!threads->end_ts()[utid].has_value());
return utid;
}
UniqueTid ProcessTracker::GetOrCreateThread(uint32_t tid) {
auto utid = GetThreadOrNull(tid);
return utid ? *utid : StartNewThread(std::nullopt, tid);
}
UniqueTid ProcessTracker::UpdateThreadName(uint32_t tid,
StringId thread_name_id,
ThreadNamePriority priority) {
auto utid = GetOrCreateThread(tid);
UpdateThreadNameByUtid(utid, thread_name_id, priority);
return utid;
}
void ProcessTracker::UpdateThreadNameByUtid(UniqueTid utid,
StringId thread_name_id,
ThreadNamePriority priority) {
if (thread_name_id.is_null())
return;
auto* thread_table = context_->storage->mutable_thread_table();
if (priority >= thread_name_priorities_[utid]) {
thread_table->mutable_name()->Set(utid, thread_name_id);
thread_name_priorities_[utid] = priority;
}
}
bool ProcessTracker::IsThreadAlive(UniqueTid utid) {
auto* threads = context_->storage->mutable_thread_table();
auto* processes = context_->storage->mutable_process_table();
// If the thread has an end ts, it's certainly dead.
if (threads->end_ts()[utid].has_value())
return false;
// If we don't know the parent process, we have to consider this thread alive.
auto opt_current_upid = threads->upid()[utid];
if (!opt_current_upid)
return true;
// If the process is already dead, the thread can't be alive.
UniquePid current_upid = *opt_current_upid;
if (processes->end_ts()[current_upid].has_value())
return false;
// If the process has been replaced in |pids_|, this thread is dead.
uint32_t current_pid = processes->pid()[current_upid];
auto pid_it = pids_.Find(current_pid);
if (pid_it && *pid_it != current_upid)
return false;
return true;
}
std::optional<UniqueTid> ProcessTracker::GetThreadOrNull(
uint32_t tid,
std::optional<uint32_t> pid) {
auto* threads = context_->storage->mutable_thread_table();
auto* processes = context_->storage->mutable_process_table();
auto vector_it = tids_.Find(tid);
if (!vector_it)
return std::nullopt;
// Iterate backwards through the threads so ones later in the trace are more
// likely to be picked.
const auto& vector = *vector_it;
for (auto it = vector.rbegin(); it != vector.rend(); it++) {
UniqueTid current_utid = *it;
// If we finished this thread, we should have removed it from the vector
// entirely.
PERFETTO_DCHECK(!threads->end_ts()[current_utid].has_value());
// If the thread is dead, ignore it.
if (!IsThreadAlive(current_utid))
continue;
// If we don't know the parent process, we have to choose this thread.
auto opt_current_upid = threads->upid()[current_utid];
if (!opt_current_upid)
return current_utid;
// We found a thread that matches both the tid and its parent pid.
uint32_t current_pid = processes->pid()[*opt_current_upid];
if (!pid || current_pid == *pid)
return current_utid;
}
return std::nullopt;
}
UniqueTid ProcessTracker::UpdateThread(uint32_t tid, uint32_t pid) {
auto* thread_table = context_->storage->mutable_thread_table();
// Try looking for a thread that matches both tid and thread group id (pid).
std::optional<UniqueTid> opt_utid = GetThreadOrNull(tid, pid);
// If no matching thread was found, create a new one.
UniqueTid utid = opt_utid ? *opt_utid : StartNewThread(std::nullopt, tid);
PERFETTO_DCHECK(thread_table->tid()[utid] == tid);
// Find matching process or create new one.
if (!thread_table->upid()[utid].has_value()) {
AssociateThreadToProcess(utid, GetOrCreateProcess(pid));
}
ResolvePendingAssociations(utid, *thread_table->upid()[utid]);
return utid;
}
void ProcessTracker::UpdateTrustedPid(uint32_t trusted_pid, uint64_t uuid) {
trusted_pids_[uuid] = trusted_pid;
}
std::optional<uint32_t> ProcessTracker::GetTrustedPid(uint64_t uuid) {
if (trusted_pids_.find(uuid) == trusted_pids_.end())
return std::nullopt;
return trusted_pids_[uuid];
}
std::optional<uint32_t> ProcessTracker::ResolveNamespacedTid(
uint32_t root_level_pid,
uint32_t tid) {
if (root_level_pid <= 0) // Not a valid pid.
return std::nullopt;
// If the process doesn't run in a namespace (or traced_probes doesn't observe
// that), return std::nullopt as failure to resolve.
auto process_it = namespaced_processes_.find(root_level_pid);
if (process_it == namespaced_processes_.end())
return std::nullopt;
// Check if it's the main thread.
const auto& process = process_it->second;
auto ns_level = process.nspid.size() - 1;
auto pid_local = process.nspid.back();
if (pid_local == tid)
return root_level_pid;
// Check if any non-main thread has a matching ns-local thread ID.
for (const auto& root_level_tid : process.threads) {
const auto& thread = namespaced_threads_[root_level_tid];
PERFETTO_DCHECK(thread.nstid.size() > ns_level);
auto tid_ns_local = thread.nstid[ns_level];
if (tid_ns_local == tid)
return thread.tid;
}
// Failed to resolve or the thread isn't namespaced
return std::nullopt;
}
UniquePid ProcessTracker::StartNewProcess(std::optional<int64_t> timestamp,
std::optional<uint32_t> parent_tid,
uint32_t pid,
StringId main_thread_name,
ThreadNamePriority priority) {
pids_.Erase(pid);
// TODO(eseckler): Consider erasing all old entries in |tids_| that match the
// |pid| (those would be for an older process with the same pid). Right now,
// we keep them in |tids_| (if they weren't erased by EndThread()), but ignore
// them in GetThreadOrNull().
// Create a new UTID for the main thread, so we don't end up reusing an old
// entry in case of TID recycling.
UniqueTid utid = StartNewThread(timestamp, /*tid=*/pid);
UpdateThreadNameByUtid(utid, main_thread_name, priority);
// Note that we erased the pid above so this should always return a new
// process.
UniquePid upid = GetOrCreateProcess(pid);
auto* process_table = context_->storage->mutable_process_table();
auto* thread_table = context_->storage->mutable_thread_table();
PERFETTO_DCHECK(!process_table->name()[upid].has_value());
PERFETTO_DCHECK(!process_table->start_ts()[upid].has_value());
if (timestamp) {
process_table->mutable_start_ts()->Set(upid, *timestamp);
}
process_table->mutable_name()->Set(upid, main_thread_name);
if (parent_tid) {
UniqueTid parent_utid = GetOrCreateThread(*parent_tid);
auto opt_parent_upid = thread_table->upid()[parent_utid];
if (opt_parent_upid.has_value()) {
process_table->mutable_parent_upid()->Set(upid, *opt_parent_upid);
} else {
pending_parent_assocs_.emplace_back(parent_utid, upid);
}
}
return upid;
}
UniquePid ProcessTracker::SetProcessMetadata(uint32_t pid,
std::optional<uint32_t> ppid,
base::StringView name,
base::StringView cmdline) {
std::optional<UniquePid> pupid;
if (ppid.has_value()) {
pupid = GetOrCreateProcess(ppid.value());
}
UniquePid upid = GetOrCreateProcess(pid);
auto* process_table = context_->storage->mutable_process_table();
StringId proc_name_id = context_->storage->InternString(name);
process_table->mutable_name()->Set(upid, proc_name_id);
process_table->mutable_cmdline()->Set(
upid, context_->storage->InternString(cmdline));
if (pupid)
process_table->mutable_parent_upid()->Set(upid, *pupid);
return upid;
}
void ProcessTracker::SetProcessUid(UniquePid upid, uint32_t uid) {
auto* process_table = context_->storage->mutable_process_table();
process_table->mutable_uid()->Set(upid, uid);
// The notion of the app ID (as derived from the uid) is defined in
// frameworks/base/core/java/android/os/UserHandle.java
process_table->mutable_android_appid()->Set(upid, uid % 100000);
}
void ProcessTracker::SetProcessNameIfUnset(UniquePid upid,
StringId process_name_id) {
auto* process_table = context_->storage->mutable_process_table();
if (!process_table->name()[upid].has_value())
process_table->mutable_name()->Set(upid, process_name_id);
}
void ProcessTracker::SetStartTsIfUnset(UniquePid upid,
int64_t start_ts_nanoseconds) {
auto* process_table = context_->storage->mutable_process_table();
if (!process_table->start_ts()[upid].has_value())
process_table->mutable_start_ts()->Set(upid, start_ts_nanoseconds);
}
void ProcessTracker::UpdateThreadNameAndMaybeProcessName(
uint32_t tid,
StringId thread_name,
ThreadNamePriority priority) {
auto* thread_table = context_->storage->mutable_thread_table();
auto* process_table = context_->storage->mutable_process_table();
UniqueTid utid = UpdateThreadName(tid, thread_name, priority);
std::optional<UniquePid> opt_upid = thread_table->upid()[utid];
if (opt_upid.has_value() && process_table->pid()[*opt_upid] == tid) {
PERFETTO_DCHECK(thread_table->is_main_thread()[utid]);
process_table->mutable_name()->Set(*opt_upid, thread_name);
}
}
UniquePid ProcessTracker::GetOrCreateProcess(uint32_t pid) {
auto* process_table = context_->storage->mutable_process_table();
// If the insertion succeeds, we'll fill the upid below.
auto it_and_ins = pids_.Insert(pid, UniquePid{0});
if (!it_and_ins.second) {
// Ensure that the process has not ended.
PERFETTO_DCHECK(!process_table->end_ts()[*it_and_ins.first].has_value());
return *it_and_ins.first;
}
tables::ProcessTable::Row row;
row.pid = pid;
UniquePid upid = process_table->Insert(row).row;
*it_and_ins.first = upid; // Update the newly inserted hashmap entry.
// Create an entry for the main thread.
// We cannot call StartNewThread() here, because threads for this process
// (including the main thread) might have been seen already prior to this
// call. This call usually comes from the ProcessTree dump which is delayed.
UpdateThread(/*tid=*/pid, pid);
return upid;
}
void ProcessTracker::AssociateThreads(UniqueTid utid1, UniqueTid utid2) {
auto* tt = context_->storage->mutable_thread_table();
// First of all check if one of the two threads is already bound to a process.
// If that is the case, map the other thread to the same process and resolve
// recursively any associations pending on the other thread.
auto opt_upid1 = tt->upid()[utid1];
auto opt_upid2 = tt->upid()[utid2];
if (opt_upid1.has_value() && !opt_upid2.has_value()) {
AssociateThreadToProcess(utid2, *opt_upid1);
ResolvePendingAssociations(utid2, *opt_upid1);
return;
}
if (opt_upid2.has_value() && !opt_upid1.has_value()) {
AssociateThreadToProcess(utid1, *opt_upid2);
ResolvePendingAssociations(utid1, *opt_upid2);
return;
}
if (opt_upid1.has_value() && opt_upid1 != opt_upid2) {
// Cannot associate two threads that belong to two different processes.
PERFETTO_ELOG("Process tracker failure. Cannot associate threads %u, %u",
tt->tid()[utid1], tt->tid()[utid2]);
context_->storage->IncrementStats(stats::process_tracker_errors);
return;
}
pending_assocs_.emplace_back(utid1, utid2);
}
void ProcessTracker::ResolvePendingAssociations(UniqueTid utid_arg,
UniquePid upid) {
auto* tt = context_->storage->mutable_thread_table();
auto* pt = context_->storage->mutable_process_table();
PERFETTO_DCHECK(tt->upid()[utid_arg] == upid);
std::vector<UniqueTid> resolved_utids;
resolved_utids.emplace_back(utid_arg);
while (!resolved_utids.empty()) {
UniqueTid utid = resolved_utids.back();
resolved_utids.pop_back();
for (auto it = pending_parent_assocs_.begin();
it != pending_parent_assocs_.end();) {
UniqueTid parent_utid = it->first;
UniquePid child_upid = it->second;
if (parent_utid != utid) {
++it;
continue;
}
PERFETTO_DCHECK(child_upid != upid);
// Set the parent pid of the other process
PERFETTO_DCHECK(!pt->parent_upid()[child_upid] ||
pt->parent_upid()[child_upid] == upid);
pt->mutable_parent_upid()->Set(child_upid, upid);
// Erase the pair. The |pending_parent_assocs_| vector is not sorted and
// swapping a std::pair<uint32_t, uint32_t> is cheap.
std::swap(*it, pending_parent_assocs_.back());
pending_parent_assocs_.pop_back();
}
auto end = pending_assocs_.end();
for (auto it = pending_assocs_.begin(); it != end;) {
UniqueTid other_utid;
if (it->first == utid) {
other_utid = it->second;
} else if (it->second == utid) {
other_utid = it->first;
} else {
++it;
continue;
}
PERFETTO_DCHECK(other_utid != utid);
// Update the other thread and associated it to the same process.
PERFETTO_DCHECK(!tt->upid()[other_utid] ||
tt->upid()[other_utid] == upid);
AssociateThreadToProcess(other_utid, upid);
// Swap the current element to the end of the list and move the end
// iterator back. This works because |pending_assocs_| is not sorted. We
// do it this way rather than modifying |pending_assocs_| directly to
// prevent undefined behaviour caused by modifying a vector while
// iterating through it.
std::swap(*it, *(--end));
// Recurse into the newly resolved thread. Some other threads might have
// been bound to that.
resolved_utids.emplace_back(other_utid);
}
// Make sure to actually erase the utids which have been resolved.
pending_assocs_.erase(end, pending_assocs_.end());
} // while (!resolved_utids.empty())
}
void ProcessTracker::AssociateThreadToProcess(UniqueTid utid, UniquePid upid) {
auto* thread_table = context_->storage->mutable_thread_table();
thread_table->mutable_upid()->Set(utid, upid);
auto* process_table = context_->storage->mutable_process_table();
bool main_thread = thread_table->tid()[utid] == process_table->pid()[upid];
thread_table->mutable_is_main_thread()->Set(utid, main_thread);
}
void ProcessTracker::SetPidZeroIsUpidZeroIdleProcess() {
// Create a mapping from (t|p)id 0 -> u(t|p)id 0 for the idle process.
tids_.Insert(0, std::vector<UniqueTid>{0});
pids_.Insert(0, UniquePid{0});
auto swapper_id = context_->storage->InternString("swapper");
UpdateThreadName(0, swapper_id, ThreadNamePriority::kTraceProcessorConstant);
}
ArgsTracker::BoundInserter ProcessTracker::AddArgsTo(UniquePid upid) {
return args_tracker_.AddArgsTo(upid);
}
void ProcessTracker::NotifyEndOfFile() {
args_tracker_.Flush();
tids_.Clear();
pids_.Clear();
pending_assocs_.clear();
pending_parent_assocs_.clear();
thread_name_priorities_.clear();
trusted_pids_.clear();
namespaced_threads_.clear();
namespaced_processes_.clear();
}
void ProcessTracker::UpdateNamespacedProcess(uint32_t pid,
std::vector<uint32_t> nspid) {
namespaced_processes_[pid] = {pid, std::move(nspid), {}};
}
void ProcessTracker::UpdateNamespacedThread(uint32_t pid,
uint32_t tid,
std::vector<uint32_t> nstid) {
PERFETTO_DCHECK(namespaced_processes_.find(pid) !=
namespaced_processes_.end());
auto& process = namespaced_processes_[pid];
process.threads.emplace(tid);
namespaced_threads_[tid] = {pid, tid, std::move(nstid)};
}
} // namespace trace_processor
} // namespace perfetto