1// Copyright (C) 2020 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 {ColumnDef, Sorting, ThreadStateExtra} from '../../public/aggregation'; 16import {AreaSelection} from '../../public/selection'; 17import {Engine} from '../../trace_processor/engine'; 18import { 19 LONG, 20 NUM, 21 NUM_NULL, 22 STR, 23 STR_NULL, 24} from '../../trace_processor/query_result'; 25import {AreaSelectionAggregator} from '../../public/selection'; 26import {UnionDataset} from '../../trace_processor/dataset'; 27import {translateState} from '../../components/sql_utils/thread_state'; 28import {TrackDescriptor} from '../../public/track'; 29 30export class ThreadStateSelectionAggregator implements AreaSelectionAggregator { 31 readonly id = 'thread_state_aggregation'; 32 33 async createAggregateView(engine: Engine, area: AreaSelection) { 34 const dataset = this.getDatasetFromTracks(area.tracks); 35 if (dataset === undefined) return false; 36 37 await engine.query(` 38 create or replace perfetto table ${this.id} as 39 select 40 process.name as process_name, 41 process.pid, 42 thread.name as thread_name, 43 thread.tid, 44 tstate.state || ',' || ifnull(tstate.io_wait, 'NULL') as concat_state, 45 sum(tstate.dur) AS total_dur, 46 sum(tstate.dur) / count() as avg_dur, 47 count() as occurrences 48 from (${dataset.query()}) tstate 49 join thread using (utid) 50 left join process using (upid) 51 where 52 ts + dur > ${area.start} 53 and ts < ${area.end} 54 group by utid, concat_state 55 `); 56 57 return true; 58 } 59 60 async getExtra( 61 engine: Engine, 62 area: AreaSelection, 63 ): Promise<ThreadStateExtra | void> { 64 const dataset = this.getDatasetFromTracks(area.tracks); 65 if (dataset === undefined) return; 66 67 const query = ` 68 select 69 state, 70 io_wait as ioWait, 71 sum(dur) as totalDur 72 from (${dataset.query()}) tstate 73 join thread using (utid) 74 where tstate.ts + tstate.dur > ${area.start} 75 and tstate.ts < ${area.end} 76 group by state, io_wait 77 `; 78 const result = await engine.query(query); 79 80 const it = result.iter({ 81 state: STR_NULL, 82 ioWait: NUM_NULL, 83 totalDur: NUM, 84 }); 85 86 let totalMs = 0; 87 const values = new Float64Array(result.numRows()); 88 const states = []; 89 for (let i = 0; it.valid(); ++i, it.next()) { 90 const state = it.state == null ? undefined : it.state; 91 const ioWait = it.ioWait === null ? undefined : it.ioWait > 0; 92 states.push(translateState(state, ioWait)); 93 const ms = it.totalDur / 1000000; 94 values[i] = ms; 95 totalMs += ms; 96 } 97 return { 98 kind: 'THREAD_STATE', 99 states, 100 values, 101 totalMs, 102 }; 103 } 104 105 getColumnDefinitions(): ColumnDef[] { 106 return [ 107 { 108 title: 'Process', 109 kind: 'STRING', 110 columnConstructor: Uint16Array, 111 columnId: 'process_name', 112 }, 113 { 114 title: 'PID', 115 kind: 'NUMBER', 116 columnConstructor: Uint16Array, 117 columnId: 'pid', 118 }, 119 { 120 title: 'Thread', 121 kind: 'STRING', 122 columnConstructor: Uint16Array, 123 columnId: 'thread_name', 124 }, 125 { 126 title: 'TID', 127 kind: 'NUMBER', 128 columnConstructor: Uint16Array, 129 columnId: 'tid', 130 }, 131 { 132 title: 'State', 133 kind: 'STATE', 134 columnConstructor: Uint16Array, 135 columnId: 'concat_state', 136 }, 137 { 138 title: 'Wall duration (ms)', 139 kind: 'TIMESTAMP_NS', 140 columnConstructor: Float64Array, 141 columnId: 'total_dur', 142 sum: true, 143 }, 144 { 145 title: 'Avg Wall duration (ms)', 146 kind: 'TIMESTAMP_NS', 147 columnConstructor: Float64Array, 148 columnId: 'avg_dur', 149 }, 150 { 151 title: 'Occurrences', 152 kind: 'NUMBER', 153 columnConstructor: Uint16Array, 154 columnId: 'occurrences', 155 sum: true, 156 }, 157 ]; 158 } 159 160 getTabName() { 161 return 'Thread States'; 162 } 163 164 getDefaultSorting(): Sorting { 165 return {column: 'total_dur', direction: 'DESC'}; 166 } 167 168 // Creates an optimized dataset containing the thread state events within a 169 // given list of tracks, or returns undefined if no compatible tracks are 170 // present in the list. 171 private getDatasetFromTracks(tracks: ReadonlyArray<TrackDescriptor>) { 172 const desiredSchema = { 173 dur: LONG, 174 io_wait: NUM_NULL, 175 state: STR, 176 utid: NUM, 177 }; 178 const validDatasets = tracks 179 .map((track) => track.track.getDataset?.()) 180 .filter((ds) => ds !== undefined) 181 .filter((ds) => ds.implements(desiredSchema)); 182 if (validDatasets.length === 0) { 183 return undefined; 184 } 185 return new UnionDataset(validDatasets).optimize(); 186 } 187} 188