1// Copyright (C) 2019 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 {assertFalse} from '../../base/logging'; 16import {NUM, NUM_NULL, STR_NULL} from '../../common/query_result'; 17import {translateState} from '../../common/thread_state'; 18import {fromNs, toNs} from '../../common/time'; 19import { 20 TrackController, 21 trackControllerRegistry 22} from '../../controller/track_controller'; 23 24import { 25 Config, 26 Data, 27 THREAD_STATE_TRACK_KIND, 28} from './common'; 29 30class ThreadStateTrackController extends TrackController<Config, Data> { 31 static readonly kind = THREAD_STATE_TRACK_KIND; 32 33 private maxDurNs = 0; 34 35 async onSetup() { 36 await this.query(` 37 create view ${this.tableName('thread_state')} as 38 select 39 id, 40 ts, 41 dur, 42 cpu, 43 state, 44 io_wait as ioWait 45 from thread_state 46 where utid = ${this.config.utid} and utid != 0 47 `); 48 49 const queryRes = await this.query(` 50 select ifnull(max(dur), 0) as maxDur 51 from ${this.tableName('thread_state')} 52 `); 53 this.maxDurNs = queryRes.firstRow({maxDur: NUM}).maxDur; 54 } 55 56 async onBoundsChange(start: number, end: number, resolution: number): 57 Promise<Data> { 58 const resolutionNs = toNs(resolution); 59 const startNs = toNs(start); 60 const endNs = toNs(end); 61 62 // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to 63 // be an even number, so we can snap in the middle. 64 const bucketNs = 65 Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1); 66 67 const query = ` 68 select 69 (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq, 70 ts, 71 state = 'S' as is_sleep, 72 max(dur) as dur, 73 ifnull(cast(cpu as integer), -1) as cpu, 74 state, 75 ioWait, 76 ifnull(id, -1) as id 77 from ${this.tableName('thread_state')} 78 where 79 ts >= ${startNs - this.maxDurNs} and 80 ts <= ${endNs} 81 group by tsq, is_sleep 82 order by tsq 83 `; 84 85 const queryRes = await this.query(query); 86 const numRows = queryRes.numRows(); 87 88 const data: Data = { 89 start, 90 end, 91 resolution, 92 length: numRows, 93 ids: new Float64Array(numRows), 94 starts: new Float64Array(numRows), 95 ends: new Float64Array(numRows), 96 strings: [], 97 state: new Uint16Array(numRows), 98 cpu: new Int8Array(numRows), 99 }; 100 101 const stringIndexes = new Map< 102 {shortState: string | undefined; ioWait: boolean | undefined}, 103 number>(); 104 function internState( 105 shortState: string|undefined, ioWait: boolean|undefined) { 106 let idx = stringIndexes.get({shortState, ioWait}); 107 if (idx !== undefined) return idx; 108 idx = data.strings.length; 109 data.strings.push(translateState(shortState, ioWait)); 110 stringIndexes.set({shortState, ioWait}, idx); 111 return idx; 112 } 113 const it = queryRes.iter({ 114 'tsq': NUM, 115 'ts': NUM, 116 'dur': NUM, 117 'cpu': NUM, 118 'state': STR_NULL, 119 'ioWait': NUM_NULL, 120 'id': NUM, 121 }); 122 for (let row = 0; it.valid(); it.next(), row++) { 123 const startNsQ = it.tsq; 124 const startNs = it.ts; 125 const durNs = it.dur; 126 const endNs = startNs + durNs; 127 128 let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs; 129 endNsQ = Math.max(endNsQ, startNsQ + bucketNs); 130 131 const cpu = it.cpu; 132 const state = it.state || undefined; 133 const ioWait = it.ioWait === null ? undefined : !!it.ioWait; 134 const id = it.id; 135 136 // We should never have the end timestamp being the same as the bucket 137 // start. 138 assertFalse(startNsQ === endNsQ); 139 140 data.starts[row] = fromNs(startNsQ); 141 data.ends[row] = fromNs(endNsQ); 142 data.state[row] = internState(state, ioWait); 143 data.ids[row] = id; 144 data.cpu[row] = cpu; 145 } 146 return data; 147 } 148 149 async onDestroy() { 150 await this.query(`drop view if exists ${this.tableName('thread_state')}`); 151 } 152} 153 154trackControllerRegistry.register(ThreadStateTrackController); 155