blob: 8a1abda3d1d1f5124afda93e1e11285f765df51a [file] [log] [blame]
/*
* Copyright (C) 2020 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/dynamic/thread_state_generator.h"
#include <memory>
#include <set>
#include "src/trace_processor/types/trace_processor_context.h"
namespace perfetto {
namespace trace_processor {
ThreadStateGenerator::ThreadStateGenerator(TraceProcessorContext* context)
: running_string_id_(context->storage->InternString("Running")),
runnable_string_id_(context->storage->InternString("R")),
context_(context) {}
ThreadStateGenerator::~ThreadStateGenerator() = default;
util::Status ThreadStateGenerator::ValidateConstraints(
const QueryConstraints&) {
return util::OkStatus();
}
std::unique_ptr<Table> ThreadStateGenerator::ComputeTable(
const std::vector<Constraint>&,
const std::vector<Order>&) {
if (!unsorted_thread_state_table_) {
int64_t trace_end_ts =
context_->storage->GetTraceTimestampBoundsNs().second;
unsorted_thread_state_table_ = ComputeThreadStateTable(trace_end_ts);
// We explicitly sort by ts here as ComputeThreadStateTable does not insert
// rows in sorted order but we expect our clients to always want to sort
// on ts. Writing ComputeThreadStateTable to insert in sorted order is
// more trouble than its worth.
sorted_thread_state_table_ = unsorted_thread_state_table_->Sort(
{unsorted_thread_state_table_->ts().ascending()});
}
PERFETTO_CHECK(sorted_thread_state_table_);
return std::unique_ptr<Table>(new Table(sorted_thread_state_table_->Copy()));
}
std::unique_ptr<tables::ThreadStateTable>
ThreadStateGenerator::ComputeThreadStateTable(int64_t trace_end_ts) {
std::unique_ptr<tables::ThreadStateTable> table(new tables::ThreadStateTable(
context_->storage->mutable_string_pool(), nullptr));
const auto& raw_sched = context_->storage->sched_slice_table();
const auto& instants = context_->storage->instant_table();
// In both tables, exclude utid == 0 which represents the idle thread.
auto sched = raw_sched.Filter({raw_sched.utid().ne(0)});
auto waking = instants.Filter(
{instants.name().eq("sched_waking"), instants.ref().ne(0)});
// We prefer to use waking if at all possible and fall back to wakeup if not
// available.
if (waking.row_count() == 0) {
waking = instants.Filter(
{instants.name().eq("sched_wakeup"), instants.ref().ne(0)});
}
const auto& sched_ts = sched.GetTypedColumnByName<int64_t>("ts");
const auto& waking_ts = waking.GetTypedColumnByName<int64_t>("ts");
uint32_t sched_idx = 0;
uint32_t waking_idx = 0;
std::unordered_map<UniqueTid, uint32_t> state_map;
while (sched_idx < sched.row_count() || waking_idx < waking.row_count()) {
// We go through both tables, picking the earliest timestamp from either
// to process that event.
if (waking_idx >= waking.row_count() ||
(sched_idx < sched.row_count() &&
sched_ts[sched_idx] <= waking_ts[waking_idx])) {
AddSchedEvent(sched, sched_idx++, state_map, trace_end_ts, table.get());
} else {
AddWakingEvent(waking, waking_idx++, state_map, table.get());
}
}
return table;
}
void ThreadStateGenerator::UpdateDurIfNotRunning(
int64_t new_ts,
uint32_t row,
tables::ThreadStateTable* table) {
// The duration should always have been left dangling and the new timestamp
// should happen after the older one (as we go through events in ts order).
PERFETTO_DCHECK(table->dur()[row] == -1);
PERFETTO_DCHECK(new_ts > table->ts()[row]);
if (table->state()[row] == running_string_id_)
return;
table->mutable_dur()->Set(row, new_ts - table->ts()[row]);
}
void ThreadStateGenerator::AddSchedEvent(
const Table& sched,
uint32_t sched_idx,
std::unordered_map<UniqueTid, uint32_t>& state_map,
int64_t trace_end_ts,
tables::ThreadStateTable* table) {
UniqueTid utid = sched.GetTypedColumnByName<uint32_t>("utid")[sched_idx];
int64_t ts = sched.GetTypedColumnByName<int64_t>("ts")[sched_idx];
// Update the duration on the existing event.
auto it = state_map.find(utid);
if (it != state_map.end()) {
// Don't update dur from -1 if we were already running (can happen because
// of data loss).
UpdateDurIfNotRunning(ts, it->second, table);
}
// Undo the expansion of the final sched slice for each CPU to the end of the
// trace by setting the duration back to -1. This counteracts the code in
// SchedEventTracker::FlushPendingEvents
// TODO(lalitm): remove this hack when we stop expanding the last slice to the
// end of the trace.
int64_t dur = sched.GetTypedColumnByName<int64_t>("dur")[sched_idx];
if (ts + dur == trace_end_ts) {
dur = -1;
}
// First, we add the sched slice itself as "Running" with the other fields
// unchanged.
tables::ThreadStateTable::Row sched_row;
sched_row.ts = ts;
sched_row.dur = dur;
sched_row.cpu = sched.GetTypedColumnByName<uint32_t>("cpu")[sched_idx];
sched_row.state = running_string_id_;
sched_row.utid = utid;
state_map[utid] = table->Insert(sched_row).row;
// If the sched row had a negative duration, don't add the dangling
// descheduled slice as it would be meaningless.
if (sched_row.dur == -1) {
return;
}
// Next, we add a dangling, slice to represent the descheduled state with the
// given end state from the sched event. The duration will be updated by the
// next event (sched or waking) that we see.
tables::ThreadStateTable::Row row;
row.ts = sched_row.ts + sched_row.dur;
row.dur = -1;
row.state = sched.GetTypedColumnByName<StringId>("end_state")[sched_idx];
row.utid = utid;
state_map[utid] = table->Insert(row).row;
}
void ThreadStateGenerator::AddWakingEvent(
const Table& waking,
uint32_t waking_idx,
std::unordered_map<UniqueTid, uint32_t>& state_map,
tables::ThreadStateTable* table) {
int64_t ts = waking.GetTypedColumnByName<int64_t>("ts")[waking_idx];
UniqueTid utid = static_cast<UniqueTid>(
waking.GetTypedColumnByName<int64_t>("ref")[waking_idx]);
auto it = state_map.find(utid);
if (it != state_map.end()) {
// As counter-intuitive as it seems, occassionally we can get a waking
// event for a thread which is currently running.
//
// There are two cases when this can happen:
// 1. The kernel legitimately send a waking event for a "running" thread
// because the thread was woken up before the kernel switched away
// from it. In this case, the waking timestamp will be in the past
// because we added the descheduled slice when we processed the sched
// event).
// 2. We're close to the end of the trace or had data-loss and we missed
// the switch out event for a thread but we see a waking after.
// Case 1 described above. In this situation, we should drop the waking
// entirely.
if (table->ts()[it->second] >= ts) {
return;
}
// Case 2 described above. We still want to set the thread as running but
// we don't update the duration.
UpdateDurIfNotRunning(ts, it->second, table);
}
// Add a dangling slice to represent that this slice is now available for
// running. The duration will be updated by the next event (sched) that we
// see.
tables::ThreadStateTable::Row row;
row.ts = ts;
row.dur = -1;
row.state = runnable_string_id_;
row.utid = utid;
state_map[utid] = table->Insert(row).row;
}
Table::Schema ThreadStateGenerator::CreateSchema() {
auto schema = tables::ThreadStateTable::Schema();
// Because we expect our users to generally want ordered by ts, we set the
// ordering for the schema to match our forced sort pass in ComputeTable.
auto ts_it = std::find_if(
schema.columns.begin(), schema.columns.end(),
[](const Table::Schema::Column& col) { return col.name == "ts"; });
ts_it->is_sorted = true;
return schema;
}
std::string ThreadStateGenerator::TableName() {
return "thread_state";
}
uint32_t ThreadStateGenerator::EstimateRowCount() {
return context_->storage->sched_slice_table().row_count();
}
} // namespace trace_processor
} // namespace perfetto