Skip to content

Commit 29817f8

Browse files
committed
tp: preserve dataframe scan storage semantics
Direct scans must only borrow stable finalized storage and must retain nullable ID values instead of treating every ID column as a non-null sequence.
1 parent 0aa09b5 commit 29817f8

4 files changed

Lines changed: 137 additions & 19 deletions

File tree

src/trace_processor/core/exec/column_view.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ class ColumnView {
4848
ColumnView view;
4949
view.type_ = type;
5050
if (type.Is<Id>()) {
51-
PERFETTO_DCHECK(data == nullptr && validity == nullptr);
51+
PERFETTO_DCHECK(data == nullptr);
5252
view.kind_ = Kind::kSequence;
53+
view.validity_ = validity;
5354
return view;
5455
}
5556
view.kind_ = Kind::kFlat;

src/trace_processor/core/exec/dataframe_scan.cc

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
#include <algorithm>
2020
#include <cstdint>
2121
#include <memory>
22+
#include <type_traits>
2223
#include <utility>
2324
#include <vector>
2425

26+
#include "perfetto/base/logging.h"
27+
2528
#include "src/trace_processor/containers/string_pool.h"
2629
#include "src/trace_processor/core/common/storage_types.h"
2730
#include "src/trace_processor/core/dataframe/dataframe.h"
@@ -70,7 +73,14 @@ class ExpanderImpl final : public DataframeScan::Expander {
7073
buffer_->validity.ClearAllBits();
7174
for (uint32_t row = 0; row < count; ++row) {
7275
if (bits_->is_set(from + row)) {
73-
buffer_->values[row] = packed_[consumed_++];
76+
if constexpr (std::is_same_v<T, uint32_t>) {
77+
buffer_->values[row] =
78+
packed_ ? packed_[consumed_] : static_cast<uint32_t>(consumed_);
79+
} else {
80+
PERFETTO_DCHECK(packed_);
81+
buffer_->values[row] = packed_[consumed_];
82+
}
83+
++consumed_;
7484
buffer_->validity.set(row);
7585
} else {
7686
// Written even for a null row, so the storage is readable everywhere.
@@ -134,9 +144,11 @@ void BuildColumn(const dataframe::Column& column,
134144

135145
} // namespace
136146

137-
DataframeScan::DataframeScan(const dataframe::Dataframe* dataframe,
147+
DataframeScan::DataframeScan(const dataframe::Dataframe& dataframe,
138148
std::vector<uint32_t> columns)
139-
: dataframe_(dataframe), columns_(std::move(columns)) {}
149+
: dataframe_(&dataframe), columns_(std::move(columns)) {
150+
PERFETTO_CHECK(dataframe.finalized());
151+
}
140152

141153
DataframeScan::~DataframeScan() = default;
142154
DataframeScan::State::~State() = default;
@@ -150,8 +162,18 @@ std::unique_ptr<OperatorState> DataframeScan::MakeState() const {
150162
uint32_t index = columns_[i];
151163
StorageType type = dataframe_->column_type(index);
152164
if (type.Is<Id>()) {
153-
// No storage at all: the value is the row it sits at.
154-
state->columns[i] = ColumnView::Reference(type, nullptr, nullptr);
165+
const auto& nulls = dataframe_->column(index).null_storage;
166+
if (nulls.nullability().Is<NonNull>()) {
167+
state->columns[i] = ColumnView::Reference(type, nullptr, nullptr);
168+
} else if (nulls.nullability().Is<DenseNull>()) {
169+
state->columns[i] =
170+
ColumnView::Reference(type, nullptr, &nulls.GetNullBitVector());
171+
} else {
172+
auto impl = std::make_unique<ExpanderImpl<uint32_t>>(
173+
StorageType{Uint32{}}, nullptr, &nulls.GetNullBitVector());
174+
state->owners[i] = impl->owner();
175+
state->expanders[i] = std::move(impl);
176+
}
155177
continue;
156178
}
157179
const dataframe::Column& column = dataframe_->column(index);

src/trace_processor/core/exec/dataframe_scan.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ namespace perfetto::trace_processor::core::exec {
4141
// execution, so a relation can be free for most of its columns and pay a
4242
// bounded amount for the rest. Nothing is materialised ahead of being asked
4343
// for, so a query which reads one batch and stops does one batch of work.
44+
//
45+
// The dataframe must be finalized and must outlive the scan.
4446
class DataframeScan : public Source {
4547
public:
46-
DataframeScan(const dataframe::Dataframe*, std::vector<uint32_t> columns);
48+
DataframeScan(const dataframe::Dataframe&, std::vector<uint32_t> columns);
4749
~DataframeScan() override;
4850

4951
std::unique_ptr<OperatorState> MakeState() const override;

src/trace_processor/core/exec/dataframe_scan_unittest.cc

Lines changed: 105 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
#include <cstdint>
2020
#include <memory>
21+
#include <optional>
2122
#include <string>
23+
#include <variant>
2224
#include <vector>
2325

2426
#include "src/trace_processor/containers/string_pool.h"
@@ -28,12 +30,15 @@
2830
#include "src/trace_processor/core/exec/column_view.h"
2931
#include "src/trace_processor/core/exec/operator.h"
3032
#include "src/trace_processor/core/exec/row_batch.h"
33+
#include "src/trace_processor/core/exec/test_utils.h"
3134
#include "test/gtest_and_gmock.h"
3235

3336
namespace perfetto::trace_processor::core::exec {
3437
namespace {
3538

3639
using ::testing::ElementsAre;
40+
using ::testing::Eq;
41+
using ::testing::Optional;
3742

3843
// Bigger than an int32, so a column holding it is stored as an int64.
3944
constexpr int64_t kBig = int64_t{1} << 40;
@@ -62,23 +67,111 @@ class DataframeScanTest : public ::testing::Test {
6267
RowBatch batch;
6368
std::vector<int64_t> out;
6469
while (scan.GetData(batch, *state)) {
65-
const ColumnView& view = batch.column(column);
66-
const auto* data = static_cast<const int64_t*>(view.data());
67-
for (uint32_t i = 0; i < batch.size(); ++i) {
68-
out.push_back(data[view.selection().GetIndex(i)]);
69-
}
70+
std::vector<int64_t> values = test::ReadColumn<int64_t>(batch, column);
71+
out.insert(out.end(), values.begin(), values.end());
7072
}
7173
return out;
7274
}
7375

7476
StringPool pool_;
7577
};
7678

79+
inline constexpr auto kAllTypes = dataframe::CreateTypedDataframeSpec(
80+
{"id", "u32", "i32", "i64", "double", "string"},
81+
dataframe::CreateTypedColumnSpec(Id{},
82+
NonNull{},
83+
IdSorted{},
84+
NoDuplicates{}),
85+
dataframe::CreateTypedColumnSpec(Uint32{}, NonNull{}, Unsorted{}),
86+
dataframe::CreateTypedColumnSpec(Int32{}, NonNull{}, Unsorted{}),
87+
dataframe::CreateTypedColumnSpec(Int64{}, NonNull{}, Unsorted{}),
88+
dataframe::CreateTypedColumnSpec(Double{}, NonNull{}, Unsorted{}),
89+
dataframe::CreateTypedColumnSpec(String{}, NonNull{}, Unsorted{}));
90+
91+
inline constexpr auto kSparseId = dataframe::CreateTypedDataframeSpec(
92+
{"id"},
93+
dataframe::CreateTypedColumnSpec(Id{},
94+
SparseNull{},
95+
Unsorted{},
96+
NoDuplicates{}));
97+
98+
inline constexpr auto kDenseId = dataframe::CreateTypedDataframeSpec(
99+
{"id"},
100+
dataframe::CreateTypedColumnSpec(Id{},
101+
DenseNull{},
102+
Unsorted{},
103+
NoDuplicates{}));
104+
105+
TEST_F(DataframeScanTest, ReadsEveryStorageType) {
106+
StringPool::Id first = pool_.InternString("first");
107+
StringPool::Id second = pool_.InternString("second");
108+
dataframe::Dataframe df =
109+
dataframe::Dataframe::CreateFromTypedSpec(kAllTypes, &pool_);
110+
df.InsertUnchecked(kAllTypes, std::monostate{}, uint32_t{7}, int32_t{-3},
111+
kBig, 1.5, first);
112+
df.InsertUnchecked(kAllTypes, std::monostate{}, uint32_t{9}, int32_t{4},
113+
kBig + 1, 2.5, second);
114+
df.Finalize();
115+
116+
DataframeScan scan(df, {0, 1, 2, 3, 4, 5});
117+
std::unique_ptr<OperatorState> state = scan.MakeState();
118+
RowBatch batch;
119+
ASSERT_TRUE(scan.GetData(batch, *state));
120+
EXPECT_TRUE(batch.column(0).type().Is<Id>());
121+
EXPECT_TRUE(batch.column(1).type().Is<Uint32>());
122+
EXPECT_TRUE(batch.column(2).type().Is<Int32>());
123+
EXPECT_TRUE(batch.column(3).type().Is<Int64>());
124+
EXPECT_TRUE(batch.column(4).type().Is<Double>());
125+
EXPECT_TRUE(batch.column(5).type().Is<String>());
126+
EXPECT_THAT(test::ReadColumn<uint32_t>(batch, 0), ElementsAre(0u, 1u));
127+
EXPECT_THAT(test::ReadColumn<uint32_t>(batch, 1), ElementsAre(7u, 9u));
128+
EXPECT_THAT(test::ReadColumn<int32_t>(batch, 2), ElementsAre(-3, 4));
129+
EXPECT_THAT(test::ReadColumn<int64_t>(batch, 3), ElementsAre(kBig, kBig + 1));
130+
EXPECT_THAT(test::ReadColumn<double>(batch, 4), ElementsAre(1.5, 2.5));
131+
EXPECT_THAT(test::ReadColumn<StringPool::Id>(batch, 5),
132+
ElementsAre(first, second));
133+
}
134+
135+
TEST_F(DataframeScanTest, PreservesNullableIdSemantics) {
136+
dataframe::Dataframe sparse =
137+
dataframe::Dataframe::CreateFromTypedSpec(kSparseId, &pool_);
138+
sparse.InsertUnchecked(kSparseId,
139+
std::optional<std::monostate>{std::monostate{}});
140+
sparse.InsertUnchecked(kSparseId, std::optional<std::monostate>{});
141+
sparse.InsertUnchecked(kSparseId,
142+
std::optional<std::monostate>{std::monostate{}});
143+
sparse.Finalize();
144+
145+
DataframeScan sparse_scan(sparse, {0});
146+
std::unique_ptr<OperatorState> sparse_state = sparse_scan.MakeState();
147+
RowBatch batch;
148+
ASSERT_TRUE(sparse_scan.GetData(batch, *sparse_state));
149+
EXPECT_TRUE(batch.column(0).type().Is<Uint32>());
150+
EXPECT_THAT(test::ReadNullableColumn<uint32_t>(batch, 0),
151+
ElementsAre(Optional(0u), Eq(std::nullopt), Optional(1u)));
152+
153+
dataframe::Dataframe dense =
154+
dataframe::Dataframe::CreateFromTypedSpec(kDenseId, &pool_);
155+
dense.InsertUnchecked(kDenseId,
156+
std::optional<std::monostate>{std::monostate{}});
157+
dense.InsertUnchecked(kDenseId, std::optional<std::monostate>{});
158+
dense.InsertUnchecked(kDenseId,
159+
std::optional<std::monostate>{std::monostate{}});
160+
dense.Finalize();
161+
162+
DataframeScan dense_scan(dense, {0});
163+
std::unique_ptr<OperatorState> dense_state = dense_scan.MakeState();
164+
ASSERT_TRUE(dense_scan.GetData(batch, *dense_state));
165+
EXPECT_TRUE(batch.column(0).type().Is<Id>());
166+
EXPECT_THAT(test::ReadNullableColumn<uint32_t>(batch, 0),
167+
ElementsAre(Optional(0u), Eq(std::nullopt), Optional(2u)));
168+
}
169+
77170
// The point of the operator: the batch reads the dataframe's own storage.
78171
TEST_F(DataframeScanTest, ReadsTheDataframesOwnStorage) {
79172
dataframe::Dataframe df =
80173
Build({kBig + 10, kBig + 20, kBig + 30}, {true, true, true});
81-
DataframeScan scan(&df, {0});
174+
DataframeScan scan(df, {0});
82175

83176
std::unique_ptr<OperatorState> state = scan.MakeState();
84177
RowBatch batch;
@@ -91,7 +184,7 @@ TEST_F(DataframeScanTest, ReadsTheDataframesOwnStorage) {
91184
TEST_F(DataframeScanTest, HandsBackEveryRow) {
92185
dataframe::Dataframe df =
93186
Build({kBig + 10, kBig + 20, kBig + 30}, {true, true, true});
94-
DataframeScan scan(&df, {0});
187+
DataframeScan scan(df, {0});
95188
EXPECT_THAT(Drain(scan, 0), ElementsAre(kBig + 10, kBig + 20, kBig + 30));
96189
}
97190

@@ -102,7 +195,7 @@ TEST_F(DataframeScanTest, SplitsIntoBatches) {
102195
values[i] = kBig + i;
103196
}
104197
dataframe::Dataframe df = Build(values, present);
105-
DataframeScan scan(&df, {0});
198+
DataframeScan scan(df, {0});
106199
EXPECT_EQ(Drain(scan, 0).size(), values.size());
107200
}
108201

@@ -111,7 +204,7 @@ TEST_F(DataframeScanTest, SplitsIntoBatches) {
111204
TEST_F(DataframeScanTest, AColumnWithoutASlotPerRowIsExpanded) {
112205
dataframe::Dataframe df =
113206
Build({kBig + 10, 0, kBig + 30}, {true, false, true});
114-
DataframeScan scan(&df, {0});
207+
DataframeScan scan(df, {0});
115208

116209
std::unique_ptr<OperatorState> state = scan.MakeState();
117210
RowBatch batch;
@@ -142,15 +235,15 @@ TEST_F(DataframeScanTest, AColumnWithoutASlotPerRowSpansBatches) {
142235
expected.push_back(values[i]);
143236
}
144237
dataframe::Dataframe df = Build(values, present);
145-
DataframeScan scan(&df, {0});
238+
DataframeScan scan(df, {0});
146239
EXPECT_EQ(Drain(scan, 0), expected);
147240
}
148241

149242
// Replaying has to wind the packed values back too, not just the row counter.
150243
TEST_F(DataframeScanTest, AColumnWithoutASlotPerRowIsReplayable) {
151244
dataframe::Dataframe df =
152245
Build({kBig + 10, 0, kBig + 30}, {true, false, true});
153-
DataframeScan scan(&df, {0});
246+
DataframeScan scan(df, {0});
154247

155248
std::unique_ptr<OperatorState> state = scan.MakeState();
156249
RowBatch batch;
@@ -167,7 +260,7 @@ TEST_F(DataframeScanTest, AColumnWithoutASlotPerRowIsReplayable) {
167260

168261
TEST_F(DataframeScanTest, IsReplayable) {
169262
dataframe::Dataframe df = Build({kBig + 1, kBig + 2}, {true, true});
170-
DataframeScan scan(&df, {0});
263+
DataframeScan scan(df, {0});
171264

172265
std::unique_ptr<OperatorState> state = scan.MakeState();
173266
RowBatch batch;

0 commit comments

Comments
 (0)