• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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';
16
17import {duration, TimeSpan} from '../../base/time';
18import {Engine} from '../../public';
19import {
20  LONG,
21  NUM_NULL,
22  STR,
23  STR_NULL,
24} from '../../trace_processor/query_result';
25import {TreeNode} from '../../widgets/tree';
26import {Utid} from '../sql_types';
27import {DurationWidget} from '../widgets/duration';
28
29// An individual node of the thread state breakdown tree.
30class Node {
31  parent?: Node;
32  children: Map<string, Node>;
33  dur: duration;
34  startsCollapsed: boolean = true;
35
36  constructor(parent?: Node) {
37    this.parent = parent;
38    this.children = new Map();
39    this.dur = 0n;
40  }
41
42  getOrCreateChild(name: string) {
43    let child = this.children.get(name);
44    if (!child) {
45      child = new Node(this);
46      this.children.set(name, child);
47    }
48    return child;
49  }
50
51  addDuration(dur: duration) {
52    let node: Node | undefined = this;
53    while (node !== undefined) {
54      node.dur += dur;
55      node = node.parent;
56    }
57  }
58}
59
60// Thread state breakdown data (tree).
61// Can be passed to ThreadStateBreakdownTreeNode to be rendered as a part of a
62// tree.
63export interface BreakdownByThreadState {
64  root: Node;
65}
66
67// Compute a breakdown of thread states for a given thread for a given time
68// interval.
69export async function breakDownIntervalByThreadState(
70  engine: Engine,
71  range: TimeSpan,
72  utid: Utid,
73): Promise<BreakdownByThreadState> {
74  // TODO(altimin): this probably should share some code with pivot tables when
75  // we actually get some pivot tables we like.
76  const query = await engine.query(`
77    INCLUDE PERFETTO MODULE sched.time_in_state;
78    INCLUDE PERFETTO MODULE sched.states;
79    INCLUDE PERFETTO MODULE cpu.size;
80
81    SELECT
82      sched_state_io_to_human_readable_string(state, io_wait) as state,
83      state AS rawState,
84      cpu_guess_core_type(cpu) AS cpuType,
85      cpu,
86      blocked_function AS blockedFunction,
87      dur
88    FROM sched_time_in_state_and_cpu_for_thread_in_interval(${range.start}, ${range.duration}, ${utid});
89  `);
90  const it = query.iter({
91    state: STR,
92    rawState: STR,
93    cpuType: 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.cpuType !== null) {
104      currentNode = currentNode.getOrCreateChild(it.cpuType);
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