xref: /aosp_15_r20/external/perfetto/ui/src/components/details/thread_state.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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