blob: 9af97174085818464b245dab1e4912bf0345af37 [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 {Engine} from '../../../trace_processor/engine';
import type {SqlValue} from '../../../trace_processor/query_result';
import {EmptyState} from '../../../widgets/empty_state';
import {DataGrid} from '../../../components/widgets/datagrid/datagrid';
import {SQLDataSource} from '../../../components/widgets/datagrid/sql_data_source';
import type {ColumnSchema} from '../../../components/widgets/datagrid/datagrid_schema';
import type {Filter} from '../../../components/widgets/datagrid/model';
import {fmtHex} from '../format';
import {
type NavFn,
sizeRenderer,
countRenderer,
shortClassName,
RowCounter,
COL_INFO,
colHeader,
} from '../components';
import {dumpFilterSql, type HeapDump} from '../queries';
function buildQuery(activeDump: HeapDump): string {
return `
SELECT
o.id,
ifnull(c.deobfuscated_name, c.name) AS cls,
o.self_size,
o.native_size,
od.array_element_count AS element_count,
ifnull(o.heap_type, 'default') AS heap,
CAST(od.array_data_hash AS TEXT) AS array_hash
FROM heap_graph_object o
JOIN heap_graph_class c ON o.type_id = c.id
LEFT JOIN heap_graph_object_data od ON o.object_data_id = od.id
WHERE o.reachable != 0
AND ${dumpFilterSql(activeDump, 'o')}
AND od.array_data_hash IS NOT NULL
`;
}
function makeUiSchema(navigate: NavFn): ColumnSchema {
return {
id: {
title: 'Object',
columnType: 'identifier',
cellRenderer: (value: SqlValue, row) => {
const id = Number(value);
const cls = String(row.cls ?? '');
const display = `${shortClassName(cls)} ${fmtHex(id)}`;
return m(
'button',
{
class: 'pf-hde-link',
onclick: () => navigate('object', {id, label: display}),
},
display,
);
},
},
cls: {
title: 'Class',
columnType: 'text',
},
self_size: {
title: colHeader('Shallow', COL_INFO.shallow),
titleString: 'Shallow',
columnType: 'quantitative',
cellRenderer: sizeRenderer,
},
native_size: {
title: colHeader('Native', COL_INFO.shallowNative),
titleString: 'Native',
columnType: 'quantitative',
cellRenderer: sizeRenderer,
},
element_count: {
title: 'Elements',
columnType: 'quantitative',
cellRenderer: countRenderer,
},
heap: {
title: 'Heap',
columnType: 'text',
},
array_hash: {
title: 'Content Hash',
columnType: 'text',
},
};
}
interface ArraysViewAttrs {
readonly engine: Engine;
readonly activeDump: HeapDump;
readonly navigate: NavFn;
readonly clearNavParam: (key: string) => void;
readonly initialArrayHash?: string;
readonly hasFieldValues?: boolean;
}
export function ArraysView(): m.Component<ArraysViewAttrs> {
let dataSource: SQLDataSource | null = null;
const counter = new RowCounter();
let filters: Filter[] = [];
function applyNavFilter(
ah: string | undefined,
clearNavParam: (key: string) => void,
) {
if (!ah) return;
filters = [{field: 'array_hash', op: '=' as const, value: ah}];
counter.onFiltersChanged(filters);
clearNavParam('arrayHash');
}
return {
oninit(vnode) {
const {engine, activeDump} = vnode.attrs;
const query = buildQuery(activeDump);
dataSource = new SQLDataSource({
engine,
tableOrSubquery: query,
});
counter.init(engine, query);
applyNavFilter(vnode.attrs.initialArrayHash, vnode.attrs.clearNavParam);
},
onupdate(vnode) {
applyNavFilter(vnode.attrs.initialArrayHash, vnode.attrs.clearNavParam);
},
view(vnode) {
const {navigate} = vnode.attrs;
if (vnode.attrs.hasFieldValues === false) {
return m(EmptyState, {
icon: 'data_array',
title: 'Array data requires an ART heap dump (.hprof)',
fillHeight: true,
});
}
if (!dataSource) return null;
return m('div', {class: 'pf-hde-view-content'}, [
m('h2', {class: 'pf-hde-view-heading'}, counter.heading('Arrays')),
m(DataGrid, {
schema: makeUiSchema(navigate),
data: dataSource,
fillHeight: true,
initialColumns: [
{id: 'id', field: 'id'},
{id: 'cls', field: 'cls'},
{id: 'self_size', field: 'self_size'},
{id: 'native_size', field: 'native_size'},
{id: 'element_count', field: 'element_count'},
{id: 'heap', field: 'heap'},
],
filters,
showExportButton: true,
onFiltersChanged: (f) => {
filters = [...f];
counter.onFiltersChanged(f);
},
}),
]);
},
};
}