/*
|
* 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.
|
*/
|
|
#ifndef SRC_TRACE_PROCESSOR_STORAGE_COLUMNS_H_
|
#define SRC_TRACE_PROCESSOR_STORAGE_COLUMNS_H_
|
|
#include <deque>
|
#include <limits>
|
#include <memory>
|
#include <string>
|
#include <vector>
|
|
#include "src/trace_processor/filtered_row_index.h"
|
#include "src/trace_processor/sqlite_utils.h"
|
#include "src/trace_processor/trace_storage.h"
|
|
namespace perfetto {
|
namespace trace_processor {
|
|
// A column of data backed by data storage.
|
class StorageColumn {
|
public:
|
struct Bounds {
|
uint32_t min_idx = 0;
|
uint32_t max_idx = std::numeric_limits<uint32_t>::max();
|
bool consumed = false;
|
};
|
using Predicate = std::function<bool(uint32_t)>;
|
using Comparator = std::function<int(uint32_t, uint32_t)>;
|
|
StorageColumn(std::string col_name, bool hidden);
|
virtual ~StorageColumn();
|
|
// Implements StorageCursor::ColumnReporter.
|
virtual void ReportResult(sqlite3_context*, uint32_t row) const = 0;
|
|
// Given a SQLite operator and value for the comparision, returns a
|
// predicate which takes in a row index and returns whether the row should
|
// be returned.
|
virtual void Filter(int op, sqlite3_value*, FilteredRowIndex*) const = 0;
|
|
// Given a order by constraint for this column, returns a comparator
|
// function which compares data in this column at two indices.
|
virtual Comparator Sort(const QueryConstraints::OrderBy& ob) const = 0;
|
|
// Returns the type of this column.
|
virtual Table::ColumnType GetType() const = 0;
|
|
// Bounds a filter on this column between a minimum and maximum index.
|
// Generally this is only possible if the column is sorted.
|
virtual Bounds BoundFilter(int, sqlite3_value*) const { return Bounds{}; }
|
|
// Returns whether this column is ordered.
|
virtual bool HasOrdering() const { return false; }
|
|
const std::string& name() const { return col_name_; }
|
bool hidden() const { return hidden_; }
|
|
private:
|
std::string col_name_;
|
bool hidden_ = false;
|
};
|
|
// The implementation of StorageColumn for Strings.
|
// The actual retrieval of the numerics from the data types is left to the
|
// Acessor trait (see below for definition).
|
template <typename Accessor /* <NullTermStringView> */>
|
class StringColumn final : public StorageColumn {
|
public:
|
StringColumn(std::string col_name, Accessor accessor, bool hidden = false)
|
: StorageColumn(col_name, hidden), accessor_(accessor) {}
|
|
void ReportResult(sqlite3_context* ctx, uint32_t row) const override {
|
NullTermStringView str = accessor_.Get(row);
|
if (str.c_str() == nullptr) {
|
sqlite3_result_null(ctx);
|
} else {
|
sqlite3_result_text(ctx, str.c_str(), -1, sqlite_utils::kSqliteStatic);
|
}
|
}
|
|
Bounds BoundFilter(int, sqlite3_value*) const override {
|
Bounds bounds;
|
bounds.max_idx = static_cast<uint32_t>(accessor_.Size());
|
return bounds;
|
}
|
|
void Filter(int, sqlite3_value*, FilteredRowIndex*) const override {}
|
|
Comparator Sort(const QueryConstraints::OrderBy& ob) const override {
|
if (ob.desc) {
|
return [this](uint32_t f, uint32_t s) {
|
NullTermStringView a = accessor_.Get(f);
|
NullTermStringView b = accessor_.Get(s);
|
return sqlite_utils::CompareValuesDesc(a, b);
|
};
|
}
|
return [this](uint32_t f, uint32_t s) {
|
NullTermStringView a = accessor_.Get(f);
|
NullTermStringView b = accessor_.Get(s);
|
return sqlite_utils::CompareValuesAsc(a, b);
|
};
|
}
|
|
Table::ColumnType GetType() const override {
|
return Table::ColumnType::kString;
|
}
|
|
bool HasOrdering() const override { return accessor_.HasOrdering(); }
|
|
private:
|
Accessor accessor_;
|
};
|
|
// The implementation of StorageColumn for numeric data types.
|
// The actual retrieval of the numerics from the data types is left to the
|
// Acessor trait (see below for definition).
|
template <typename Accessor,
|
typename sqlite_utils::is_numeric<typename Accessor::Type>* = nullptr>
|
class NumericColumn : public StorageColumn {
|
public:
|
// The type of the column. This is one of uint32_t, int32_t, uint64_t etc.
|
using NumericType = typename Accessor::Type;
|
|
NumericColumn(std::string col_name, bool hidden, Accessor accessor)
|
: StorageColumn(col_name, hidden), accessor_(accessor) {}
|
~NumericColumn() override = default;
|
|
void ReportResult(sqlite3_context* ctx, uint32_t row) const override {
|
sqlite_utils::ReportSqliteResult(ctx, accessor_.Get(row));
|
}
|
|
Bounds BoundFilter(int op, sqlite3_value* sqlite_val) const override {
|
Bounds bounds;
|
bounds.max_idx = accessor_.Size();
|
|
if (!accessor_.HasOrdering())
|
return bounds;
|
|
// Makes the below code much more readable.
|
using namespace sqlite_utils;
|
|
NumericType min = kTMin;
|
NumericType max = kTMax;
|
if (IsOpGe(op) || IsOpGt(op)) {
|
min = FindGtBound<NumericType>(IsOpGe(op), sqlite_val);
|
} else if (IsOpLe(op) || IsOpLt(op)) {
|
max = FindLtBound<NumericType>(IsOpLe(op), sqlite_val);
|
} else if (IsOpEq(op)) {
|
auto val = FindEqBound<NumericType>(sqlite_val);
|
min = val;
|
max = val;
|
}
|
|
if (min <= kTMin && max >= kTMax)
|
return bounds;
|
|
bounds.min_idx = accessor_.LowerBoundIndex(min);
|
bounds.max_idx = accessor_.UpperBoundIndex(max);
|
bounds.consumed = true;
|
|
return bounds;
|
}
|
|
void Filter(int op,
|
sqlite3_value* value,
|
FilteredRowIndex* index) const override {
|
auto type = sqlite3_value_type(value);
|
|
bool same_type = (kIsIntegralType && type == SQLITE_INTEGER) ||
|
(kIsRealType && type == SQLITE_FLOAT);
|
if (sqlite_utils::IsOpEq(op) && same_type &&
|
accessor_.CanFindEqualIndices()) {
|
auto raw = sqlite_utils::ExtractSqliteValue<NumericType>(value);
|
index->IntersectRows(accessor_.EqualIndices(raw));
|
return;
|
}
|
|
if (kIsIntegralType && (type == SQLITE_INTEGER || type == SQLITE_NULL)) {
|
FilterWithCast<int64_t>(op, value, index);
|
} else if (type == SQLITE_INTEGER || type == SQLITE_FLOAT ||
|
type == SQLITE_NULL) {
|
FilterWithCast<double>(op, value, index);
|
} else {
|
PERFETTO_FATAL("Unexpected sqlite value to compare against");
|
}
|
}
|
|
Comparator Sort(const QueryConstraints::OrderBy& ob) const override {
|
if (ob.desc) {
|
return [this](uint32_t f, uint32_t s) {
|
return sqlite_utils::CompareValuesDesc(accessor_.Get(f),
|
accessor_.Get(s));
|
};
|
}
|
return [this](uint32_t f, uint32_t s) {
|
return sqlite_utils::CompareValuesAsc(accessor_.Get(f), accessor_.Get(s));
|
};
|
}
|
|
bool HasOrdering() const override { return accessor_.HasOrdering(); }
|
|
Table::ColumnType GetType() const override {
|
if (std::is_same<NumericType, int32_t>::value) {
|
return Table::ColumnType::kInt;
|
} else if (std::is_same<NumericType, uint8_t>::value ||
|
std::is_same<NumericType, uint32_t>::value) {
|
return Table::ColumnType::kUint;
|
} else if (std::is_same<NumericType, int64_t>::value) {
|
return Table::ColumnType::kLong;
|
} else if (std::is_same<NumericType, double>::value) {
|
return Table::ColumnType::kDouble;
|
}
|
PERFETTO_FATAL("Unexpected column type");
|
}
|
|
private:
|
static constexpr bool kIsIntegralType = std::is_integral<NumericType>::value;
|
static constexpr bool kIsRealType =
|
std::is_floating_point<NumericType>::value;
|
|
NumericType kTMin = std::numeric_limits<NumericType>::lowest();
|
NumericType kTMax = std::numeric_limits<NumericType>::max();
|
|
// Filters the rows of this column by creating the predicate from the sqlite
|
// value using type |UpcastNumericType| and casting data from the column
|
// to also be this type.
|
// Note: We cast here to make numeric comparisions as accurate as possible.
|
// For example, suppose NumericType == uint32_t and the sqlite value has
|
// an integer. Then UpcastNumericType == int64_t because uint32_t can be
|
// upcast to an int64_t and it's the most generic type we can compare using.
|
// Alternatively if either the column or sqlite value is real, we will always
|
// cast to a double before comparing.
|
template <typename UpcastNumericType>
|
void FilterWithCast(int op,
|
sqlite3_value* value,
|
FilteredRowIndex* index) const {
|
auto predicate =
|
sqlite_utils::CreateNumericPredicate<UpcastNumericType>(op, value);
|
auto cast_predicate = [this,
|
predicate](uint32_t row) PERFETTO_ALWAYS_INLINE {
|
return predicate(static_cast<UpcastNumericType>(accessor_.Get(row)));
|
};
|
index->FilterRows(cast_predicate);
|
}
|
|
Accessor accessor_;
|
};
|
|
// Defines an accessor for columns.
|
// An accessor is a abstraction over the method to retrieve data in a column. As
|
// there are many possible types of backing data (std::vector, std::deque,
|
// creating on the flight etc.), this class hides this complexity behind an
|
// interface to let the column implementation focus on actually interfacing
|
// with SQLite and rest of trace processor.
|
// This class exists as an interface for documentation purposes. There should
|
// be no use of it apart from classes inheriting from it to ensure they comply
|
// with the requirements of the interface.
|
template <typename DataType>
|
class Accessor {
|
public:
|
using Type = DataType;
|
|
virtual ~Accessor() = default;
|
|
// Returns the number of elements in the backing storage.
|
virtual uint32_t Size() const = 0;
|
|
// Returns the element located at index |idx|.
|
virtual Type Get(uint32_t idx) const = 0;
|
|
// Returns whether the backing data source is ordered. |LowerBoundIndex| and
|
// |UpperBoundIndex| will be called only if HasOrdering() returns true.
|
virtual bool HasOrdering() const { return false; }
|
|
// Returns the index of the lower bound of the value.
|
virtual uint32_t LowerBoundIndex(Type) const { PERFETTO_CHECK(false); }
|
|
// Returns the index of the lower bound of the value.
|
virtual uint32_t UpperBoundIndex(Type) const { PERFETTO_CHECK(false); }
|
|
// Returns whether the backing data sources can efficiently provide the
|
// indices of elements equal to a given value. |EqualIndices| will be called
|
// only if |CanFindEqualIndices| returns true.
|
virtual bool CanFindEqualIndices() const { return false; }
|
|
// Returns the indices into the backing data source with value equal to
|
// |value|.
|
virtual std::vector<uint32_t> EqualIndices(Type) const {
|
PERFETTO_CHECK(false);
|
}
|
};
|
|
// An accessor implementation for string which uses a deque to store offsets
|
// into a StringPool.
|
class StringPoolAccessor : public Accessor<NullTermStringView> {
|
public:
|
StringPoolAccessor(const std::deque<StringPool::Id>* deque,
|
const StringPool* string_pool);
|
~StringPoolAccessor() override;
|
|
uint32_t Size() const override {
|
return static_cast<uint32_t>(deque_->size());
|
}
|
|
NullTermStringView Get(uint32_t idx) const override {
|
return string_pool_->Get((*deque_)[idx]);
|
}
|
|
private:
|
const std::deque<StringPool::Id>* deque_;
|
const StringPool* string_pool_;
|
};
|
|
// An accessor implementation for string which uses a deque to store indices
|
// into a vector of strings.
|
template <typename Id>
|
class StringVectorAccessor : public Accessor<NullTermStringView> {
|
public:
|
StringVectorAccessor(const std::deque<Id>* deque,
|
const std::vector<const char*>* string_map)
|
: deque_(deque), string_map_(string_map) {}
|
~StringVectorAccessor() override = default;
|
|
uint32_t Size() const override {
|
return static_cast<uint32_t>(deque_->size());
|
}
|
|
NullTermStringView Get(uint32_t idx) const override {
|
const char* ptr = (*string_map_)[(*deque_)[idx]];
|
return ptr ? NullTermStringView(ptr) : NullTermStringView();
|
}
|
|
private:
|
const std::deque<Id>* deque_;
|
const std::vector<const char*>* string_map_;
|
};
|
|
// An accessor implementation for numeric columns which uses a deque as the
|
// backing storage with an opitonal index for quick equality filtering.
|
template <typename NumericType>
|
class NumericDequeAccessor : public Accessor<NumericType> {
|
public:
|
NumericDequeAccessor(const std::deque<NumericType>* deque,
|
const std::deque<std::vector<uint32_t>>* index,
|
bool has_ordering)
|
: deque_(deque), index_(index), has_ordering_(has_ordering) {}
|
~NumericDequeAccessor() override = default;
|
|
uint32_t Size() const override {
|
return static_cast<uint32_t>(deque_->size());
|
}
|
|
NumericType Get(uint32_t idx) const override { return (*deque_)[idx]; }
|
|
bool HasOrdering() const override { return has_ordering_; }
|
|
uint32_t LowerBoundIndex(NumericType value) const override {
|
PERFETTO_DCHECK(HasOrdering());
|
auto it = std::lower_bound(deque_->begin(), deque_->end(), value);
|
return static_cast<uint32_t>(std::distance(deque_->begin(), it));
|
}
|
|
uint32_t UpperBoundIndex(NumericType value) const override {
|
PERFETTO_DCHECK(HasOrdering());
|
auto it = std::upper_bound(deque_->begin(), deque_->end(), value);
|
return static_cast<uint32_t>(std::distance(deque_->begin(), it));
|
}
|
|
bool CanFindEqualIndices() const override {
|
return std::is_integral<NumericType>::value && index_ != nullptr;
|
}
|
|
std::vector<uint32_t> EqualIndices(NumericType value) const override {
|
PERFETTO_DCHECK(CanFindEqualIndices());
|
if (value < 0 || static_cast<size_t>(value) >= index_->size())
|
return {};
|
return (*index_)[static_cast<size_t>(value)];
|
}
|
|
private:
|
const std::deque<NumericType>* deque_ = nullptr;
|
const std::deque<std::vector<uint32_t>>* index_ = nullptr;
|
bool has_ordering_ = false;
|
};
|
|
class TsEndAccessor : public Accessor<int64_t> {
|
public:
|
TsEndAccessor(const std::deque<int64_t>* ts, const std::deque<int64_t>* dur);
|
~TsEndAccessor() override;
|
|
uint32_t Size() const override { return static_cast<uint32_t>(ts_->size()); }
|
|
int64_t Get(uint32_t idx) const override {
|
return (*ts_)[idx] + (*dur_)[idx];
|
}
|
|
private:
|
const std::deque<int64_t>* ts_ = nullptr;
|
const std::deque<int64_t>* dur_ = nullptr;
|
};
|
|
class RowIdAccessor : public Accessor<int64_t> {
|
public:
|
RowIdAccessor(TableId table_id);
|
~RowIdAccessor() override;
|
|
uint32_t Size() const override {
|
return std::numeric_limits<uint32_t>::max();
|
}
|
|
int64_t Get(uint32_t idx) const override {
|
return TraceStorage::CreateRowId(table_id_, idx);
|
}
|
|
private:
|
TableId table_id_;
|
};
|
|
class RowAccessor : public Accessor<uint32_t> {
|
public:
|
RowAccessor();
|
~RowAccessor() override;
|
|
uint32_t Size() const override {
|
return std::numeric_limits<uint32_t>::max();
|
}
|
|
uint32_t Get(uint32_t idx) const override { return idx; }
|
|
bool HasOrdering() const override { return true; }
|
|
uint32_t LowerBoundIndex(uint32_t idx) const override { return idx; }
|
|
uint32_t UpperBoundIndex(uint32_t idx) const override { return idx + 1; }
|
};
|
|
} // namespace trace_processor
|
} // namespace perfetto
|
|
#endif // SRC_TRACE_PROCESSOR_STORAGE_COLUMNS_H_
|