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