| // 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 { |
| LineChartSvg, |
| type LineChartData, |
| type LineChartSeries, |
| } from '../../../../components/widgets/charts_svg/line_chart_svg'; |
| import type {LiveSession, SnapshotData} from '../../sessions/live_session'; |
| import {billboardBytes, maxSeriesKb, niceKbInterval} from '../../utils'; |
| import {formatBytesIec} from '../../../../base/bytes_format'; |
| import {Billboard, BillboardStrip} from '../../components/billboard'; |
| import {Panel} from '../../components/panel'; |
| |
| /** System memory breakdown from /proc/meminfo, partitioning MemTotal. */ |
| function buildSystemTimeSeries( |
| data: SnapshotData, |
| t0: number, |
| ): LineChartData | undefined { |
| const counterNames = [ |
| 'MemTotal', |
| 'MemFree', |
| 'Buffers', |
| 'Shmem', |
| 'Active(anon)', |
| 'Inactive(anon)', |
| 'Active(file)', |
| 'Inactive(file)', |
| 'Slab', |
| 'KernelStack', |
| 'PageTables', |
| 'Zram', |
| ]; |
| const byName = new Map<string, Map<number, number>>(); |
| for (const name of counterNames) { |
| const arr = data.systemCounters.get(name); |
| if (arr === undefined) continue; |
| const tsMap = new Map<number, number>(); |
| for (const {ts, value} of arr) { |
| tsMap.set(ts, value / 1024); |
| } |
| byName.set(name, tsMap); |
| } |
| |
| const dmaArr = data.systemCounters.get('mem.dma_heap'); |
| const dmaByTs = new Map<number, number>(); |
| if (dmaArr !== undefined) { |
| for (const {ts, value} of dmaArr) { |
| dmaByTs.set(ts, value / 1024); |
| } |
| } |
| const hasDmaHeap = dmaByTs.size > 0; |
| |
| const tsSet = new Set<number>(); |
| for (const tsMap of byName.values()) { |
| for (const ts of tsMap.keys()) tsSet.add(ts); |
| } |
| for (const ts of dmaByTs.keys()) tsSet.add(ts); |
| const timestamps = [...tsSet].sort((a, b) => a - b); |
| if (timestamps.length < 2) return undefined; |
| |
| const lastKnown = new Map<string, number>(); |
| let lastDmaKb = 0; |
| |
| const anonPts: {x: number; y: number}[] = []; |
| const filePts: {x: number; y: number}[] = []; |
| const shmemPts: {x: number; y: number}[] = []; |
| const buffersPts: {x: number; y: number}[] = []; |
| const slabPts: {x: number; y: number}[] = []; |
| const pageTablesPts: {x: number; y: number}[] = []; |
| const kernelStackPts: {x: number; y: number}[] = []; |
| const zramPts: {x: number; y: number}[] = []; |
| const dmaHeapPts: {x: number; y: number}[] = []; |
| const freePts: {x: number; y: number}[] = []; |
| const unaccountedPts: {x: number; y: number}[] = []; |
| |
| for (const ts of timestamps) { |
| const x = (ts - t0) / 1e9; |
| for (const name of counterNames) { |
| const tsMap = byName.get(name); |
| if (tsMap !== undefined && tsMap.has(ts)) { |
| lastKnown.set(name, tsMap.get(ts)!); |
| } |
| } |
| if (dmaByTs.has(ts)) lastDmaKb = dmaByTs.get(ts)!; |
| |
| const get = (name: string) => lastKnown.get(name) ?? 0; |
| const total = get('MemTotal'); |
| const free = get('MemFree'); |
| const anon = get('Active(anon)') + get('Inactive(anon)'); |
| const fileLru = get('Active(file)') + get('Inactive(file)'); |
| const shmem = get('Shmem'); |
| const fileCache = Math.max(0, fileLru - shmem); |
| const buffers = get('Buffers'); |
| const slab = get('Slab'); |
| const pageTables = get('PageTables'); |
| const kernelStack = get('KernelStack'); |
| const zram = get('Zram'); |
| const dmaHeap = hasDmaHeap ? lastDmaKb : 0; |
| const accounted = |
| anon + |
| fileCache + |
| shmem + |
| buffers + |
| slab + |
| pageTables + |
| kernelStack + |
| zram + |
| dmaHeap + |
| free; |
| const unaccounted = Math.max(0, total - accounted); |
| |
| anonPts.push({x, y: anon}); |
| filePts.push({x, y: fileCache}); |
| shmemPts.push({x, y: shmem}); |
| buffersPts.push({x, y: buffers}); |
| slabPts.push({x, y: slab}); |
| pageTablesPts.push({x, y: pageTables}); |
| kernelStackPts.push({x, y: kernelStack}); |
| zramPts.push({x, y: zram}); |
| dmaHeapPts.push({x, y: dmaHeap}); |
| freePts.push({x, y: free}); |
| unaccountedPts.push({x, y: unaccounted}); |
| } |
| |
| const series: LineChartSeries[] = [ |
| {name: 'Anon', points: anonPts}, |
| {name: 'Page cache', points: filePts}, |
| {name: 'Shmem', points: shmemPts}, |
| {name: 'Buffers', points: buffersPts}, |
| {name: 'Slab', points: slabPts}, |
| {name: 'PageTables', points: pageTablesPts}, |
| {name: 'KernelStack', points: kernelStackPts}, |
| {name: 'Zram', points: zramPts}, |
| {name: 'MemFree', points: freePts}, |
| {name: 'Unaccounted', points: unaccountedPts}, |
| ]; |
| if (hasDmaHeap) { |
| series.splice(series.length - 2, 0, { |
| name: 'DMA-BUF', |
| points: dmaHeapPts, |
| }); |
| } |
| return {series}; |
| } |
| |
| interface SystemBillboards { |
| totalKb: number; |
| availableKb: number; |
| anonKb: number; |
| fileCacheKb: number; |
| freeKb: number; |
| } |
| |
| /** Latest system memory values for billboard display. */ |
| function buildSystemBillboards( |
| data: SnapshotData, |
| ): SystemBillboards | undefined { |
| const getLatest = (name: string): number => { |
| const arr = data.systemCounters.get(name); |
| if (arr === undefined || arr.length === 0) return 0; |
| return Math.round(arr[arr.length - 1].value / 1024); |
| }; |
| const totalKb = getLatest('MemTotal'); |
| if (totalKb === 0) return undefined; |
| const anonKb = getLatest('Active(anon)') + getLatest('Inactive(anon)'); |
| const fileLruKb = getLatest('Active(file)') + getLatest('Inactive(file)'); |
| const shmemKb = getLatest('Shmem'); |
| return { |
| totalKb, |
| availableKb: getLatest('MemAvailable'), |
| anonKb, |
| fileCacheKb: Math.max(0, fileLruKb - shmemKb), |
| freeKb: getLatest('MemFree'), |
| }; |
| } |
| |
| export function renderSystemTab(session: LiveSession): m.Children { |
| const data = session.data; |
| if (!data) return null; |
| |
| const t0 = data.ts0; |
| const bb = buildSystemBillboards(data); |
| const chartData = buildSystemTimeSeries(data, t0); |
| |
| return m.fragment({}, [ |
| bb !== undefined && |
| m(BillboardStrip, [ |
| m(Billboard, { |
| ...billboardBytes(bb.totalKb * 1024), |
| label: 'MemTotal', |
| desc: 'Total physical RAM', |
| }), |
| m(Billboard, { |
| ...billboardBytes(bb.availableKb * 1024), |
| label: 'MemAvailable', |
| desc: 'Available without swapping', |
| }), |
| m(Billboard, { |
| ...billboardBytes(bb.anonKb * 1024), |
| label: 'Anon', |
| desc: 'Active(anon) + Inactive(anon)', |
| }), |
| m(Billboard, { |
| ...billboardBytes(bb.fileCacheKb * 1024), |
| label: 'Page Cache', |
| desc: 'File LRU \u2212 Shmem', |
| }), |
| m(Billboard, { |
| ...billboardBytes(bb.freeKb * 1024), |
| label: 'MemFree', |
| desc: 'Completely unused RAM', |
| }), |
| ]), |
| |
| m( |
| Panel, |
| m(Panel.Header, { |
| title: 'System Memory', |
| subtitle: |
| 'Stacked areas partition MemTotal. Source: /proc/meminfo. ' + |
| 'Anon = Active(anon) + Inactive(anon). Page cache = Active(file) + Inactive(file) \u2212 Shmem. ' + |
| 'Unaccounted = MemTotal \u2212 sum of all other categories.', |
| }), |
| m( |
| Panel.Body, |
| chartData |
| ? m(LineChartSvg, { |
| data: chartData, |
| height: 400, |
| xAxisLabel: 'Time (s)', |
| yAxisLabel: 'Memory', |
| showLegend: true, |
| showPoints: false, |
| stacked: true, |
| gridLines: 'both', |
| xAxisMin: data.xMin, |
| xAxisMax: data.xMax, |
| formatXValue: (v: number) => `${v.toFixed(0)}s`, |
| formatYValue: (v: number) => formatBytesIec(v * 1024), |
| yAxisMinInterval: niceKbInterval(maxSeriesKb(chartData.series)), |
| }) |
| : m('.pf-memscope-placeholder', 'Waiting for data\u2026'), |
| ), |
| ), |
| ]); |
| } |