1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16import {duration, TimeSpan} from '../../base/time'; 17import {Engine} from '../../trace_processor/engine'; 18import { 19 LONG, 20 NUM_NULL, 21 STR, 22 STR_NULL, 23} from '../../trace_processor/query_result'; 24import {TreeNode} from '../../widgets/tree'; 25import {DurationWidget} from '../widgets/duration'; 26import {Utid} from '../sql_utils/core_types'; 27 28// An individual node of the thread state breakdown tree. 29class Node { 30 parent?: Node; 31 children: Map<string, Node>; 32 dur: duration; 33 startsCollapsed: boolean = true; 34 35 constructor(parent?: Node) { 36 this.parent = parent; 37 this.children = new Map(); 38 this.dur = 0n; 39 } 40 41 getOrCreateChild(name: string) { 42 let child = this.children.get(name); 43 if (!child) { 44 child = new Node(this); 45 this.children.set(name, child); 46 } 47 return child; 48 } 49 50 addDuration(dur: duration) { 51 let node: Node | undefined = this; 52 while (node !== undefined) { 53 node.dur += dur; 54 node = node.parent; 55 } 56 } 57} 58 59// Thread state breakdown data (tree). 60// Can be passed to ThreadStateBreakdownTreeNode to be rendered as a part of a 61// tree. 62export interface BreakdownByThreadState { 63 root: Node; 64} 65 66// Compute a breakdown of thread states for a given thread for a given time 67// interval. 68export async function breakDownIntervalByThreadState( 69 engine: Engine, 70 range: TimeSpan, 71 utid: Utid, 72): Promise<BreakdownByThreadState> { 73 // TODO(altimin): this probably should share some code with pivot tables when 74 // we actually get some pivot tables we like. 75 const query = await engine.query(` 76 INCLUDE PERFETTO MODULE sched.time_in_state; 77 INCLUDE PERFETTO MODULE sched.states; 78 INCLUDE PERFETTO MODULE android.cpu.cluster_type; 79 80 SELECT 81 sched_state_io_to_human_readable_string(state, io_wait) as state, 82 state AS rawState, 83 cluster_type AS clusterType, 84 cpu, 85 blocked_function AS blockedFunction, 86 dur 87 FROM sched_time_in_state_and_cpu_for_thread_in_interval(${range.start}, ${range.duration}, ${utid}) 88 LEFT JOIN android_cpu_cluster_mapping USING(cpu); 89 `); 90 const it = query.iter({ 91 state: STR, 92 rawState: STR, 93 clusterType: STR_NULL, 94 cpu: NUM_NULL, 95 blockedFunction: STR_NULL, 96 dur: LONG, 97 }); 98 const root = new Node(); 99 for (; it.valid(); it.next()) { 100 let currentNode = root; 101 currentNode = currentNode.getOrCreateChild(it.state); 102 // If the CPU time is not null, add it to the breakdown. 103 if (it.clusterType !== null) { 104 currentNode = currentNode.getOrCreateChild(it.clusterType); 105 } 106 if (it.cpu !== null) { 107 currentNode = currentNode.getOrCreateChild(`CPU ${it.cpu}`); 108 } 109 if (it.blockedFunction !== null) { 110 currentNode = currentNode.getOrCreateChild(`${it.blockedFunction}`); 111 } 112 currentNode.addDuration(it.dur); 113 } 114 return { 115 root, 116 }; 117} 118 119function renderChildren(node: Node, totalDur: duration): m.Child[] { 120 const res = Array.from(node.children.entries()).map(([name, child]) => 121 renderNode(child, name, totalDur), 122 ); 123 return res; 124} 125 126function renderNode(node: Node, name: string, totalDur: duration): m.Child { 127 const durPercent = (100 * Number(node.dur)) / Number(totalDur); 128 return m( 129 TreeNode, 130 { 131 left: name, 132 right: [ 133 m(DurationWidget, {dur: node.dur}), 134 ` (${durPercent.toFixed(2)}%)`, 135 ], 136 startsCollapsed: node.startsCollapsed, 137 }, 138 renderChildren(node, totalDur), 139 ); 140} 141 142interface BreakdownByThreadStateTreeNodeAttrs { 143 dur: duration; 144 data: BreakdownByThreadState; 145} 146 147// A tree node that displays a nested breakdown a time interval by thread state. 148export class BreakdownByThreadStateTreeNode 149 implements m.ClassComponent<BreakdownByThreadStateTreeNodeAttrs> 150{ 151 view({attrs}: m.Vnode<BreakdownByThreadStateTreeNodeAttrs>): m.Child[] { 152 return renderChildren(attrs.data.root, attrs.dur); 153 } 154} 155