blob: 919334952289b5bfbaecc0a0d1ccde4913d3b098 [file] [log] [blame]
/*
* Copyright (C) 2023 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 <array>
#include <cstddef>
#include <memory>
#include <numeric>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/status_or.h"
#include "src/trace_processor/db/overlays/arrangement_overlay.h"
#include "src/trace_processor/db/overlays/null_overlay.h"
#include "src/trace_processor/db/overlays/selector_overlay.h"
#include "src/trace_processor/db/overlays/storage_overlay.h"
#include "src/trace_processor/db/overlays/types.h"
#include "src/trace_processor/db/query_executor.h"
#include "src/trace_processor/db/storage/id_storage.h"
#include "src/trace_processor/db/storage/numeric_storage.h"
#include "src/trace_processor/db/storage/string_storage.h"
#include "src/trace_processor/db/storage/types.h"
#include "src/trace_processor/db/table.h"
namespace perfetto {
namespace trace_processor {
namespace {
using Range = RowMap::Range;
using OverlayOp = overlays::OverlayOp;
using StorageRange = overlays::StorageRange;
using TableRange = overlays::TableRange;
using Storage = storage::Storage;
using StorageOverlay = overlays::StorageOverlay;
using TableIndexVector = overlays::TableIndexVector;
using StorageIndexVector = overlays::StorageIndexVector;
using TableBitVector = overlays::TableBitVector;
using StorageBitVector = overlays::StorageBitVector;
using OverlaysVec = base::SmallVector<const overlays::StorageOverlay*,
QueryExecutor::kMaxOverlayCount>;
// Helper struct to simplify operations on |global| and |current| sets of
// indices. Having this coupling enables efficient implementation of
// IndexedColumnFilter.
struct IndexFilterHelper {
explicit IndexFilterHelper(std::vector<uint32_t> indices) {
current_ = indices;
global_ = std::move(indices);
}
// Removes pairs of elements that are not set in the |bv| and returns
// Indices made of them.
static std::pair<IndexFilterHelper, IndexFilterHelper> Partition(
IndexFilterHelper indices,
const BitVector& bv) {
if (bv.CountSetBits() == 0) {
return {IndexFilterHelper(), indices};
}
IndexFilterHelper set_partition;
IndexFilterHelper non_set_partition;
for (auto it = bv.IterateAllBits(); it; it.Next()) {
uint32_t idx = it.index();
if (it.IsSet()) {
set_partition.PushBack({indices.current_[idx], indices.global_[idx]});
} else {
non_set_partition.PushBack(
{indices.current_[idx], indices.global_[idx]});
}
}
return {set_partition, non_set_partition};
}
// Removes pairs of elements that are not set in the |bv|. Returns count of
// removed elements.
uint32_t KeepAtSet(BitVector filter_nulls) {
PERFETTO_DCHECK(filter_nulls.size() == current_.size() ||
filter_nulls.CountSetBits() == 0);
uint32_t count_removed =
static_cast<uint32_t>(current_.size()) - filter_nulls.CountSetBits();
uint32_t i = 0;
auto filter = [&i, &filter_nulls](uint32_t) {
return !filter_nulls.IsSet(i++);
};
auto current_it = std::remove_if(current_.begin(), current_.end(), filter);
current_.erase(current_it, current_.end());
i = 0;
auto global_it = std::remove_if(global_.begin(), global_.end(), filter);
global_.erase(global_it, global_.end());
return count_removed;
}
std::vector<uint32_t>& current() { return current_; }
std::vector<uint32_t>& global() { return global_; }
private:
IndexFilterHelper() = default;
void PushBack(std::pair<uint32_t, uint32_t> cur_and_global_idx) {
current_.push_back(cur_and_global_idx.first);
global_.push_back(cur_and_global_idx.second);
}
std::vector<uint32_t> current_;
std::vector<uint32_t> global_;
};
} // namespace
void QueryExecutor::FilterColumn(const Constraint& c,
const SimpleColumn& col,
RowMap* rm) {
if (rm->empty())
return;
uint32_t rm_size = rm->size();
uint32_t rm_first = rm->Get(0);
uint32_t rm_last = rm->Get(rm_size - 1);
uint32_t range_size = rm_last - rm_first;
// If the number of elements in the rowmap is small or the number of elements
// is less than 1/10th of the range, use indexed filtering.
// TODO(b/283763282): use Overlay estimations.
bool disallows_index_search = rm->IsRange();
bool prefers_index_search =
rm->IsIndexVector() || rm_size < 1024 || rm_size * 10 < range_size;
if (!disallows_index_search && prefers_index_search) {
*rm = IndexSearch(c, col, rm);
return;
}
LinearSearch(c, col, rm);
}
void QueryExecutor::LinearSearch(const Constraint& c,
const SimpleColumn& col,
RowMap* rm) {
// TODO(b/283763282): Align these to word boundaries.
TableRange bounds{Range(rm->Get(0), rm->Get(rm->size() - 1) + 1)};
// Translate the bounds to the storage level.
for (const auto& overlay : col.overlays) {
bounds = TableRange({overlay->MapToStorageRange(bounds).range});
}
// Search the storage.
overlays::TableRangeOrBitVector res(
col.storage->Search(c.op, c.value, bounds.range));
// Translate the result to global level.
OverlayOp op = overlays::FilterOpToOverlayOp(c.op);
for (uint32_t i = 0; i < col.overlays.size(); ++i) {
uint32_t rev_i = static_cast<uint32_t>(col.overlays.size()) - 1 - i;
if (res.IsBitVector()) {
TableBitVector t_bv = col.overlays[rev_i]->MapToTableBitVector(
StorageBitVector{std::move(res).TakeIfBitVector()}, op);
res.val = RangeOrBitVector(std::move(t_bv.bv));
} else {
res = col.overlays[rev_i]->MapToTableRangeOrBitVector(
StorageRange(std::move(res).TakeIfRange()), op);
}
}
if (rm->IsRange()) {
if (res.IsRange()) {
Range range = std::move(res).TakeIfRange();
*rm = RowMap(range.start, range.end);
} else {
// The BitVector was already limited on the RowMap when created, so we can
// take it as it is.
*rm = RowMap(std::move(res).TakeIfBitVector());
}
return;
}
if (res.IsRange()) {
Range range = std::move(res).TakeIfRange();
rm->Intersect(RowMap(range.start, range.end));
return;
}
rm->Intersect(RowMap(std::move(res).TakeIfBitVector()));
}
RowMap QueryExecutor::IndexSearch(const Constraint& c,
const SimpleColumn& col,
RowMap* rm) {
// Create outmost TableIndexVector.
std::vector<uint32_t> table_indices = std::move(*rm).TakeAsIndexVector();
// Datastructures for storing data across overlays.
IndexFilterHelper to_filter(std::move(table_indices));
std::vector<uint32_t> matched;
uint32_t count_removed = 0;
uint32_t count_starting_indices =
static_cast<uint32_t>(to_filter.current().size());
// Fetch the list of indices that require storage lookup and deal with all
// of the indices that can be compared before it.
OverlayOp op = overlays::FilterOpToOverlayOp(c.op);
for (const auto& overlay : col.overlays) {
BitVector partition =
overlay->IsStorageLookupRequired(op, {to_filter.current()});
// Most overlays don't require partitioning.
if (partition.CountSetBits() == partition.size()) {
to_filter.current() =
overlay->MapToStorageIndexVector({to_filter.current()}).indices;
continue;
}
// Separate indices that don't require storage lookup. Those can be dealt
// with in each pass.
auto [storage_lookup, no_storage_lookup] =
IndexFilterHelper::Partition(to_filter, partition);
to_filter = storage_lookup;
// Erase the values which don't match the constraint and add the
// remaining ones to the result.
BitVector valid_bv =
overlay->IndexSearch(op, {no_storage_lookup.current()});
count_removed += no_storage_lookup.KeepAtSet(std::move(valid_bv));
matched.insert(matched.end(), no_storage_lookup.global().begin(),
no_storage_lookup.global().end());
// Update the current indices to the next storage overlay.
to_filter.current() =
overlay->MapToStorageIndexVector({to_filter.current()}).indices;
}
RangeOrBitVector matched_in_storage = col.storage->IndexSearch(
c.op, c.value, to_filter.current().data(),
static_cast<uint32_t>(to_filter.current().size()));
// TODO(b/283763282): Remove after implementing extrinsic binary search.
PERFETTO_DCHECK(matched_in_storage.IsBitVector());
count_removed +=
to_filter.KeepAtSet(std::move(matched_in_storage).TakeIfBitVector());
matched.insert(matched.end(), to_filter.global().begin(),
to_filter.global().end());
PERFETTO_CHECK(count_starting_indices == matched.size() + count_removed);
std::sort(matched.begin(), matched.end());
return RowMap(std::move(matched));
}
RowMap QueryExecutor::FilterLegacy(const Table* table,
const std::vector<Constraint>& c_vec) {
RowMap rm(0, table->row_count());
for (const auto& c : c_vec) {
const Column& col = table->columns()[c.col_idx];
uint32_t column_size =
col.IsId() ? col.overlay().row_map().Max() : col.storage_base().size();
// RowMap size
bool use_legacy = rm.size() <= 1;
// Rare cases where we have a range which doesn't match the size of the
// column.
use_legacy = use_legacy || (col.overlay().size() != column_size &&
col.overlay().row_map().IsRange());
// Rare cases where string columns can be sorted.
use_legacy =
use_legacy || (col.IsSorted() && col.col_type() == ColumnType::kString);
// Column types
use_legacy = use_legacy || col.col_type() == ColumnType::kDummy;
// Mismatched types
use_legacy = use_legacy || (overlays::FilterOpToOverlayOp(c.op) ==
overlays::OverlayOp::kOther &&
col.type() != c.value.type);
// Specific column flags.
use_legacy = use_legacy || col.IsDense() || col.IsSetId();
// Extrinsically sorted columns
use_legacy = use_legacy ||
(col.IsSorted() && col.overlay().row_map().IsIndexVector());
if (use_legacy) {
col.FilterInto(c.op, c.value, &rm);
continue;
}
// String columns are inherently nullable: null values are signified with
// Id::Null().
PERFETTO_CHECK(
!(col.col_type() == ColumnType::kString && col.IsNullable()));
SimpleColumn s_col{OverlaysVec(), nullptr};
// Create storage
std::unique_ptr<Storage> storage;
if (col.IsId()) {
storage.reset(new storage::IdStorage(column_size));
} else if (col.col_type() == ColumnType::kString) {
storage.reset(new storage::StringStorage(
table->string_pool(),
static_cast<const StringPool::Id*>(col.storage_base().data()),
col.storage_base().non_null_size()));
} else {
storage.reset(new storage::NumericStorage(
col.storage_base().data(), col.storage_base().non_null_size(),
col.col_type(), col.IsSorted()));
}
s_col.storage = storage.get();
// Create cDBv2 overlays based on col.overlay()
overlays::SelectorOverlay selector_overlay(
col.overlay().row_map().GetIfBitVector());
if (col.overlay().size() != column_size &&
col.overlay().row_map().IsBitVector())
s_col.overlays.emplace_back(&selector_overlay);
overlays::ArrangementOverlay arrangement_overlay(
col.overlay().row_map().GetIfIndexVector());
if (col.overlay().row_map().IsIndexVector())
s_col.overlays.emplace_back(&arrangement_overlay);
// Add nullability
BitVector null_bv;
overlays::NullOverlay null_overlay(
col.IsNullable() ? col.storage_base().bv() : &null_bv);
if (col.IsNullable())
s_col.overlays.emplace_back(&null_overlay);
uint32_t pre_count = rm.size();
FilterColumn(c, s_col, &rm);
PERFETTO_DCHECK(rm.size() <= pre_count);
}
return rm;
}
} // namespace trace_processor
} // namespace perfetto