blob: 11fd467de248ccaa0a390f1b75aa68f7820bebd3 [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.
import m from 'mithril';
import type {Trace} from '../../../public/trace';
import {Time} from '../../../base/time';
import type {QueryFlamegraphMetric} from '../../../components/query_flamegraph';
import {FlamegraphPanel} from '../../../components/flamegraph_panel';
import {Flamegraph, type FlamegraphState} from '../../../widgets/flamegraph';
import {Stack} from '../../../widgets/stack';
import {EmptyState} from '../../../widgets/empty_state';
import {
buildOomeCallstackMetrics,
loadOomeErrorMsg,
} from '../../dev.perfetto.HeapProfile/oome_callstack_common';
import type {OomeData} from '../types';
import {getOome} from '../queries';
import type {HeapDump} from '../queries';
import {AsyncLimiter} from '../../../base/async_limiter';
import {Monitor} from '../../../base/monitor';
interface CallstackViewAttrs {
readonly trace: Trace;
readonly dump: HeapDump;
readonly state: FlamegraphState | undefined;
readonly onStateChange: (state: FlamegraphState) => void;
}
export class CallstackView implements m.ClassComponent<CallstackViewAttrs> {
private oomeData?: OomeData;
private oomeDataLoaded = false;
private cachedMetrics?: ReadonlyArray<QueryFlamegraphMetric>;
private cachedKey?: string;
private errorMsg?: string;
private readonly limiter = new AsyncLimiter();
private monitor?: Monitor;
async loadErrorMsg(trace: Trace, ts: bigint) {
this.errorMsg = await loadOomeErrorMsg(trace.engine, Time.fromRaw(ts));
m.redraw();
}
view({attrs}: m.Vnode<CallstackViewAttrs>) {
this.monitor ??= new Monitor([() => attrs.dump]);
if (this.monitor.ifStateChanged()) {
this.oomeData = undefined;
this.oomeDataLoaded = false;
const dump = attrs.dump;
this.limiter.schedule(async () => {
try {
this.oomeData = await getOome(attrs.trace.engine, dump);
} catch {
this.oomeData = undefined;
} finally {
this.oomeDataLoaded = true;
m.redraw();
}
});
}
if (!this.oomeDataLoaded) {
return m(
'div',
{class: 'pf-hde-view-content pf-hde-flamegraph-view'},
m(FlamegraphPanel, {
trace: attrs.trace,
metrics: undefined,
state: attrs.state,
onStateChange: attrs.onStateChange,
}),
);
}
if (this.oomeData === undefined) {
return m(
EmptyState,
{
icon: 'data_array',
title: 'Data is not available in this trace',
fillHeight: true,
},
m(
'div',
'Callstacks in heap dumps are only available in Perfetto heap dumps collected on OutOfMemoryError and in recent versions of Android',
),
);
}
const upid = this.oomeData.upid;
const ts = this.oomeData.ts;
const key = `${upid}:${ts}`;
if (this.cachedMetrics === undefined || key !== this.cachedKey) {
this.cachedMetrics = buildOomeCallstackMetrics(Time.fromRaw(ts));
this.cachedKey = key;
this.errorMsg = undefined;
this.loadErrorMsg(attrs.trace, ts);
}
const metrics = this.cachedMetrics;
let state = attrs.state;
if (state === undefined) {
state = Flamegraph.createDefaultState(metrics);
attrs.onStateChange(state);
}
return m(
'div',
{class: 'pf-hde-view-content pf-hde-flamegraph-view'},
m(
Stack,
{orientation: 'vertical'},
this.errorMsg &&
m(
'div',
{style: {padding: '8px', fontSize: '14px', color: '#ff4081'}},
this.errorMsg,
),
m(FlamegraphPanel, {
trace: attrs.trace,
metrics,
state,
onStateChange: attrs.onStateChange,
}),
),
);
}
}