Merge "[ui] Remove superfluous args from getSliceRect()" into main
diff --git a/Android.bp b/Android.bp
index 328fc3d..ef5737b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10974,6 +10974,7 @@
name: "perfetto_src_trace_processor_db_storage_storage",
srcs: [
"src/trace_processor/db/storage/arrangement_storage.cc",
+ "src/trace_processor/db/storage/dense_null_storage.cc",
"src/trace_processor/db/storage/dummy_storage.cc",
"src/trace_processor/db/storage/id_storage.cc",
"src/trace_processor/db/storage/null_storage.cc",
@@ -10990,6 +10991,7 @@
name: "perfetto_src_trace_processor_db_storage_unittests",
srcs: [
"src/trace_processor/db/storage/arrangement_storage_unittest.cc",
+ "src/trace_processor/db/storage/dense_null_storage_unittest.cc",
"src/trace_processor/db/storage/id_storage_unittest.cc",
"src/trace_processor/db/storage/null_storage_unittest.cc",
"src/trace_processor/db/storage/numeric_storage_unittest.cc",
@@ -15481,3 +15483,29 @@
"LICENSE",
],
}
+
+// TODO(b/315118713): use list of proto file sources instead of merged proto
+gensrcs {
+ name: "perfetto_trace_javastream_protos",
+ srcs: [
+ "protos/perfetto/trace/perfetto_trace.proto",
+ ],
+ tools: [
+ "aprotoc",
+ "protoc-gen-javastream",
+ "soong_zip",
+ ],
+ cmd: "mkdir -p $(genDir)/$(in) " +
+ "&& $(location aprotoc) " +
+ "--plugin=$(location protoc-gen-javastream) " +
+ "--javastream_out=$(genDir)/$(in) " +
+ "-Iexternal/protobuf/src " +
+ "-Iexternal/perfetto " +
+ "-I . $(in) " +
+ "&& $(location soong_zip) " +
+ "-jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+ data: [
+ ":libprotobuf-internal-protos",
+ ],
+ output_extension: "srcjar",
+}
diff --git a/Android.bp.extras b/Android.bp.extras
index 3292ad9..9c7b0b0 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -171,3 +171,29 @@
"LICENSE",
],
}
+
+// TODO(b/315118713): use list of proto file sources instead of merged proto
+gensrcs {
+ name: "perfetto_trace_javastream_protos",
+ srcs: [
+ "protos/perfetto/trace/perfetto_trace.proto",
+ ],
+ tools: [
+ "aprotoc",
+ "protoc-gen-javastream",
+ "soong_zip",
+ ],
+ cmd: "mkdir -p $(genDir)/$(in) " +
+ "&& $(location aprotoc) " +
+ "--plugin=$(location protoc-gen-javastream) " +
+ "--javastream_out=$(genDir)/$(in) " +
+ "-Iexternal/protobuf/src " +
+ "-Iexternal/perfetto " +
+ "-I . $(in) " +
+ "&& $(location soong_zip) " +
+ "-jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+ data: [
+ ":libprotobuf-internal-protos",
+ ],
+ output_extension: "srcjar",
+}
diff --git a/BUILD b/BUILD
index cddab6b..5c8fbe6 100644
--- a/BUILD
+++ b/BUILD
@@ -1302,6 +1302,8 @@
srcs = [
"src/trace_processor/db/storage/arrangement_storage.cc",
"src/trace_processor/db/storage/arrangement_storage.h",
+ "src/trace_processor/db/storage/dense_null_storage.cc",
+ "src/trace_processor/db/storage/dense_null_storage.h",
"src/trace_processor/db/storage/dummy_storage.cc",
"src/trace_processor/db/storage/dummy_storage.h",
"src/trace_processor/db/storage/id_storage.cc",
diff --git a/protos/perfetto/trace_processor/serialization.proto b/protos/perfetto/trace_processor/serialization.proto
index e79b7c9..f559bd9 100644
--- a/protos/perfetto/trace_processor/serialization.proto
+++ b/protos/perfetto/trace_processor/serialization.proto
@@ -92,6 +92,12 @@
optional Storage storage = 2;
}
+ // A schema for serialization of |storage::DenseNullStorage|.
+ message DenseNullStorage {
+ optional BitVector bit_vector = 1;
+ optional Storage storage = 2;
+ }
+
oneof data {
DummyStorage dummy_storage = 1;
IdStorage id_storage = 2;
@@ -101,6 +107,7 @@
NullStorage null_storage = 6;
ArrangementStorage arrangement_storage = 7;
SelectorStorage selector_storage = 8;
+ DenseNullStorage dense_null_storage = 9;
}
}
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index 869c9da..6b02d28 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -84,6 +84,7 @@
"../../../gn:default_deps",
"../../../include/perfetto/base",
"../../../include/perfetto/ext/base",
+ "../../../include/perfetto/trace_processor:basic_types",
"../../base:test_support",
"../tables:tables_python",
]
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index c77cb38..046aee4 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -28,6 +28,7 @@
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/db/query_executor.h"
#include "src/trace_processor/db/storage/arrangement_storage.h"
+#include "src/trace_processor/db/storage/dense_null_storage.h"
#include "src/trace_processor/db/storage/dummy_storage.h"
#include "src/trace_processor/db/storage/id_storage.h"
#include "src/trace_processor/db/storage/null_storage.h"
@@ -170,9 +171,6 @@
(c.op != FilterOp::kIsNull && c.op != FilterOp::kIsNotNull &&
(int_with_double || double_with_int));
- // Dense columns.
- use_legacy = use_legacy || col.IsDense();
-
// Extrinsically sorted columns.
use_legacy = use_legacy ||
(col.IsSorted() && col.overlay().row_map().IsIndexVector());
@@ -255,8 +253,13 @@
// String columns are inherently nullable: null values are signified
// with Id::Null().
PERFETTO_CHECK(col.col_type() != ColumnType::kString);
- storage = std::make_unique<storage::NullStorage>(std::move(storage),
- col.storage_base().bv());
+ if (col.IsDense()) {
+ storage = std::make_unique<storage::DenseNullStorage>(
+ std::move(storage), col.storage_base().bv());
+ } else {
+ storage = std::make_unique<storage::NullStorage>(
+ std::move(storage), col.storage_base().bv());
+ }
}
if (col.overlay().row_map().IsIndexVector()) {
storage = std::make_unique<storage::ArrangementStorage>(
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index 5bbc99f..eb23903 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -20,9 +20,11 @@
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/trace_processor/basic_types.h"
#include "src/base/test/utils.h"
#include "src/trace_processor/db/table.h"
#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
#include "src/trace_processor/tables/slice_tables_py.h"
#include "src/trace_processor/tables/track_tables_py.h"
@@ -35,6 +37,7 @@
using ExpectedFrameTimelineSliceTable = tables::ExpectedFrameTimelineSliceTable;
using RawTable = tables::RawTable;
using FtraceEventTable = tables::FtraceEventTable;
+using HeapGraphObjectTable = tables::HeapGraphObjectTable;
// `SELECT * FROM SLICE` on android_monitor_contention_trace.at
static char kSliceTable[] = "test/data/slice_table_for_benchmarks.csv";
@@ -50,6 +53,10 @@
static char kFtraceEventTable[] =
"test/data/ftrace_event_cpu_for_benchmarks.csv";
+// `SELECT id, upid, reference_set_id FROM heap_graph_object` on
+static char kHeapGraphObjectTable[] =
+ "test/data/heap_pgraph_object_for_benchmarks_query.csv";
+
enum DB { V1, V2 };
std::vector<std::string> SplitCSVLine(const std::string& line) {
@@ -193,6 +200,24 @@
tables::FtraceEventTable table_{&pool_, &raw_};
};
+struct HeapGraphObjectTableForBenchmark {
+ explicit HeapGraphObjectTableForBenchmark(benchmark::State& state) {
+ std::vector<std::string> table_rows_as_string =
+ ReadCSV(state, kHeapGraphObjectTable);
+
+ for (size_t i = 1; i < table_rows_as_string.size(); ++i) {
+ std::vector<std::string> row_vec = SplitCSVLine(table_rows_as_string[i]);
+
+ HeapGraphObjectTable::Row row;
+ row.upid = *base::StringToUInt32(row_vec[1]);
+ row.reference_set_id = base::StringToUInt32(row_vec[2]);
+ table_.Insert(row);
+ }
+ }
+ StringPool pool_;
+ HeapGraphObjectTable table_{&pool_};
+};
+
void BenchmarkSliceTable(benchmark::State& state,
SliceTableForBenchmark& table,
std::initializer_list<Constraint> c) {
@@ -346,6 +371,38 @@
BENCHMARK(BM_QEFilterWithArrangement)->ArgsProduct({{DB::V1, DB::V2}});
+static void BM_QEDenseNullFilter(benchmark::State& state) {
+ Table::kUseFilterV2 = state.range(0) == 1;
+
+ HeapGraphObjectTableForBenchmark table(state);
+ Constraint c{table.table_.reference_set_id().index_in_table(), FilterOp::kGt,
+ SqlValue::Long(1000)};
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(table.table_.FilterToRowMap({c}));
+ }
+ state.counters["s/row"] =
+ benchmark::Counter(static_cast<double>(table.table_.row_count()),
+ benchmark::Counter::kIsIterationInvariantRate |
+ benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_QEDenseNullFilter)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QEDenseNullFilterIsNull(benchmark::State& state) {
+ Table::kUseFilterV2 = state.range(0) == 1;
+
+ HeapGraphObjectTableForBenchmark table(state);
+ Constraint c{table.table_.reference_set_id().index_in_table(),
+ FilterOp::kIsNull, SqlValue()};
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(table.table_.FilterToRowMap({c}));
+ }
+ state.counters["s/row"] =
+ benchmark::Counter(static_cast<double>(table.table_.row_count()),
+ benchmark::Counter::kIsIterationInvariantRate |
+ benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_QEDenseNullFilterIsNull)->ArgsProduct({{DB::V1, DB::V2}});
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
index 55b7eb8..bd80ec7 100644
--- a/src/trace_processor/db/storage/BUILD.gn
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -18,6 +18,8 @@
sources = [
"arrangement_storage.cc",
"arrangement_storage.h",
+ "dense_null_storage.cc",
+ "dense_null_storage.h",
"dummy_storage.cc",
"dummy_storage.h",
"id_storage.cc",
@@ -67,6 +69,7 @@
testonly = true
sources = [
"arrangement_storage_unittest.cc",
+ "dense_null_storage_unittest.cc",
"id_storage_unittest.cc",
"null_storage_unittest.cc",
"numeric_storage_unittest.cc",
diff --git a/src/trace_processor/db/storage/dense_null_storage.cc b/src/trace_processor/db/storage/dense_null_storage.cc
new file mode 100644
index 0000000..9f764a4
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.cc
@@ -0,0 +1,134 @@
+/*
+ * 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 "src/trace_processor/db/storage/dense_null_storage.h"
+
+#include <cstdint>
+#include <variant>
+
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+DenseNullStorage::DenseNullStorage(std::unique_ptr<Storage> inner,
+ const BitVector* non_null)
+ : inner_(std::move(inner)), non_null_(non_null) {}
+
+Storage::SearchValidationResult DenseNullStorage::ValidateSearchConstraints(
+ SqlValue sql_val,
+ FilterOp op) const {
+ return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
+RangeOrBitVector DenseNullStorage::Search(FilterOp op,
+ SqlValue sql_val,
+ RowMap::Range in) const {
+ PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::Search");
+
+ RangeOrBitVector inner_res = inner_->Search(op, sql_val, in);
+ BitVector res;
+ if (inner_res.IsRange()) {
+ // If the inner storage returns a range, mask out the appropriate values in
+ // |non_null_| which matches the range. Then, resize to |in.end| as this
+ // is mandated by the API contract of |Storage::Search|.
+ RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+ PERFETTO_DCHECK(inner_range.end <= in.end);
+ PERFETTO_DCHECK(inner_range.start >= in.start);
+ res = non_null_->IntersectRange(inner_range.start, inner_range.end);
+ res.Resize(in.end, false);
+ } else {
+ res = std::move(inner_res).TakeIfBitVector();
+ }
+ PERFETTO_DCHECK(res.size() == in.end);
+
+ if (op == FilterOp::kIsNull) {
+ // For IS NULL, we need to add any rows in |non_null_| which are zeros: we
+ // do this by taking the appropriate number of rows, inverting it and then
+ // bitwise or-ing the result with it.
+ BitVector non_null_copy = non_null_->Copy();
+ non_null_copy.Resize(in.end);
+ non_null_copy.Not();
+ res.Or(non_null_copy);
+ } else {
+ // For anything else, we just need to ensure that any rows which are null
+ // are removed as they would not match.
+ res.And(*non_null_);
+ }
+ return RangeOrBitVector(std::move(res));
+}
+
+RangeOrBitVector DenseNullStorage::IndexSearch(FilterOp op,
+ SqlValue sql_val,
+ uint32_t* indices,
+ uint32_t indices_size,
+ bool sorted) const {
+ PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::IndexSearch");
+
+ RangeOrBitVector inner_res =
+ inner_->IndexSearch(op, sql_val, indices, indices_size, sorted);
+ if (inner_res.IsRange()) {
+ RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+ BitVector::Builder builder(indices_size, inner_range.start);
+ for (uint32_t i = inner_range.start; i < inner_range.end; ++i) {
+ builder.Append(non_null_->IsSet(indices[i]));
+ }
+ return RangeOrBitVector(std::move(builder).Build());
+ }
+
+ BitVector::Builder builder(indices_size);
+ for (uint32_t i = 0; i < indices_size; ++i) {
+ builder.Append(non_null_->IsSet(indices[i]));
+ }
+ BitVector non_null = std::move(builder).Build();
+ PERFETTO_DCHECK(non_null.size() == indices_size);
+
+ BitVector res = std::move(inner_res).TakeIfBitVector();
+ PERFETTO_DCHECK(res.size() == indices_size);
+
+ if (op == FilterOp::kIsNull) {
+ BitVector null = std::move(non_null);
+ null.Not();
+ res.Or(null);
+ } else {
+ res.And(non_null);
+ }
+ return RangeOrBitVector(std::move(res));
+}
+
+void DenseNullStorage::StableSort(uint32_t*, uint32_t) const {
+ // TODO(b/307482437): Implement.
+ PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Sort(uint32_t*, uint32_t) const {
+ // TODO(b/307482437): Implement.
+ PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Serialize(StorageProto* storage) const {
+ auto* null_storage = storage->set_dense_null_storage();
+ non_null_->Serialize(null_storage->set_bit_vector());
+ inner_->Serialize(null_storage->set_storage());
+}
+
+} // namespace storage
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/db/storage/dense_null_storage.h b/src/trace_processor/db/storage/dense_null_storage.h
new file mode 100644
index 0000000..ec7b6e9
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.h
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
+
+#include <memory>
+#include <variant>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage which introduces the layer of nullability but without changing the
+// "spacing" of the underlying storage i.e. this storage simply "masks" out
+// rows in the underlying storage with nulls.
+class DenseNullStorage : public Storage {
+ public:
+ DenseNullStorage(std::unique_ptr<Storage> inner, const BitVector* non_null);
+
+ SearchValidationResult ValidateSearchConstraints(SqlValue,
+ FilterOp) const override;
+
+ RangeOrBitVector Search(FilterOp op,
+ SqlValue value,
+ RowMap::Range range) const override;
+
+ RangeOrBitVector IndexSearch(FilterOp op,
+ SqlValue value,
+ uint32_t* indices,
+ uint32_t indices_count,
+ bool sorted) const override;
+
+ void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+ void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+ void Serialize(StorageProto*) const override;
+
+ uint32_t size() const override { return non_null_->size(); }
+
+ private:
+ std::unique_ptr<Storage> inner_;
+ const BitVector* non_null_ = nullptr;
+};
+
+} // namespace storage
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
diff --git a/src/trace_processor/db/storage/dense_null_storage_unittest.cc b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
new file mode 100644
index 0000000..d8ec93c
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
@@ -0,0 +1,131 @@
+/*
+ * 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 "src/trace_processor/db/storage/dense_null_storage.h"
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/fake_storage.h"
+#include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+ RowMap rm;
+ if (r_or_bv.IsBitVector()) {
+ rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+ } else {
+ Range range = std::move(r_or_bv).TakeIfRange();
+ rm = RowMap(range.start, range.end);
+ }
+ return rm.GetAllIndices();
+}
+
+TEST(DenseNullStorage, NoFilteringSearch) {
+ std::vector<uint32_t> data{0, 1, 0, 1, 0};
+ auto numeric =
+ std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+ BitVector bv{0, 1, 0, 1, 0};
+ DenseNullStorage storage(std::move(numeric), &bv);
+
+ auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+ ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3));
+}
+
+TEST(DenseNullStorage, RestrictInputSearch) {
+ std::vector<uint32_t> data{0, 1, 0, 1, 0};
+ auto numeric =
+ std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+ BitVector bv{0, 1, 0, 1, 0};
+ DenseNullStorage storage(std::move(numeric), &bv);
+
+ auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(1, 3));
+ ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, RangeFilterSearch) {
+ auto fake = FakeStorage::SearchSubset(5, Range(1, 3));
+
+ BitVector bv{0, 1, 0, 1, 0};
+ DenseNullStorage storage(std::move(fake), &bv);
+
+ auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+ ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, BitvectorFilterSearch) {
+ auto fake = FakeStorage::SearchSubset(5, BitVector({0, 1, 1, 0, 0}));
+
+ BitVector bv{0, 1, 0, 1, 0};
+ DenseNullStorage storage(std::move(fake), &bv);
+
+ auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+ ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, IsNullSearch) {
+ auto fake = FakeStorage::SearchSubset(5, BitVector({1, 1, 0, 0, 1}));
+
+ BitVector bv{1, 0, 0, 1, 1};
+ DenseNullStorage storage(std::move(fake), &bv);
+
+ auto res = storage.Search(FilterOp::kIsNull, SqlValue(), Range(0, 5));
+ ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 4));
+}
+
+TEST(DenseNullStorage, IndexSearch) {
+ std::vector<uint32_t> data{1, 0, 0, 1, 1, 1};
+ auto numeric =
+ std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+ BitVector bv{1, 0, 0, 1, 1, 1};
+ DenseNullStorage storage(std::move(numeric), &bv);
+
+ std::vector<uint32_t> index({5, 2, 3, 4, 1});
+ auto res = storage.IndexSearch(FilterOp::kGe, SqlValue::Long(0), index.data(),
+ static_cast<uint32_t>(index.size()), false);
+ ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 2, 3));
+}
+
+TEST(DenseNullStorage, IsNullIndexSearch) {
+ auto fake = FakeStorage::SearchSubset(6, BitVector({0, 0, 0, 1, 1, 1}));
+
+ BitVector bv{0, 1, 0, 1, 1, 1};
+ DenseNullStorage storage(std::move(fake), &bv);
+
+ std::vector<uint32_t> index({5, 2, 3, 4, 1});
+ auto res = storage.IndexSearch(FilterOp::kIsNull, SqlValue(), index.data(),
+ static_cast<uint32_t>(index.size()), false);
+ ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 3));
+}
+
+} // namespace
+} // namespace storage
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/db/storage/id_storage_unittest.cc b/src/trace_processor/db/storage/id_storage_unittest.cc
index 2262c43..0db3159 100644
--- a/src/trace_processor/db/storage/id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/id_storage_unittest.cc
@@ -108,7 +108,7 @@
ASSERT_EQ(search_result, empty_range);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqSimple) {
+TEST(IdStorageUnittest, SearchEqSimple) {
IdStorage storage(100);
Range range = storage.Search(FilterOp::kEq, SqlValue::Long(15), Range(10, 20))
.TakeIfRange();
@@ -117,21 +117,21 @@
ASSERT_EQ(range.end, 16u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqOnRangeBoundary) {
+TEST(IdStorageUnittest, SearchEqOnRangeBoundary) {
IdStorage storage(100);
Range range = storage.Search(FilterOp::kEq, SqlValue::Long(20), Range(10, 20))
.TakeIfRange();
ASSERT_EQ(range.size(), 0u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqOutsideRange) {
+TEST(IdStorageUnittest, SearchEqOutsideRange) {
IdStorage storage(100);
Range range = storage.Search(FilterOp::kEq, SqlValue::Long(25), Range(10, 20))
.TakeIfRange();
ASSERT_EQ(range.size(), 0u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqTooBig) {
+TEST(IdStorageUnittest, SearchEqTooBig) {
IdStorage storage(100);
Range range =
storage.Search(FilterOp::kEq, SqlValue::Long(125), Range(10, 20))
@@ -139,7 +139,7 @@
ASSERT_EQ(range.size(), 0u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicLe) {
+TEST(IdStorageUnittest, SearchLe) {
IdStorage storage(100);
Range range = storage.Search(FilterOp::kLe, SqlValue::Long(50), Range(30, 70))
.TakeIfRange();
@@ -147,7 +147,7 @@
ASSERT_EQ(range.end, 51u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicLt) {
+TEST(IdStorageUnittest, SearchLt) {
IdStorage storage(100);
Range range = storage.Search(FilterOp::kLt, SqlValue::Long(50), Range(30, 70))
.TakeIfRange();
@@ -155,7 +155,7 @@
ASSERT_EQ(range.end, 50u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicGe) {
+TEST(IdStorageUnittest, SearchGe) {
IdStorage storage(100);
Range range = storage.Search(FilterOp::kGe, SqlValue::Long(40), Range(30, 70))
.TakeIfRange();
@@ -163,7 +163,7 @@
ASSERT_EQ(range.end, 70u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicGt) {
+TEST(IdStorageUnittest, SearchGt) {
IdStorage storage(100);
Range range = storage.Search(FilterOp::kGt, SqlValue::Long(40), Range(30, 70))
.TakeIfRange();
@@ -171,7 +171,7 @@
ASSERT_EQ(range.end, 70u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicNe) {
+TEST(IdStorageUnittest, SearchNe) {
IdStorage storage(100);
BitVector bv =
storage.Search(FilterOp::kNe, SqlValue::Long(40), Range(30, 70))
@@ -179,13 +179,112 @@
ASSERT_EQ(bv.CountSetBits(), 39u);
}
-TEST(IdStorageUnittest, BinarySearchIntrinsicNeInvalidNum) {
+TEST(IdStorageUnittest, SearchNeInvalidNum) {
IdStorage storage(100);
Range r = storage.Search(FilterOp::kNe, SqlValue::Long(-1), Range(30, 70))
.TakeIfRange();
ASSERT_EQ(r.size(), 40u);
}
+TEST(IdStorageUnittest, IndexSearchEqSimple) {
+ IdStorage storage(12);
+ std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+ BitVector bv =
+ storage
+ .IndexSearch(FilterOp::kEq, SqlValue::Long(3), indices.data(),
+ static_cast<uint32_t>(indices.size()), false)
+ .TakeIfBitVector();
+
+ ASSERT_EQ(bv.CountSetBits(), 1u);
+ ASSERT_TRUE(bv.IsSet(1));
+}
+
+TEST(IdStorageUnittest, IndexSearchEqTooBig) {
+ IdStorage storage(12);
+ std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+ BitVector bv =
+ storage
+ .IndexSearch(FilterOp::kEq, SqlValue::Long(20), indices.data(),
+ static_cast<uint32_t>(indices.size()), false)
+ .TakeIfBitVector();
+
+ ASSERT_EQ(bv.CountSetBits(), 0u);
+}
+
+TEST(IdStorageUnittest, IndexSearchNe) {
+ IdStorage storage(12);
+ std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+ BitVector bv =
+ storage
+ .IndexSearch(FilterOp::kNe, SqlValue::Long(3), indices.data(),
+ static_cast<uint32_t>(indices.size()), false)
+ .TakeIfBitVector();
+
+ ASSERT_EQ(bv.CountSetBits(), 7u);
+ ASSERT_FALSE(bv.IsSet(1));
+}
+
+TEST(IdStorageUnittest, IndexSearchLe) {
+ IdStorage storage(12);
+ std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+ BitVector bv =
+ storage
+ .IndexSearch(FilterOp::kLe, SqlValue::Long(3), indices.data(),
+ static_cast<uint32_t>(indices.size()), false)
+ .TakeIfBitVector();
+
+ ASSERT_EQ(bv.CountSetBits(), 3u);
+ ASSERT_TRUE(bv.IsSet(0));
+ ASSERT_TRUE(bv.IsSet(1));
+ ASSERT_TRUE(bv.IsSet(6));
+}
+
+TEST(IdStorageUnittest, IndexSearchLt) {
+ IdStorage storage(12);
+ std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+ BitVector bv =
+ storage
+ .IndexSearch(FilterOp::kLt, SqlValue::Long(3), indices.data(),
+ static_cast<uint32_t>(indices.size()), false)
+ .TakeIfBitVector();
+
+ ASSERT_EQ(bv.CountSetBits(), 2u);
+}
+
+TEST(IdStorageUnittest, IndexSearchGe) {
+ IdStorage storage(12);
+ std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+ BitVector bv =
+ storage
+ .IndexSearch(FilterOp::kGe, SqlValue::Long(6), indices.data(),
+ static_cast<uint32_t>(indices.size()), false)
+ .TakeIfBitVector();
+
+ ASSERT_EQ(bv.CountSetBits(), 3u);
+}
+
+TEST(IdStorageUnittest, IndexSearchGt) {
+ IdStorage storage(12);
+ std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+ BitVector bv =
+ storage
+ .IndexSearch(FilterOp::kGt, SqlValue::Long(6), indices.data(),
+ static_cast<uint32_t>(indices.size()), false)
+ .TakeIfBitVector();
+
+ ASSERT_EQ(bv.CountSetBits(), 3u);
+ ASSERT_TRUE(bv.IsSet(3));
+ ASSERT_TRUE(bv.IsSet(4));
+ ASSERT_TRUE(bv.IsSet(5));
+}
+
TEST(IdStorageUnittest, Sort) {
std::vector<uint32_t> order{4, 3, 6, 1, 5};
IdStorage storage(10);
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index c699ab9..711415f 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -53,6 +53,47 @@
return context->clock_tracker->ToTraceTime(clock_id, ts);
}
+// Fast path for parsing the event id of an ftrace event.
+// Speculate on the fact that, if the timestamp was found, the common pid
+// will appear immediately after and the event id immediately after that.
+uint64_t TryFastParseFtraceEventId(const uint8_t* start, const uint8_t* end) {
+ constexpr auto kPidFieldNumber = protos::pbzero::FtraceEvent::kPidFieldNumber;
+ constexpr auto kPidFieldTag = MakeTagVarInt(kPidFieldNumber);
+
+ // If the next byte is not the common pid's tag, just skip the field.
+ constexpr uint32_t kMaxPidLength = 5;
+ if (PERFETTO_UNLIKELY(static_cast<uint32_t>(end - start) <= kMaxPidLength ||
+ start[0] != kPidFieldTag)) {
+ return 0;
+ }
+
+ // Skip the common pid.
+ uint64_t common_pid = 0;
+ const uint8_t* common_pid_end = ParseVarInt(start + 1, end, &common_pid);
+ if (PERFETTO_UNLIKELY(common_pid_end == start + 1)) {
+ return 0;
+ }
+
+ // Read the next varint: this should be the event id tag.
+ uint64_t event_tag = 0;
+ const uint8_t* event_id_end = ParseVarInt(common_pid_end, end, &event_tag);
+ if (event_id_end == common_pid_end) {
+ return 0;
+ }
+
+ constexpr uint8_t kFieldTypeNumBits = 3;
+ constexpr uint64_t kFieldTypeMask =
+ (1 << kFieldTypeNumBits) - 1; // 0000 0111;
+
+ // The event wire type should be length delimited.
+ auto wire_type = static_cast<protozero::proto_utils::ProtoWireType>(
+ event_tag & kFieldTypeMask);
+ if (wire_type != protozero::proto_utils::ProtoWireType::kLengthDelimited) {
+ return 0;
+ }
+ return event_tag >> kFieldTypeNumBits;
+}
+
} // namespace
PERFETTO_ALWAYS_INLINE
@@ -125,29 +166,53 @@
const uint8_t* data = event.data();
const size_t length = event.length();
- ProtoDecoder decoder(data, length);
- // Speculate on the fact that the timestamp is often the 1st field of the
- // event.
+ // Speculate on the following sequence of varints
+ // - timestamp tag
+ // - timestamp (64 bit)
+ // - common pid tag
+ // - common pid (32 bit)
+ // - event tag
uint64_t raw_timestamp = 0;
bool timestamp_found = false;
+ uint64_t event_id = 0;
if (PERFETTO_LIKELY(length > 10 && data[0] == kTimestampFieldTag)) {
// Fastpath.
- const uint8_t* next = ParseVarInt(data + 1, data + 11, &raw_timestamp);
- timestamp_found = next != data + 1;
- decoder.Reset(next);
- } else {
- // Slowpath.
+ const uint8_t* ts_end = ParseVarInt(data + 1, data + 11, &raw_timestamp);
+ timestamp_found = ts_end != data + 1;
+ if (PERFETTO_LIKELY(timestamp_found)) {
+ event_id = TryFastParseFtraceEventId(ts_end, data + length);
+ }
+ }
+
+ // Slowpath for finding the timestamp.
+ if (PERFETTO_UNLIKELY(!timestamp_found)) {
+ ProtoDecoder decoder(data, length);
if (auto ts_field = decoder.FindField(kTimestampFieldNumber)) {
timestamp_found = true;
raw_timestamp = ts_field.as_uint64();
}
+ if (PERFETTO_UNLIKELY(!timestamp_found)) {
+ context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
+ return;
+ }
}
- if (PERFETTO_UNLIKELY(!timestamp_found)) {
- PERFETTO_ELOG("Timestamp field not found in FtraceEvent");
- context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
- return;
+ // Slowpath for finding the event id.
+ if (PERFETTO_UNLIKELY(event_id == 0)) {
+ ProtoDecoder decoder(data, length);
+ for (auto f = decoder.ReadField(); f.valid(); f = decoder.ReadField()) {
+ // Find the first length-delimited tag as this corresponds to the ftrace
+ // event.
+ if (f.type() == protozero::proto_utils::ProtoWireType::kLengthDelimited) {
+ event_id = f.id();
+ break;
+ }
+ }
+ if (PERFETTO_UNLIKELY(!event_id)) {
+ context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
+ return;
+ }
}
// ClockTracker will increment some error stats if it failed to convert the
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.cc b/src/trace_processor/perfetto_sql/engine/created_function.cc
index 162dbc6..9c32a97 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/created_function.cc
@@ -587,6 +587,10 @@
SqlValue& out,
Destructors&) {
State* state = static_cast<State*>(ctx);
+
+ // Enter the function and ensure that we have a statement allocated.
+ RETURN_IF_ERROR(state->PushStackEntry());
+
if (argc != state->prototype().arguments.size()) {
return base::ErrStatus(
"%s: invalid number of args; expected %zu, received %zu",
@@ -608,9 +612,6 @@
}
}
- // Enter the function and ensure that we have a statement allocated.
- RETURN_IF_ERROR(state->PushStackEntry());
-
std::optional<Memoizer::MemoizedArgs> memoized_args =
Memoizer::AsMemoizedArgs(argc, argv);
diff --git a/test/data/heap_graph_object_for_benchmarks.pftrace.sha256 b/test/data/heap_graph_object_for_benchmarks.pftrace.sha256
new file mode 100644
index 0000000..dd7380f
--- /dev/null
+++ b/test/data/heap_graph_object_for_benchmarks.pftrace.sha256
@@ -0,0 +1 @@
+d0ee1affa7afdb325620a251f20ff16d5e19a5dae76508bb6db746d55dabd1cb
\ No newline at end of file
diff --git a/test/data/heap_pgraph_object_for_benchmarks_query.csv.sha256 b/test/data/heap_pgraph_object_for_benchmarks_query.csv.sha256
new file mode 100644
index 0000000..35a79ea
--- /dev/null
+++ b/test/data/heap_pgraph_object_for_benchmarks_query.csv.sha256
@@ -0,0 +1 @@
+62d757e7de34b466929f0444f2e097123b857c675fbe160f300f998a9309a3ae
\ No newline at end of file
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 6d00144..c1c1ea1 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -536,6 +536,7 @@
self.apex_available = set()
self.min_sdk_version = None
self.proto = dict()
+ self.output_extension: Optional[str] = None
# The genrule_XXX below are properties that must to be propagated back
# on the module(s) that depend on the genrule.
self.genrule_headers = set()
@@ -587,6 +588,7 @@
self._output_field(output, 'stubs')
self._output_field(output, 'proto')
self._output_field(output, 'main')
+ self._output_field(output, 'output_extension')
target_out = []
self._output_field(target_out, 'android')
diff --git a/tools/install-build-deps b/tools/install-build-deps
index c3822f6..fd17b81 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -63,7 +63,7 @@
'buildtools/test_data', # Moved to test/data by r.android.com/1539381 .
'buildtools/d8', # Removed by r.android.com/1424334 .
- # Build toools moved to third_party/ by r.android.com/2327602 .
+ # Build tools moved to third_party/ by r.android.com/2327602 .
'buildtools/mac/clang-format',
'buildtools/mac/gn',
'buildtools/mac/ninja',
@@ -95,6 +95,11 @@
'f706aaa0676e3e22f5fc9ca482295d7caee8535d1869f99efa2358177b64f5cd',
'linux', 'x64'),
Dependency(
+ 'third_party/gn/gn',
+ 'https://storage.googleapis.com/perfetto/gn-linux-arm64-1968-0725d782',
+ 'c2a372cd4f911028d8bc351fbf24835c9b1194fcc92beadf6c5a2b3addae973c',
+ 'linux', 'arm64'),
+ Dependency(
'third_party/gn/gn.exe',
'https://storage.googleapis.com/perfetto/gn-win-1968-0725d782',
'001f777f023c7a6959c778fb3a6b6cfc63f6baef953410ecdeaec350fb12285b',
@@ -150,6 +155,11 @@
'https://storage.googleapis.com/perfetto/ninja-win-182',
'09ced0fcd1a4dec7d1b798a2cf9ce5d20e5d2fbc2337343827f192ce47d0f491',
'windows', 'x64'),
+ Dependency(
+ 'third_party/ninja/ninja',
+ 'https://storage.googleapis.com/perfetto/ninja-linux-arm64-1111',
+ '05031a734ec4310a51b2cfe9f0096b26fce25ab4ff19e5b51abe6371de066cc5',
+ 'linux', 'arm64'),
# Keep the revision in sync with Chrome's PACKAGE_VERSION in
# tools/clang/scripts/update.py.
@@ -463,6 +473,8 @@
arch = machine()
if arch == 'arm64':
return 'arm64'
+ elif arch == 'aarch64':
+ return 'arm64'
else:
# Assume everything else is x64 matching previous behaviour.
return 'x64'
diff --git a/ui/src/base/static_initializers.ts b/ui/src/base/static_initializers.ts
index 5abfcd9..f927471 100644
--- a/ui/src/base/static_initializers.ts
+++ b/ui/src/base/static_initializers.ts
@@ -38,7 +38,7 @@
// from the global state (which is frozen) and later try to update the copies.
// By doing so, we accidentally the local copy of global state, which is
// supposed to be immutable.
- setAutoFreeze(false);
+ setAutoFreeze(true);
}
function initializeProtobuf() {
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index a524dfb..ec6e7e6 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -24,7 +24,7 @@
TrackGroupState,
TrackState,
} from '../common/state';
-import {Migrate, Track, TrackContext} from '../public';
+import {Migrate, Track, TrackContext, TrackTags} from '../public';
import {globals} from './globals';
import {drawGridLines} from './gridline_helper';
@@ -44,6 +44,7 @@
private shellWidth = 0;
private backgroundColor = '#ffffff'; // Updated from CSS later.
private summaryTrack?: Track;
+ private summaryTrackTags?: TrackTags;
constructor({attrs}: m.CVnode<Attrs>) {
super();
@@ -70,6 +71,7 @@
};
this.summaryTrack = pluginManager.createTrack(uri, ctx);
+ this.summaryTrackTags = pluginManager.resolveTrackInfo(uri)?.tags;
}
get trackGroupState(): TrackGroupState {
@@ -148,7 +150,7 @@
'h1.track-title',
{title: name},
name,
- renderChips(this.summaryTrackState),
+ renderChips(this.summaryTrackTags),
),
(this.trackGroupState.collapsed && child !== null) ?
m('h2.track-subtitle', child) :
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 5c3df0f..dec3a65 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -22,8 +22,9 @@
import {pluginManager} from '../common/plugins';
import {TrackState} from '../common/state';
import {raf} from '../core/raf_scheduler';
-import {Migrate, SliceRect, Track, TrackContext} from '../public';
+import {Migrate, SliceRect, Track, TrackContext, TrackTags} from '../public';
+import {checkerboard} from './checkerboard';
import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
import {globals} from './globals';
import {drawGridLines} from './gridline_helper';
@@ -74,29 +75,24 @@
}
}
-export function renderChips({uri}: TrackState) {
- const tagElements: m.Children = [];
- const trackInfo = pluginManager.resolveTrackInfo(uri);
- const tags = trackInfo?.tags;
- tags?.metric && tagElements.push(m(TrackChip, {text: 'metric'}));
- tags?.debuggable && tagElements.push(m(TrackChip, {text: 'debuggable'}));
- return tagElements;
+export function renderChips(tags?: TrackTags) {
+ return [
+ tags?.metric && m(TrackChip, {text: 'metric'}),
+ tags?.debuggable && m(TrackChip, {text: 'debuggable'}),
+ ];
}
interface TrackShellAttrs {
- track: Track;
- trackState: TrackState;
+ trackKey: string;
+ title: string;
+ buttons: m.Children;
+ tags?: TrackTags;
}
class TrackShell implements m.ClassComponent<TrackShellAttrs> {
// Set to true when we click down and drag the
private dragging = false;
private dropping: 'before'|'after'|undefined = undefined;
- private attrs?: TrackShellAttrs;
-
- oninit(vnode: m.Vnode<TrackShellAttrs>) {
- this.attrs = vnode.attrs;
- }
view({attrs}: m.CVnode<TrackShellAttrs>) {
// The shell should be highlighted if the current search result is inside
@@ -105,7 +101,7 @@
const searchIndex = globals.state.searchIndex;
if (searchIndex !== -1) {
const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
- if (trackKey === attrs.trackState.key) {
+ if (trackKey === attrs.trackKey) {
highlightClass = 'flash';
}
}
@@ -116,34 +112,34 @@
`.track-shell[draggable=true]`,
{
class: `${highlightClass} ${dragClass} ${dropClass}`,
- ondragstart: this.ondragstart.bind(this),
+ ondragstart: (e: DragEvent) => this.ondragstart(e, attrs.trackKey),
ondragend: this.ondragend.bind(this),
ondragover: this.ondragover.bind(this),
ondragleave: this.ondragleave.bind(this),
- ondrop: this.ondrop.bind(this),
+ ondrop: (e: DragEvent) => this.ondrop(e, attrs.trackKey),
},
m(
'h1',
{
- title: attrs.trackState.name,
+ title: attrs.title,
style: {
- 'font-size': getTitleSize(attrs.trackState.name),
+ 'font-size': getTitleSize(attrs.title),
},
},
- attrs.trackState.name,
- renderChips(attrs.trackState),
+ attrs.title,
+ renderChips(attrs.tags),
),
m('.track-buttons',
- attrs.track.getTrackShellButtons(),
+ attrs.buttons,
m(TrackButton, {
action: () => {
globals.dispatch(
- Actions.toggleTrackPinned({trackKey: attrs.trackState.key}));
+ Actions.toggleTrackPinned({trackKey: attrs.trackKey}));
},
i: Icons.Pin,
- filledIcon: isPinned(attrs.trackState.key),
- tooltip: isPinned(attrs.trackState.key) ? 'Unpin' : 'Pin to top',
- showButton: isPinned(attrs.trackState.key),
+ filledIcon: isPinned(attrs.trackKey),
+ tooltip: isPinned(attrs.trackKey) ? 'Unpin' : 'Pin to top',
+ showButton: isPinned(attrs.trackKey),
fullHeight: true,
}),
globals.state.currentSelection !== null &&
@@ -151,25 +147,24 @@
m(TrackButton, {
action: (e: MouseEvent) => {
globals.dispatch(Actions.toggleTrackSelection(
- {id: attrs.trackState.key, isTrackGroup: false}));
+ {id: attrs.trackKey, isTrackGroup: false}));
e.stopPropagation();
},
- i: isSelected(attrs.trackState.key) ? Icons.Checkbox :
- Icons.BlankCheckbox,
- tooltip: isSelected(attrs.trackState.key) ?
- 'Remove track' :
- 'Add track to selection',
+ i: isSelected(attrs.trackKey) ? Icons.Checkbox :
+ Icons.BlankCheckbox,
+ tooltip: isSelected(attrs.trackKey) ? 'Remove track' :
+ 'Add track to selection',
showButton: true,
}) :
''));
}
- ondragstart(e: DragEvent) {
+ ondragstart(e: DragEvent, trackKey: string) {
const dataTransfer = e.dataTransfer;
if (dataTransfer === null) return;
this.dragging = true;
raf.scheduleFullRedraw();
- dataTransfer.setData('perfetto/track', `${this.attrs!.trackState.key}`);
+ dataTransfer.setData('perfetto/track', `${trackKey}`);
dataTransfer.setDragImage(new Image(), 0, 0);
}
@@ -202,13 +197,13 @@
raf.scheduleFullRedraw();
}
- ondrop(e: DragEvent) {
+ ondrop(e: DragEvent, trackKey: string) {
if (this.dropping === undefined) return;
const dataTransfer = e.dataTransfer;
if (dataTransfer === null) return;
raf.scheduleFullRedraw();
const srcId = dataTransfer.getData('perfetto/track');
- const dstId = this.attrs!.trackState.key;
+ const dstId = trackKey;
globals.dispatch(Actions.moveTrack({srcId, op: this.dropping, dstId}));
this.dropping = undefined;
}
@@ -273,9 +268,14 @@
}
interface TrackComponentAttrs {
- trackState: TrackState;
- track: Track;
+ trackKey: string;
+ heightPx?: number;
+ title: string;
+ buttons?: m.Children;
+ tags?: TrackTags;
+ track?: Track;
}
+
class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
view({attrs}: m.CVnode<TrackComponentAttrs>) {
// TODO(hjd): The min height below must match the track_shell_title
@@ -285,19 +285,24 @@
'.track',
{
style: {
- height: `${Math.max(18, attrs.track.getHeight())}px`,
+ height: `${Math.max(18, attrs.heightPx ?? 0)}px`,
},
- id: 'track_' + attrs.trackState.key,
+ id: 'track_' + attrs.trackKey,
},
[
- m(TrackShell, {track: attrs.track, trackState: attrs.trackState}),
- m(TrackContent, {track: attrs.track}),
+ m(TrackShell, {
+ buttons: attrs.buttons,
+ title: attrs.title,
+ trackKey: attrs.trackKey,
+ tags: attrs.tags,
+ }),
+ attrs.track && m(TrackContent, {track: attrs.track}),
]);
}
oncreate({attrs}: m.CVnode<TrackComponentAttrs>) {
- if (globals.scrollToTrackKey === attrs.trackState.key) {
- verticalScrollToTrack(attrs.trackState.key);
+ if (globals.scrollToTrackKey === attrs.trackKey) {
+ verticalScrollToTrack(attrs.trackKey);
globals.scrollToTrackKey = undefined;
}
}
@@ -340,6 +345,7 @@
// has disappeared.
private track: Track|undefined;
private trackState: TrackState|undefined;
+ private tags: TrackTags|undefined;
private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
const trackKey = vnode.attrs.trackKey;
@@ -363,6 +369,7 @@
};
this.track = pluginManager.createTrack(uri, trackCtx);
+ this.tags = pluginManager.resolveTrackInfo(uri)?.tags;
this.track?.onCreate(trackCtx);
this.trackState = trackState;
@@ -374,9 +381,19 @@
}
if (this.track === undefined || this.trackState === undefined) {
- return m('div', 'No such track');
+ return m(TrackComponent, {
+ trackKey: vnode.attrs.trackKey,
+ title: this.trackState?.name ?? 'Loading...',
+ });
}
- return m(TrackComponent, {trackState: this.trackState, track: this.track});
+ return m(TrackComponent, {
+ tags: this.tags,
+ heightPx: this.track.getHeight(),
+ title: this.trackState.name,
+ trackKey: this.trackState.key,
+ buttons: this.track.getTrackShellButtons(),
+ track: this.track,
+ });
}
oncreate() {
@@ -428,6 +445,8 @@
ctx.translate(TRACK_SHELL_WIDTH, 0);
if (this.track !== undefined) {
this.track.render(ctx);
+ } else {
+ checkerboard(ctx, size.height, 0, size.width - TRACK_SHELL_WIDTH);
}
ctx.restore();
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
index 7658fb6..a521724 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
@@ -58,6 +58,59 @@
SELECT * FROM android_binder_graph(-1000, 1000, -1000, 1000)`,
'all process binder graph'),
});
+
+ ctx.registerCommand({
+ id: 'dev.perfetto.AndroidPerf#ThreadClusterDistribution',
+ name: 'Run query: runtime cluster distribution for a thread',
+ callback: async (tid) => {
+ if (tid === undefined) {
+ tid = prompt('Enter a thread tid', '');
+ if (tid === null) return;
+ }
+ ctx.tabs.openQuery(`
+ INCLUDE PERFETTO MODULE common.cpus;
+ WITH
+ total_runtime AS (
+ SELECT sum(dur) AS total_runtime
+ FROM sched s
+ LEFT JOIN thread t
+ USING (utid)
+ WHERE t.tid = ${tid}
+ )
+ SELECT
+ c.size AS cluster,
+ sum(dur)/1e6 AS total_dur_ms,
+ sum(dur) * 1.0 / (SELECT * FROM total_runtime) AS percentage
+ FROM sched s
+ LEFT JOIN thread t
+ USING (utid)
+ LEFT JOIN cpus c
+ ON s.cpu = c.cpu_index
+ WHERE t.tid = ${tid}
+ GROUP BY 1`, `runtime cluster distrubtion for tid ${tid}`);
+ },
+ });
+
+ ctx.registerCommand({
+ id: 'dev.perfetto.AndroidPerf#SchedLatency',
+ name: 'Run query: top 50 sched latency for a thread',
+ callback: async (tid) => {
+ if (tid === undefined) {
+ tid = prompt('Enter a thread tid', '');
+ if (tid === null) return;
+ }
+ ctx.tabs.openQuery(`
+ SELECT ts.*, t.tid, t.name, tt.id AS track_id
+ FROM thread_state ts
+ LEFT JOIN thread_track tt
+ USING (utid)
+ LEFT JOIN thread t
+ USING (utid)
+ WHERE ts.state IN ('R', 'R+') AND tid = ${tid}
+ ORDER BY dur DESC
+ LIMIT 50`, `top 50 sched latency slice for tid ${tid}`);
+ },
+ });
}
}
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS
new file mode 100644
index 0000000..e5632b1
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS
@@ -0,0 +1 @@
+lukechang@google.com
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
new file mode 100644
index 0000000..46fbb46
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -0,0 +1,109 @@
+// 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.
+
+import {
+ Plugin,
+ PluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
+} from '../../public';
+import {addDebugSliceTrack} from '../../tracks/debug/slice_track';
+
+class AndroidPerfTraceCounters implements Plugin {
+
+ onActivate(_: PluginContext): void {}
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ ctx.registerCommand({
+ id: 'dev.perfetto.AndroidPerfTraceCounters#ThreadRuntimeIPC',
+ name: 'Add a track to show a thread runtime ipc',
+ callback: async (tid) => {
+ if (tid === undefined) {
+ tid = prompt('Enter a thread tid', '');
+ if (tid === null) return;
+ }
+ const sql_prefix = `
+WITH
+ sched_switch_ipc AS (
+ SELECT
+ ts,
+ EXTRACT_ARG(arg_set_id, 'prev_pid') AS tid,
+ EXTRACT_ARG(arg_set_id, 'prev_comm') AS thread_name,
+ EXTRACT_ARG(arg_set_id, 'inst') / (EXTRACT_ARG(arg_set_id, 'cyc') * 1.0) AS ipc,
+ EXTRACT_ARG(arg_set_id, 'inst') AS instruction,
+ EXTRACT_ARG(arg_set_id, 'cyc') AS cycle,
+ EXTRACT_ARG(arg_set_id, 'stallbm') AS stall_backend_mem,
+ EXTRACT_ARG(arg_set_id, 'l3dm') AS l3_cache_miss
+ FROM ftrace_event
+ WHERE name = 'sched_switch_with_ctrs' AND tid = ${tid}
+ ),
+ target_thread_sched_slice AS (
+ SELECT s.*, t.tid, t.name FROM sched s LEFT JOIN thread t USING (utid) WHERE t.tid = ${tid}
+ ),
+ target_thread_ipc_slice AS (
+ SELECT
+ (
+ SELECT
+ ts
+ FROM target_thread_sched_slice ts
+ WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+ ORDER BY ts.ts DESC
+ LIMIT 1
+ ) AS ts,
+ (
+ SELECT
+ dur
+ FROM target_thread_sched_slice ts
+ WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+ ORDER BY ts.ts DESC
+ LIMIT 1
+ ) AS dur,
+ ssi.ipc,
+ ssi.instruction,
+ ssi.cycle,
+ ssi.stall_backend_mem,
+ ssi.l3_cache_miss
+ FROM sched_switch_ipc ssi
+ )
+`
+
+ await addDebugSliceTrack(
+ ctx.engine,
+ {
+ sqlSource: sql_prefix + `
+SELECT * FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+ },
+ 'Rutime IPC:' + tid,
+ {ts: 'ts', dur: 'dur', name: 'ipc'},
+ ['instruction', 'cycle', 'stall_backend_mem', 'l3_cache_miss' ],
+ );
+ ctx.tabs.openQuery(sql_prefix + `
+SELECT
+ (sum(instruction) * 1.0 / sum(cycle)*1.0) AS avg_ipc,
+ sum(dur)/1e6 as total_runtime_ms,
+ sum(instruction) AS total_instructions,
+ sum(cycle) AS total_cycles,
+ sum(stall_backend_mem) as total_stall_backend_mem,
+ sum(l3_cache_miss) as total_l3_cache_miss
+FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+ 'target thread ipc statistic');
+ },
+ });
+ }
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: 'dev.perfetto.AndroidPerfTraceCounters',
+ plugin: AndroidPerfTraceCounters,
+};
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index f07f263..aa421f6 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -383,7 +383,7 @@
// Draw CPU idle rectangles that overlay the CPU freq graph.
ctx.fillStyle = `rgba(240, 240, 240, 1)`;
- for (let i = 0; i < data.lastIdleValues.length; i++) {
+ for (let i = startIdx; i < endIdx; i++) {
if (data.lastIdleValues[i] < 0) {
continue;
}