blob: bf88916cb824e9c9db70d786dd15d4666817ff51 [file]
/*
* 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 "src/trace_processor/plugins/flamechart/flamechart.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/core/dataframe/adhoc_dataframe_builder.h"
#include "src/trace_processor/core/dataframe/dataframe.h"
#include "src/trace_processor/core/tree/tree.h"
#include "src/trace_processor/core/util/span.h"
namespace perfetto::trace_processor::flamechart {
namespace {
// One open (unclosed) segment per stack depth during the sweep.
struct OpenSegment {
// Tree row index of the frame at this depth.
uint32_t row;
// Timestamp at which the segment opened.
int64_t start;
// Number of points accumulated in this segment so far.
int64_t count;
};
} // namespace
base::StatusOr<dataframe::Dataframe> Build(const core::Tree& tree,
core::Span<const int64_t> ts,
core::Span<const int64_t> leaf_id,
StringPool* pool) {
PERFETTO_CHECK(ts.size() == leaf_id.size());
// Output ids are the tree's original node ids so consumers can join the
// runs back against the table the tree was built from. Trees without an
// Int64 id column (built by hand) fall back to row indices, matching how
// their leaf ids resolve.
const int64_t* original_ids =
!tree.columns.empty() && tree.columns[0].type.Is<core::Int64>()
? tree.columns[0].unchecked_data<int64_t>()
: nullptr;
// Segments are streamed straight into the output dataframe: memory stays
// O(open segments) plus the output.
dataframe::AdhocDataframeBuilder builder(
{"ts", "dur", "depth", "id", "sample_count"}, pool,
dataframe::AdhocDataframeBuilder::Options{
{}, dataframe::NullabilityType::kDenseNull, /*emit_auto_id=*/false});
const auto emit = [&](const OpenSegment& seg, size_t depth, int64_t dur) {
// Equal-timestamp stack transitions can close a frame immediately.
if (dur == 0) {
return;
}
builder.PushNonNull(0, seg.start);
builder.PushNonNull(1, dur);
builder.PushNonNull(2, static_cast<int64_t>(depth));
builder.PushNonNull(3, original_ids ? original_ids[seg.row]
: static_cast<int64_t>(seg.row));
builder.PushNonNull(4, seg.count);
};
std::vector<OpenSegment> open;
// Stack path of the current point, innermost frame first.
std::vector<uint32_t> path;
int64_t last_ts = 0;
for (size_t i = 0; i < ts.size(); ++i) {
if (i > 0 && ts[i] < last_ts) {
return base::ErrStatus("flamechart: ts must be non-decreasing");
}
last_ts = ts[i];
// Unresolvable leaves (e.g. a sample with no stack) are skipped; open
// segments are kept as-is, without increasing their sample counts.
const uint32_t leaf_row = tree.FindRow(leaf_id[i]);
if (leaf_row == core::Tree::kNullParent) {
continue;
}
path.clear();
for (uint32_t r = leaf_row; r != core::Tree::kNullParent;
r = tree.parent[r]) {
path.push_back(r);
}
const size_t old_depth = open.size();
const size_t new_depth = path.size();
// Length of the common prefix counting from the root (outermost frame):
// segments at these depths continue, everything deeper diverges.
size_t common = 0;
const size_t max_cmp = std::min(old_depth, new_depth);
while (common < max_cmp &&
open[common].row == path[new_depth - 1 - common]) {
++common;
}
// Close the divergent tail (deepest first for a stable output order).
for (size_t d = old_depth; d > common; --d) {
emit(open[d - 1], d - 1, ts[i] - open[d - 1].start);
}
open.resize(common);
// Open new segments for the divergent tail of the new stack.
for (size_t d = common; d < new_depth; ++d) {
open.push_back(OpenSegment{path[new_depth - 1 - d], ts[i], /*count=*/0});
}
// Every depth of the point's stack gains one sample.
for (size_t d = 0; d < new_depth; ++d) {
++open[d].count;
}
}
// No later observation closes these frames: emit them as incomplete.
for (size_t d = 0; d < open.size(); ++d) {
emit(open[d], d, -1);
}
return std::move(builder).Build();
}
} // namespace perfetto::trace_processor::flamechart