| /* | |
| * Copyright (C) 2026 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 <benchmark/benchmark.h> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <string> | |
| #include <vector> | |
| #include "perfetto/base/logging.h" | |
| #include "src/trace_processor/containers/string_pool.h" | |
| #include "src/trace_processor/core/tree/tree.h" | |
| #include "src/trace_processor/core/util/slab.h" | |
| #include "src/trace_processor/plugins/flamegraph/flamegraph.h" | |
| namespace perfetto::trace_processor::flamegraph { | |
| namespace { | |
| struct Rng { | |
| uint64_t state = 42; | |
| uint32_t operator()() { | |
| state = state * 6364136223846793005ull + 1442695040888963407ull; | |
| return static_cast<uint32_t>(state >> 33); | |
| } | |
| }; | |
| template <typename T> | |
| core::Tree::Column ColumnFromValues(const std::vector<T>& values) { | |
| auto column = | |
| core::Tree::Column::Create<T>(static_cast<uint32_t>(values.size())); | |
| memcpy(column.data.begin(), values.data(), values.size() * sizeof(T)); | |
| return column; | |
| } | |
| core::Tree MakeInput(StringPool* pool, uint32_t rows) { | |
| constexpr uint32_t kNames = 10000; | |
| Rng rng; | |
| std::vector<StringPool::Id> name_table; | |
| for (uint32_t i = 0; i < kNames; ++i) { | |
| name_table.push_back( | |
| pool->InternString(base::StringView("frame_" + std::to_string(i)))); | |
| } | |
| core::Tree tree; | |
| tree.row_count = rows; | |
| tree.parent = core::Slab<uint32_t>::Alloc(rows); | |
| std::vector<StringPool::Id> names; | |
| std::vector<int64_t> values; | |
| std::vector<int64_t> groupings; | |
| names.reserve(rows); | |
| values.reserve(rows); | |
| groupings.reserve(rows); | |
| std::vector<uint32_t> stack; | |
| for (uint32_t row = 0; row < rows; ++row) { | |
| if (!stack.empty() && rng() % 100 < 40) { | |
| stack.resize(stack.size() - (rng() % stack.size())); | |
| } | |
| tree.parent[row] = stack.empty() ? core::Tree::kNullParent : stack.back(); | |
| names.push_back(name_table[rng() % kNames]); | |
| values.push_back(rng() % 3 == 0 ? rng() % 100 : 0); | |
| groupings.push_back(row); | |
| stack.push_back(row); | |
| } | |
| tree.names = {"name", "value", "grouping"}; | |
| tree.columns.push_back(ColumnFromValues(names)); | |
| tree.columns.push_back(ColumnFromValues(values)); | |
| tree.columns.push_back(ColumnFromValues(groupings)); | |
| return tree; | |
| } | |
| struct RunOptions { | |
| static RunOptions Filtered() { | |
| RunOptions options; | |
| options.filtered = true; | |
| return options; | |
| } | |
| static RunOptions Pivot() { | |
| RunOptions options; | |
| options.pivot = true; | |
| return options; | |
| } | |
| static RunOptions Aggregate() { | |
| RunOptions options; | |
| options.aggregate = true; | |
| return options; | |
| } | |
| static RunOptions SummaryAggregate() { | |
| RunOptions options; | |
| options.summary_aggregate = true; | |
| return options; | |
| } | |
| static RunOptions ConcatAggregate() { | |
| RunOptions options; | |
| options.concat_aggregate = true; | |
| return options; | |
| } | |
| static RunOptions NumericFilter() { | |
| RunOptions options; | |
| options.numeric_filter = true; | |
| return options; | |
| } | |
| bool filtered = false; | |
| bool pivot = false; | |
| bool aggregate = false; | |
| bool summary_aggregate = false; | |
| bool concat_aggregate = false; | |
| bool numeric_filter = false; | |
| }; | |
| void Run(benchmark::State& state, Config::View view, RunOptions options = {}) { | |
| StringPool pool; | |
| uint32_t rows = static_cast<uint32_t>(state.range(0)); | |
| for (auto _ : state) { | |
| state.PauseTiming(); | |
| core::Tree input = MakeInput(&pool, rows); | |
| Config config(pool); | |
| config.view = view; | |
| config.name = &input.columns[0]; | |
| config.value_columns.push_back(&input.columns[1]); | |
| if (options.aggregate) { | |
| config.aggregate_columns.push_back( | |
| {&input.columns[1], Config::Aggregate::kSum, "sum_value"}); | |
| } | |
| if (options.summary_aggregate) { | |
| config.aggregate_columns.push_back( | |
| {&input.columns[2], Config::Aggregate::kOneOrSummary, "summary"}); | |
| } | |
| if (options.concat_aggregate) { | |
| config.aggregate_columns.push_back({&input.columns[2], | |
| Config::Aggregate::kConcatWithComma, | |
| "concatenated"}); | |
| } | |
| if (options.numeric_filter) { | |
| config.grouping_columns.push_back(&input.columns[2]); | |
| config.show_stack_filters.push_back( | |
| base::Regex::CreateOrCheck("^12345$")); | |
| } | |
| if (options.filtered) { | |
| config.show_stack_filters.push_back( | |
| base::Regex::CreateOrCheck("frame_1\\d\\d")); | |
| config.hide_frame_filters.push_back( | |
| base::Regex::CreateOrCheck("frame_2\\d\\d")); | |
| } | |
| if (options.pivot) { | |
| config.view_pattern = base::Regex::CreateOrCheck("frame_12"); | |
| } | |
| state.ResumeTiming(); | |
| auto output = Build(std::move(input), config); | |
| PERFETTO_CHECK(output.ok()); | |
| benchmark::DoNotOptimize(output); | |
| } | |
| state.counters["frames/s"] = benchmark::Counter( | |
| static_cast<double>(rows), benchmark::Counter::kIsIterationInvariantRate); | |
| } | |
| void BM_FlamegraphTopDown(benchmark::State& state) { | |
| Run(state, Config::View(Config::TopDown{})); | |
| } | |
| BENCHMARK(BM_FlamegraphTopDown)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphTopDownFiltered(benchmark::State& state) { | |
| Run(state, Config::View(Config::TopDown{}), RunOptions::Filtered()); | |
| } | |
| BENCHMARK(BM_FlamegraphTopDownFiltered)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphTopDownAggregate(benchmark::State& state) { | |
| Run(state, Config::View(Config::TopDown{}), RunOptions::Aggregate()); | |
| } | |
| BENCHMARK(BM_FlamegraphTopDownAggregate)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphTopDownNumericFilter(benchmark::State& state) { | |
| Run(state, Config::View(Config::TopDown{}), RunOptions::NumericFilter()); | |
| } | |
| BENCHMARK(BM_FlamegraphTopDownNumericFilter)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphBottomUp(benchmark::State& state) { | |
| Run(state, Config::View(Config::BottomUp{})); | |
| } | |
| BENCHMARK(BM_FlamegraphBottomUp)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphBottomUpAggregate(benchmark::State& state) { | |
| Run(state, Config::View(Config::BottomUp{}), RunOptions::Aggregate()); | |
| } | |
| BENCHMARK(BM_FlamegraphBottomUpAggregate)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphTopDownSummaryAggregate(benchmark::State& state) { | |
| Run(state, Config::View(Config::TopDown{}), RunOptions::SummaryAggregate()); | |
| } | |
| BENCHMARK(BM_FlamegraphTopDownSummaryAggregate)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphTopDownConcatAggregate(benchmark::State& state) { | |
| Run(state, Config::View(Config::TopDown{}), RunOptions::ConcatAggregate()); | |
| } | |
| BENCHMARK(BM_FlamegraphTopDownConcatAggregate)->Arg(100000)->Arg(1000000); | |
| void BM_FlamegraphPivot(benchmark::State& state) { | |
| Run(state, Config::View(Config::Pivot{}), RunOptions::Pivot()); | |
| } | |
| BENCHMARK(BM_FlamegraphPivot)->Arg(100000)->Arg(1000000); | |
| } // namespace | |
| } // namespace perfetto::trace_processor::flamegraph |