/*
|
* 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/sched_slice_table.h"
|
|
namespace perfetto {
|
namespace trace_processor {
|
|
SchedSliceTable::SchedSliceTable(sqlite3*, const TraceStorage* storage)
|
: storage_(storage) {}
|
|
void SchedSliceTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
|
Table::Register<SchedSliceTable>(db, storage, "sched");
|
}
|
|
StorageSchema SchedSliceTable::CreateStorageSchema() {
|
const auto& slices = storage_->slices();
|
return StorageSchema::Builder()
|
.AddOrderedNumericColumn("ts", &slices.start_ns())
|
.AddNumericColumn("cpu", &slices.cpus())
|
.AddNumericColumn("dur", &slices.durations())
|
.AddGenericNumericColumn(
|
"ts_end", TsEndAccessor(&slices.start_ns(), &slices.durations()))
|
.AddNumericColumn("utid", &slices.utids(), &slices.rows_for_utids())
|
.AddColumn<EndStateColumn>("end_state", &slices.end_state())
|
.AddNumericColumn("priority", &slices.priorities())
|
.AddGenericNumericColumn("row_id", RowIdAccessor(TableId::kSched))
|
.Build({"cpu", "ts"});
|
}
|
|
uint32_t SchedSliceTable::RowCount() {
|
return static_cast<uint32_t>(storage_->slices().slice_count());
|
}
|
|
int SchedSliceTable::BestIndex(const QueryConstraints& qc,
|
BestIndexInfo* info) {
|
info->estimated_cost = EstimateQueryCost(qc);
|
|
// We should be able to handle any constraint and any order by clause given
|
// to us.
|
info->order_by_consumed = true;
|
std::fill(info->omit.begin(), info->omit.end(), true);
|
|
return SQLITE_OK;
|
}
|
|
uint32_t SchedSliceTable::EstimateQueryCost(const QueryConstraints& qc) {
|
const auto& cs = qc.constraints();
|
|
size_t ts_idx = schema().ColumnIndexFromName("ts");
|
auto has_ts_column = [ts_idx](const QueryConstraints::Constraint& c) {
|
return c.iColumn == static_cast<int>(ts_idx);
|
};
|
bool has_time_constraint = std::any_of(cs.begin(), cs.end(), has_ts_column);
|
if (has_time_constraint) {
|
// If there is a constraint on ts, we can do queries very fast (O(log n))
|
// so always make this preferred if available.
|
return 10;
|
}
|
|
size_t utid_idx = schema().ColumnIndexFromName("utid");
|
auto has_utid_eq_cs = [utid_idx](const QueryConstraints::Constraint& c) {
|
return c.iColumn == static_cast<int>(utid_idx) &&
|
sqlite_utils::IsOpEq(c.op);
|
};
|
bool has_utid_eq = std::any_of(cs.begin(), cs.end(), has_utid_eq_cs);
|
if (has_utid_eq) {
|
// The other column which is often joined on is utid. Sometimes, doing
|
// nested subqueries on the thread table is faster but with some queries,
|
// it's actually better to do subqueries on this table. Estimate the cost
|
// of filtering on utid equality constraint by dividing the number of slices
|
// by the number of threads.
|
return RowCount() / storage_->thread_count();
|
}
|
|
// If we get to this point, we do not have any special filter logic so
|
// simply return the number of rows.
|
return RowCount();
|
}
|
|
SchedSliceTable::EndStateColumn::EndStateColumn(
|
std::string col_name,
|
const std::deque<ftrace_utils::TaskState>* deque)
|
: StorageColumn(col_name, false), deque_(deque) {
|
for (uint16_t i = 0; i < state_strings_.size(); i++) {
|
state_strings_[i] = ftrace_utils::TaskState(i).ToString();
|
}
|
}
|
SchedSliceTable::EndStateColumn::~EndStateColumn() = default;
|
|
void SchedSliceTable::EndStateColumn::ReportResult(sqlite3_context* ctx,
|
uint32_t row) const {
|
const auto& state = (*deque_)[row];
|
if (state.is_valid()) {
|
PERFETTO_CHECK(state.raw_state() < state_strings_.size());
|
sqlite3_result_text(ctx, state_strings_[state.raw_state()].data(), -1,
|
sqlite_utils::kSqliteStatic);
|
} else {
|
sqlite3_result_null(ctx);
|
}
|
}
|
|
void SchedSliceTable::EndStateColumn::Filter(int op,
|
sqlite3_value* value,
|
FilteredRowIndex* index) const {
|
switch (op) {
|
case SQLITE_INDEX_CONSTRAINT_ISNULL:
|
case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: {
|
bool non_nulls = op == SQLITE_INDEX_CONSTRAINT_ISNOTNULL;
|
index->FilterRows([this, non_nulls](uint32_t row) {
|
const auto& state = (*deque_)[row];
|
return state.is_valid() == non_nulls;
|
});
|
break;
|
}
|
case SQLITE_INDEX_CONSTRAINT_EQ:
|
case SQLITE_INDEX_CONSTRAINT_NE:
|
case SQLITE_INDEX_CONSTRAINT_MATCH:
|
FilterOnState(op, value, index);
|
break;
|
default:
|
index->set_error("Unsupported op given to filter on end_state");
|
break;
|
}
|
}
|
|
void SchedSliceTable::EndStateColumn::FilterOnState(
|
int op,
|
sqlite3_value* value,
|
FilteredRowIndex* index) const {
|
if (sqlite3_value_type(value) != SQLITE_TEXT) {
|
index->set_error("end_state can only be filtered using strings");
|
return;
|
}
|
|
const char* str = reinterpret_cast<const char*>(sqlite3_value_text(value));
|
ftrace_utils::TaskState compare(str);
|
if (!compare.is_valid()) {
|
index->set_error("Invalid end_state string given to filter");
|
return;
|
}
|
|
uint16_t raw_state = compare.raw_state();
|
if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
|
index->FilterRows([this, raw_state](uint32_t row) {
|
const auto& state = (*deque_)[row];
|
return state.is_valid() && state.raw_state() == raw_state;
|
});
|
} else if (op == SQLITE_INDEX_CONSTRAINT_NE) {
|
index->FilterRows([this, raw_state](uint32_t row) {
|
const auto& state = (*deque_)[row];
|
return state.is_valid() && state.raw_state() != raw_state;
|
});
|
} else if (op == SQLITE_INDEX_CONSTRAINT_MATCH) {
|
index->FilterRows([this, compare](uint32_t row) {
|
const auto& state = (*deque_)[row];
|
if (!state.is_valid())
|
return false;
|
return (state.raw_state() & compare.raw_state()) == compare.raw_state();
|
});
|
} else {
|
PERFETTO_FATAL("Should never reach this state");
|
}
|
}
|
|
StorageColumn::Comparator SchedSliceTable::EndStateColumn::Sort(
|
const QueryConstraints::OrderBy& ob) const {
|
if (ob.desc) {
|
return [this](uint32_t f, uint32_t s) {
|
const auto& a = (*deque_)[f];
|
const auto& b = (*deque_)[s];
|
if (!a.is_valid()) {
|
return !b.is_valid() ? 0 : 1;
|
} else if (!b.is_valid()) {
|
return -1;
|
}
|
return sqlite_utils::CompareValuesAsc(a.raw_state(), b.raw_state());
|
};
|
}
|
return [this](uint32_t f, uint32_t s) {
|
const auto& a = (*deque_)[f];
|
const auto& b = (*deque_)[s];
|
if (!a.is_valid()) {
|
return !b.is_valid() ? 0 : -1;
|
} else if (!b.is_valid()) {
|
return 1;
|
}
|
return sqlite_utils::CompareValuesAsc(a.raw_state(), b.raw_state());
|
};
|
}
|
|
Table::ColumnType SchedSliceTable::EndStateColumn::GetType() const {
|
return Table::ColumnType::kString;
|
}
|
|
} // namespace trace_processor
|
} // namespace perfetto
|