// Copyright (C) 2023 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 {duration, TimeSpan} from '../../base/time'; import {Engine} from '../../trace_processor/engine'; import { LONG, NUM_NULL, STR, STR_NULL, } from '../../trace_processor/query_result'; import {TreeNode} from '../../widgets/tree'; import {DurationWidget} from '../widgets/duration'; import {Utid} from '../sql_utils/core_types'; // An individual node of the thread state breakdown tree. class Node { parent?: Node; children: Map; dur: duration; startsCollapsed: boolean = true; constructor(parent?: Node) { this.parent = parent; this.children = new Map(); this.dur = 0n; } getOrCreateChild(name: string) { let child = this.children.get(name); if (!child) { child = new Node(this); this.children.set(name, child); } return child; } addDuration(dur: duration) { let node: Node | undefined = this; while (node !== undefined) { node.dur += dur; node = node.parent; } } } // Thread state breakdown data (tree). // Can be passed to ThreadStateBreakdownTreeNode to be rendered as a part of a // tree. export interface BreakdownByThreadState { root: Node; } // Compute a breakdown of thread states for a given thread for a given time // interval. export async function breakDownIntervalByThreadState( engine: Engine, range: TimeSpan, utid: Utid, ): Promise { // TODO(altimin): this probably should share some code with pivot tables when // we actually get some pivot tables we like. const query = await engine.query(` INCLUDE PERFETTO MODULE sched.time_in_state; INCLUDE PERFETTO MODULE sched.states; INCLUDE PERFETTO MODULE android.cpu.cluster_type; SELECT sched_state_io_to_human_readable_string(state, io_wait) as state, state AS rawState, cluster_type AS clusterType, cpu, blocked_function AS blockedFunction, dur FROM sched_time_in_state_and_cpu_for_thread_in_interval(${range.start}, ${range.duration}, ${utid}) LEFT JOIN android_cpu_cluster_mapping USING(cpu); `); const it = query.iter({ state: STR, rawState: STR, clusterType: STR_NULL, cpu: NUM_NULL, blockedFunction: STR_NULL, dur: LONG, }); const root = new Node(); for (; it.valid(); it.next()) { let currentNode = root; currentNode = currentNode.getOrCreateChild(it.state); // If the CPU time is not null, add it to the breakdown. if (it.clusterType !== null) { currentNode = currentNode.getOrCreateChild(it.clusterType); } if (it.cpu !== null) { currentNode = currentNode.getOrCreateChild(`CPU ${it.cpu}`); } if (it.blockedFunction !== null) { currentNode = currentNode.getOrCreateChild(`${it.blockedFunction}`); } currentNode.addDuration(it.dur); } return { root, }; } function renderChildren(node: Node, totalDur: duration): m.Child[] { const res = Array.from(node.children.entries()).map(([name, child]) => renderNode(child, name, totalDur), ); return res; } function renderNode(node: Node, name: string, totalDur: duration): m.Child { const durPercent = (100 * Number(node.dur)) / Number(totalDur); return m( TreeNode, { left: name, right: [ m(DurationWidget, {dur: node.dur}), ` (${durPercent.toFixed(2)}%)`, ], startsCollapsed: node.startsCollapsed, }, renderChildren(node, totalDur), ); } interface BreakdownByThreadStateTreeNodeAttrs { dur: duration; data: BreakdownByThreadState; } // A tree node that displays a nested breakdown a time interval by thread state. export class BreakdownByThreadStateTreeNode implements m.ClassComponent { view({attrs}: m.Vnode): m.Child[] { return renderChildren(attrs.data.root, attrs.dur); } }