1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/db/runtime_table.h"
18
19 #include <algorithm>
20 #include <cinttypes>
21 #include <cstddef>
22 #include <cstdint>
23 #include <limits>
24 #include <memory>
25 #include <optional>
26 #include <string>
27 #include <utility>
28 #include <variant>
29 #include <vector>
30
31 #include "perfetto/base/logging.h"
32 #include "perfetto/base/status.h"
33 #include "perfetto/ext/base/status_or.h"
34 #include "perfetto/trace_processor/basic_types.h"
35 #include "perfetto/trace_processor/ref_counted.h"
36 #include "src/trace_processor/containers/bit_vector.h"
37 #include "src/trace_processor/containers/string_pool.h"
38 #include "src/trace_processor/db/column.h"
39 #include "src/trace_processor/db/column/data_layer.h"
40 #include "src/trace_processor/db/column/id_storage.h"
41 #include "src/trace_processor/db/column/null_overlay.h"
42 #include "src/trace_processor/db/column/numeric_storage.h"
43 #include "src/trace_processor/db/column/overlay_layer.h"
44 #include "src/trace_processor/db/column/selector_overlay.h"
45 #include "src/trace_processor/db/column/storage_layer.h"
46 #include "src/trace_processor/db/column/string_storage.h"
47 #include "src/trace_processor/db/column/types.h"
48 #include "src/trace_processor/db/column_storage.h"
49 #include "src/trace_processor/db/column_storage_overlay.h"
50
51 namespace perfetto::trace_processor {
52 namespace {
53
54 template <typename T, typename U>
Fill(uint32_t leading_nulls,U value)55 T Fill(uint32_t leading_nulls, U value) {
56 T res;
57 for (uint32_t i = 0; i < leading_nulls; ++i) {
58 res.Append(value);
59 }
60 return res;
61 }
62
IsPerfectlyRepresentableAsDouble(int64_t res)63 bool IsPerfectlyRepresentableAsDouble(int64_t res) {
64 static constexpr int64_t kMaxDoubleRepresentible = 1ull << 53;
65 return res >= -kMaxDoubleRepresentible && res <= kMaxDoubleRepresentible;
66 }
67
CreateNonNullableIntsColumn(uint32_t col_idx,const char * col_name,ColumnStorage<int64_t> * ints_storage,std::vector<RefPtr<column::StorageLayer>> & storage_layers,std::vector<RefPtr<column::OverlayLayer>> & overlay_layers,std::vector<ColumnLegacy> & legacy_columns,std::vector<ColumnStorageOverlay> & legacy_overlays)68 void CreateNonNullableIntsColumn(
69 uint32_t col_idx,
70 const char* col_name,
71 ColumnStorage<int64_t>* ints_storage,
72 std::vector<RefPtr<column::StorageLayer>>& storage_layers,
73 std::vector<RefPtr<column::OverlayLayer>>& overlay_layers,
74 std::vector<ColumnLegacy>& legacy_columns,
75 std::vector<ColumnStorageOverlay>& legacy_overlays) {
76 const std::vector<int64_t>& values = ints_storage->vector();
77
78 // Looking for the iterator to the first value that is less or equal to the
79 // previous value. The values before are therefore strictly monotonic - each
80 // is greater than the previous one.
81 bool is_monotonic = true;
82 bool is_sorted = true;
83 for (uint32_t i = 1; i < values.size() && is_sorted; i++) {
84 is_monotonic = is_monotonic && values[i - 1] < values[i];
85 is_sorted = values[i - 1] <= values[i];
86 }
87
88 // The special treatement for Id columns makes no sense for empty or
89 // single element indices. Those should be treated as standard int
90 // column.
91
92 // We expect id column to:
93 // - be strictly monotonic.
94 bool is_id = is_monotonic;
95 // - have more than 1 element.
96 is_id = is_id && values.size() > 1;
97 // - have first elements smaller then 2^20, mostly to prevent timestamps
98 // columns from becoming Id columns.
99 is_id = is_id && values.front() < 1 << 20;
100 // - have `uint32_t` values.
101 is_id = is_id && values.front() >= std::numeric_limits<uint32_t>::min() &&
102 values.back() < std::numeric_limits<uint32_t>::max();
103 // - have on average more than 1 set bit per int64_t (over 1/64 density)
104 is_id = is_id && static_cast<uint32_t>(values.back()) < 64 * values.size();
105
106 if (is_id) {
107 // The column is an Id column.
108 storage_layers[col_idx].reset(new column::IdStorage());
109
110 // If the id is dense (i.e. the start is zero and the size equals the last
111 // value) then there's no need for an overlay.
112 bool is_dense = values.front() == 0 &&
113 static_cast<uint32_t>(values.back()) == values.size() - 1;
114 if (is_dense) {
115 legacy_columns.push_back(
116 ColumnLegacy::IdColumn(col_idx, 0, col_name, ColumnLegacy::kIdFlags));
117 } else {
118 legacy_overlays.emplace_back(BitVector::FromSortedIndexVector(values));
119 overlay_layers.emplace_back().reset(new column::SelectorOverlay(
120 legacy_overlays.back().row_map().GetIfBitVector()));
121 legacy_columns.push_back(ColumnLegacy::IdColumn(
122 col_idx, static_cast<uint32_t>(legacy_overlays.size() - 1), col_name,
123 ColumnLegacy::kIdFlags));
124 }
125 return;
126 }
127
128 uint32_t flags =
129 is_sorted ? ColumnLegacy::Flag::kNonNull | ColumnLegacy::Flag::kSorted
130 : ColumnLegacy::Flag::kNonNull;
131
132 legacy_columns.emplace_back(col_name, ints_storage, flags, col_idx, 0);
133 storage_layers[col_idx].reset(new column::NumericStorage<int64_t>(
134 &values, ColumnType::kInt64, is_sorted));
135 }
136
137 } // namespace
138
RuntimeTable(StringPool * pool,uint32_t row_count,std::vector<ColumnLegacy> columns,std::vector<ColumnStorageOverlay> overlays,std::vector<RefPtr<column::StorageLayer>> storage_layers,std::vector<RefPtr<column::OverlayLayer>> null_layers,std::vector<RefPtr<column::OverlayLayer>> overlay_layers)139 RuntimeTable::RuntimeTable(
140 StringPool* pool,
141 uint32_t row_count,
142 std::vector<ColumnLegacy> columns,
143 std::vector<ColumnStorageOverlay> overlays,
144 std::vector<RefPtr<column::StorageLayer>> storage_layers,
145 std::vector<RefPtr<column::OverlayLayer>> null_layers,
146 std::vector<RefPtr<column::OverlayLayer>> overlay_layers)
147 : Table(pool, row_count, std::move(columns), std::move(overlays)) {
148 OnConstructionCompleted(std::move(storage_layers), std::move(null_layers),
149 std::move(overlay_layers));
150 }
151
152 RuntimeTable::~RuntimeTable() = default;
153
Builder(StringPool * pool,const std::vector<std::string> & col_names)154 RuntimeTable::Builder::Builder(StringPool* pool,
155 const std::vector<std::string>& col_names)
156 : Builder(pool,
157 col_names,
158 std::vector<BuilderColumnType>(col_names.size(), kNull)) {}
159
Builder(StringPool * pool,const std::vector<std::string> & col_names,const std::vector<BuilderColumnType> & col_types)160 RuntimeTable::Builder::Builder(StringPool* pool,
161 const std::vector<std::string>& col_names,
162 const std::vector<BuilderColumnType>& col_types)
163 : string_pool_(pool), col_names_(col_names) {
164 for (BuilderColumnType type : col_types) {
165 switch (type) {
166 case kNull:
167 storage_.emplace_back(std::make_unique<VariantStorage>());
168 break;
169 case kInt:
170 storage_.emplace_back(std::make_unique<VariantStorage>(IntStorage()));
171 break;
172 case kNullInt:
173 storage_.emplace_back(
174 std::make_unique<VariantStorage>(NullIntStorage()));
175 break;
176 case kDouble:
177 storage_.emplace_back(
178 std::make_unique<VariantStorage>(DoubleStorage()));
179 break;
180 case kNullDouble:
181 storage_.emplace_back(
182 std::make_unique<VariantStorage>(NullDoubleStorage()));
183 break;
184 case kString:
185 storage_.emplace_back(
186 std::make_unique<VariantStorage>(StringStorage()));
187 break;
188 }
189 }
190 }
191
AddNull(uint32_t idx)192 base::Status RuntimeTable::Builder::AddNull(uint32_t idx) {
193 auto* col = storage_[idx].get();
194 if (auto* leading_nulls = std::get_if<uint32_t>(col)) {
195 (*leading_nulls)++;
196 } else if (auto* ints = std::get_if<NullIntStorage>(col)) {
197 ints->Append(std::nullopt);
198 } else if (auto* strings = std::get_if<StringStorage>(col)) {
199 strings->Append(StringPool::Id::Null());
200 } else if (auto* doubles = std::get_if<NullDoubleStorage>(col)) {
201 doubles->Append(std::nullopt);
202 } else {
203 PERFETTO_FATAL("Unexpected column type");
204 }
205 return base::OkStatus();
206 }
207
AddInteger(uint32_t idx,int64_t res)208 base::Status RuntimeTable::Builder::AddInteger(uint32_t idx, int64_t res) {
209 auto* col = storage_[idx].get();
210 if (auto* leading_nulls_ptr = std::get_if<uint32_t>(col)) {
211 *col = Fill<NullIntStorage>(*leading_nulls_ptr, std::nullopt);
212 }
213 if (auto* doubles = std::get_if<NullDoubleStorage>(col)) {
214 if (!IsPerfectlyRepresentableAsDouble(res)) {
215 return base::ErrStatus("Column %s contains %" PRId64
216 " which cannot be represented as a double",
217 col_names_[idx].c_str(), res);
218 }
219 doubles->Append(static_cast<double>(res));
220 return base::OkStatus();
221 }
222 auto* ints = std::get_if<NullIntStorage>(col);
223 if (!ints) {
224 return base::ErrStatus("Column %s does not have consistent types",
225 col_names_[idx].c_str());
226 }
227 ints->Append(res);
228 return base::OkStatus();
229 }
230
AddFloat(uint32_t idx,double res)231 base::Status RuntimeTable::Builder::AddFloat(uint32_t idx, double res) {
232 auto* col = storage_[idx].get();
233 if (auto* leading_nulls_ptr = std::get_if<uint32_t>(col)) {
234 *col = Fill<NullDoubleStorage>(*leading_nulls_ptr, std::nullopt);
235 }
236 if (auto* ints = std::get_if<NullIntStorage>(col)) {
237 NullDoubleStorage storage;
238 for (uint32_t i = 0; i < ints->size(); ++i) {
239 std::optional<int64_t> int_val = ints->Get(i);
240 if (!int_val) {
241 storage.Append(std::nullopt);
242 continue;
243 }
244 if (int_val && !IsPerfectlyRepresentableAsDouble(*int_val)) {
245 return base::ErrStatus("Column %s contains %" PRId64
246 " which cannot be represented as a double",
247 col_names_[idx].c_str(), *int_val);
248 }
249 storage.Append(static_cast<double>(*int_val));
250 }
251 *col = std::move(storage);
252 }
253 auto* doubles = std::get_if<NullDoubleStorage>(col);
254 if (!doubles) {
255 return base::ErrStatus("Column %s does not have consistent types",
256 col_names_[idx].c_str());
257 }
258 doubles->Append(res);
259 return base::OkStatus();
260 }
261
AddText(uint32_t idx,const char * ptr)262 base::Status RuntimeTable::Builder::AddText(uint32_t idx, const char* ptr) {
263 auto* col = storage_[idx].get();
264 if (auto* leading_nulls_ptr = std::get_if<uint32_t>(col)) {
265 *col = Fill<StringStorage>(*leading_nulls_ptr, StringPool::Id::Null());
266 }
267 auto* strings = std::get_if<StringStorage>(col);
268 if (!strings) {
269 return base::ErrStatus("Column %s does not have consistent types",
270 col_names_[idx].c_str());
271 }
272 strings->Append(string_pool_->InternString(ptr));
273 return base::OkStatus();
274 }
275
AddIntegers(uint32_t idx,int64_t val,uint32_t count)276 base::Status RuntimeTable::Builder::AddIntegers(uint32_t idx,
277 int64_t val,
278 uint32_t count) {
279 auto* col = storage_[idx].get();
280 if (auto* leading_nulls_ptr = std::get_if<uint32_t>(col)) {
281 *col = Fill<NullIntStorage>(*leading_nulls_ptr, std::nullopt);
282 }
283 if (auto* doubles = std::get_if<NullDoubleStorage>(col)) {
284 if (!IsPerfectlyRepresentableAsDouble(val)) {
285 return base::ErrStatus("Column %s contains %" PRId64
286 " which cannot be represented as a double",
287 col_names_[idx].c_str(), val);
288 }
289 doubles->AppendMultiple(static_cast<double>(val), count);
290 return base::OkStatus();
291 }
292 if (auto* null_ints = std::get_if<NullIntStorage>(col)) {
293 null_ints->AppendMultiple(val, count);
294 return base::OkStatus();
295 }
296 auto* ints = std::get_if<IntStorage>(col);
297 if (!ints) {
298 return base::ErrStatus("Column %s does not have consistent types",
299 col_names_[idx].c_str());
300 }
301 ints->AppendMultiple(val, count);
302 return base::OkStatus();
303 }
304
AddFloats(uint32_t idx,double res,uint32_t count)305 base::Status RuntimeTable::Builder::AddFloats(uint32_t idx,
306 double res,
307 uint32_t count) {
308 auto* col = storage_[idx].get();
309 if (auto* leading_nulls_ptr = std::get_if<uint32_t>(col)) {
310 *col = Fill<NullDoubleStorage>(*leading_nulls_ptr, std::nullopt);
311 }
312 if (auto* ints = std::get_if<NullIntStorage>(col)) {
313 NullDoubleStorage storage;
314 for (uint32_t i = 0; i < ints->size(); ++i) {
315 std::optional<int64_t> int_val = ints->Get(i);
316 if (!int_val) {
317 storage.AppendMultipleNulls(count);
318 continue;
319 }
320 if (int_val && !IsPerfectlyRepresentableAsDouble(*int_val)) {
321 return base::ErrStatus("Column %s contains %" PRId64
322 " which cannot be represented as a double",
323 col_names_[idx].c_str(), *int_val);
324 }
325 storage.AppendMultiple(static_cast<double>(*int_val), count);
326 }
327 *col = std::move(storage);
328 }
329 auto* doubles = std::get_if<NullDoubleStorage>(col);
330 if (!doubles) {
331 return base::ErrStatus("Column %s does not have consistent types",
332 col_names_[idx].c_str());
333 }
334 doubles->AppendMultiple(res, count);
335 return base::OkStatus();
336 }
337
AddTexts(uint32_t idx,const char * ptr,uint32_t count)338 base::Status RuntimeTable::Builder::AddTexts(uint32_t idx,
339 const char* ptr,
340 uint32_t count) {
341 auto* col = storage_[idx].get();
342 if (auto* leading_nulls_ptr = std::get_if<uint32_t>(col)) {
343 *col = Fill<StringStorage>(*leading_nulls_ptr, StringPool::Id::Null());
344 }
345 auto* strings = std::get_if<StringStorage>(col);
346 if (!strings) {
347 return base::ErrStatus("Column %s does not have consistent types",
348 col_names_[idx].c_str());
349 }
350 strings->AppendMultiple(string_pool_->InternString(ptr), count);
351 return base::OkStatus();
352 }
353
AddNulls(uint32_t idx,uint32_t count)354 base::Status RuntimeTable::Builder::AddNulls(uint32_t idx, uint32_t count) {
355 auto* col = storage_[idx].get();
356 if (auto* leading_nulls = std::get_if<uint32_t>(col)) {
357 (*leading_nulls)++;
358 } else if (auto* ints = std::get_if<NullIntStorage>(col)) {
359 ints->AppendMultipleNulls(count);
360 } else if (auto* strings = std::get_if<StringStorage>(col)) {
361 strings->AppendMultiple(StringPool::Id::Null(), count);
362 } else if (auto* doubles = std::get_if<NullDoubleStorage>(col)) {
363 doubles->AppendMultipleNulls(count);
364 } else {
365 PERFETTO_FATAL("Unexpected column type");
366 }
367 return base::OkStatus();
368 }
369
AddNonNullIntegersUnchecked(uint32_t idx,const std::vector<int64_t> & res)370 void RuntimeTable::Builder::AddNonNullIntegersUnchecked(
371 uint32_t idx,
372 const std::vector<int64_t>& res) {
373 std::get<IntStorage>(*storage_[idx]).Append(res);
374 }
375
AddNullIntegersUnchecked(uint32_t idx,const std::vector<int64_t> & res)376 void RuntimeTable::Builder::AddNullIntegersUnchecked(
377 uint32_t idx,
378 const std::vector<int64_t>& res) {
379 std::get<NullIntStorage>(*storage_[idx]).Append(res);
380 }
381
AddNonNullDoublesUnchecked(uint32_t idx,const std::vector<double> & vals)382 void RuntimeTable::Builder::AddNonNullDoublesUnchecked(
383 uint32_t idx,
384 const std::vector<double>& vals) {
385 std::get<DoubleStorage>(*storage_[idx]).Append(vals);
386 }
387
AddNullDoublesUnchecked(uint32_t idx,const std::vector<double> & vals)388 void RuntimeTable::Builder::AddNullDoublesUnchecked(
389 uint32_t idx,
390 const std::vector<double>& vals) {
391 std::get<NullDoubleStorage>(*storage_[idx]).Append(vals);
392 }
393
Build(uint32_t rows)394 base::StatusOr<std::unique_ptr<RuntimeTable>> RuntimeTable::Builder::Build(
395 uint32_t rows) && {
396 std::vector<RefPtr<column::StorageLayer>> storage_layers(col_names_.size() +
397 1);
398 std::vector<RefPtr<column::OverlayLayer>> null_layers(col_names_.size() + 1);
399
400 std::vector<ColumnLegacy> legacy_columns;
401 std::vector<ColumnStorageOverlay> legacy_overlays;
402
403 // |overlay_layers| might use the RowMaps used by |legacy_overlays| and access
404 // them by fetching the pointer to the RowMap inside overlay. We need to make
405 // sure that those pointers will not change, hence we need to make sure that
406 // the vector will not resize. In the current implementation there is at most
407 // one overlay per column.
408 legacy_overlays.reserve(col_names_.size() + 1);
409 legacy_overlays.emplace_back(rows);
410 std::vector<RefPtr<column::OverlayLayer>> overlay_layers(1);
411
412 for (uint32_t i = 0; i < col_names_.size(); ++i) {
413 auto* col = storage_[i].get();
414 std::unique_ptr<column::DataLayerChain> chain;
415 if (auto* leading_nulls = std::get_if<uint32_t>(col)) {
416 PERFETTO_CHECK(*leading_nulls == rows);
417 *col = Fill<NullIntStorage>(*leading_nulls, std::nullopt);
418 }
419
420 if (auto* null_ints = std::get_if<NullIntStorage>(col)) {
421 // The `ints` column
422 PERFETTO_CHECK(null_ints->size() == rows);
423
424 if (null_ints->non_null_size() == null_ints->size()) {
425 // The column doesn't have any nulls so we construct a new nonnullable
426 // column.
427 *col = IntStorage::CreateFromAssertNonNull(std::move(*null_ints));
428 CreateNonNullableIntsColumn(
429 i, col_names_[i].c_str(), std::get_if<IntStorage>(col),
430 storage_layers, overlay_layers, legacy_columns, legacy_overlays);
431 } else {
432 // Nullable ints column.
433 legacy_columns.emplace_back(col_names_[i].c_str(), null_ints,
434 ColumnLegacy::Flag::kNoFlag, i, 0);
435 storage_layers[i].reset(new column::NumericStorage<int64_t>(
436 &null_ints->non_null_vector(), ColumnType::kInt64, false));
437 null_layers[i].reset(
438 new column::NullOverlay(&null_ints->non_null_bit_vector()));
439 }
440
441 } else if (auto* ints = std::get_if<IntStorage>(col)) {
442 // The `ints` column for tables where column types was provided before.
443 PERFETTO_CHECK(ints->size() == rows);
444 CreateNonNullableIntsColumn(
445 i, col_names_[i].c_str(), std::get_if<IntStorage>(col),
446 storage_layers, overlay_layers, legacy_columns, legacy_overlays);
447
448 } else if (auto* doubles = std::get_if<DoubleStorage>(col)) {
449 // The `doubles` column for tables where column types was provided before.
450 PERFETTO_CHECK(doubles->size() == rows);
451 bool is_sorted =
452 std::is_sorted(doubles->vector().begin(), doubles->vector().end());
453 uint32_t flags =
454 is_sorted ? ColumnLegacy::Flag::kNonNull | ColumnLegacy::Flag::kSorted
455 : ColumnLegacy::Flag::kNonNull;
456 legacy_columns.emplace_back(col_names_[i].c_str(), doubles, flags, i, 0);
457 storage_layers[i].reset(new column::NumericStorage<double>(
458 &doubles->vector(), ColumnType::kDouble, is_sorted));
459
460 } else if (auto* null_doubles = std::get_if<NullDoubleStorage>(col)) {
461 // The doubles column.
462 PERFETTO_CHECK(null_doubles->size() == rows);
463 if (null_doubles->non_null_size() == null_doubles->size()) {
464 // The column is not nullable.
465 *col = DoubleStorage::CreateFromAssertNonNull(std::move(*null_doubles));
466
467 auto* non_null_doubles = std::get_if<DoubleStorage>(col);
468 bool is_sorted = std::is_sorted(non_null_doubles->vector().begin(),
469 non_null_doubles->vector().end());
470 uint32_t flags = is_sorted ? ColumnLegacy::Flag::kNonNull |
471 ColumnLegacy::Flag::kSorted
472 : ColumnLegacy::Flag::kNonNull;
473 legacy_columns.emplace_back(col_names_[i].c_str(), non_null_doubles,
474 flags, i, 0);
475 storage_layers[i].reset(new column::NumericStorage<double>(
476 &non_null_doubles->vector(), ColumnType::kDouble, is_sorted));
477 } else {
478 // The column is nullable.
479 legacy_columns.emplace_back(col_names_[i].c_str(), null_doubles,
480 ColumnLegacy::Flag::kNoFlag, i, 0);
481 storage_layers[i].reset(new column::NumericStorage<double>(
482 &null_doubles->non_null_vector(), ColumnType::kDouble, false));
483 null_layers[i].reset(
484 new column::NullOverlay(&null_doubles->non_null_bit_vector()));
485 }
486
487 } else if (auto* strings = std::get_if<StringStorage>(col)) {
488 // The `strings` column.
489 PERFETTO_CHECK(strings->size() == rows);
490 legacy_columns.emplace_back(col_names_[i].c_str(), strings,
491 ColumnLegacy::Flag::kNonNull, i, 0);
492 storage_layers[i].reset(
493 new column::StringStorage(string_pool_, &strings->vector()));
494 } else {
495 PERFETTO_FATAL("Unexpected column type");
496 }
497 }
498
499 legacy_columns.push_back(ColumnLegacy::IdColumn(
500 static_cast<uint32_t>(legacy_columns.size()), 0, "_auto_id",
501 ColumnLegacy::kIdFlags | ColumnLegacy::Flag::kHidden));
502 storage_layers.back().reset(new column::IdStorage());
503
504 auto table = std::make_unique<RuntimeTable>(
505 string_pool_, rows, std::move(legacy_columns), std::move(legacy_overlays),
506 std::move(storage_layers), std::move(null_layers),
507 std::move(overlay_layers));
508 table->storage_ = std::move(storage_);
509 table->col_names_ = std::move(col_names_);
510
511 table->schema_.columns.reserve(table->columns().size());
512 for (size_t i = 0; i < table->columns().size(); ++i) {
513 const auto& col = table->columns()[i];
514 SqlValue::Type column_type =
515 col.col_type() != ColumnType::kId &&
516 col.storage_base().non_null_size() == 0
517 ? SqlValue::kNull
518 : ColumnLegacy::ToSqlValueType(col.col_type());
519 table->schema_.columns.emplace_back(
520 Schema::Column{col.name(), column_type, col.IsId(), col.IsSorted(),
521 col.IsHidden(), col.IsSetId()});
522 }
523 return {std::move(table)};
524 }
525
526 } // namespace perfetto::trace_processor
527