/*
* 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