1// Copyright (C) 2018 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, assertTrue} from '../base/logging'; 16import * as protos from '../gen/protos'; 17 18// Aliases protos to avoid the super nested namespaces. 19// See https://www.typescriptlang.org/docs/handbook/namespaces.html#aliases 20import AndroidLogConfig = protos.perfetto.protos.AndroidLogConfig; 21import AndroidPowerConfig = protos.perfetto.protos.AndroidPowerConfig; 22import AndroidLogId = protos.perfetto.protos.AndroidLogId; 23import BatteryCounters = 24 protos.perfetto.protos.AndroidPowerConfig.BatteryCounters; 25import BufferConfig = protos.perfetto.protos.TraceConfig.BufferConfig; 26import DataSourceConfig = protos.perfetto.protos.DataSourceConfig; 27import FtraceConfig = protos.perfetto.protos.FtraceConfig; 28import IAndroidPowerConfig = protos.perfetto.protos.IAndroidPowerConfig; 29import IBufferConfig = protos.perfetto.protos.TraceConfig.IBufferConfig; 30import IProcessStatsConfig = protos.perfetto.protos.IProcessStatsConfig; 31import IRawQueryArgs = protos.perfetto.protos.IRawQueryArgs; 32import ISysStatsConfig = protos.perfetto.protos.ISysStatsConfig; 33import ITraceConfig = protos.perfetto.protos.ITraceConfig; 34import MeminfoCounters = protos.perfetto.protos.MeminfoCounters; 35import ProcessStatsConfig = protos.perfetto.protos.ProcessStatsConfig; 36import RawQueryArgs = protos.perfetto.protos.RawQueryArgs; 37import RawQueryResult = protos.perfetto.protos.RawQueryResult; 38import StatCounters = protos.perfetto.protos.SysStatsConfig.StatCounters; 39import SysStatsConfig = protos.perfetto.protos.SysStatsConfig; 40import TraceConfig = protos.perfetto.protos.TraceConfig; 41import TraceProcessor = protos.perfetto.protos.TraceProcessor; 42import VmstatCounters = protos.perfetto.protos.VmstatCounters; 43 44// TODO(hjd): Maybe these should go in their own file. 45export interface Row { [key: string]: number|string; } 46 47const COLUMN_TYPE_STR = RawQueryResult.ColumnDesc.Type.STRING; 48const COLUMN_TYPE_DOUBLE = RawQueryResult.ColumnDesc.Type.DOUBLE; 49const COLUMN_TYPE_LONG = RawQueryResult.ColumnDesc.Type.LONG; 50 51function getCell(result: RawQueryResult, column: number, row: number): number| 52 string|null { 53 const values = result.columns[column]; 54 if (values.isNulls![row]) return null; 55 switch (result.columnDescriptors[column].type) { 56 case COLUMN_TYPE_LONG: 57 return +values.longValues![row]; 58 case COLUMN_TYPE_DOUBLE: 59 return +values.doubleValues![row]; 60 case COLUMN_TYPE_STR: 61 return values.stringValues![row]; 62 default: 63 throw new Error('Unhandled type!'); 64 } 65} 66 67export function rawQueryResultColumns(result: RawQueryResult): string[] { 68 // Two columns can conflict on the same name, e.g. 69 // select x.foo, y.foo from x join y. In that case store them using the 70 // full table.column notation. 71 const res = [] as string[]; 72 const uniqColNames = new Set<string>(); 73 const colNamesToDedupe = new Set<string>(); 74 for (const col of result.columnDescriptors) { 75 const colName = col.name || ''; 76 if (uniqColNames.has(colName)) { 77 colNamesToDedupe.add(colName); 78 } 79 uniqColNames.add(colName); 80 } 81 for (let i = 0; i < result.columnDescriptors.length; i++) { 82 const colName = result.columnDescriptors[i].name || ''; 83 if (colNamesToDedupe.has(colName)) { 84 res.push(`${colName}.${i + 1}`); 85 } else { 86 res.push(colName); 87 } 88 } 89 return res; 90} 91 92export function* rawQueryResultIter(result: RawQueryResult) { 93 const columns: Array<[string, number]> = rawQueryResultColumns(result).map( 94 (name, i): [string, number] => [name, i]); 95 for (let rowNum = 0; rowNum < result.numRecords; rowNum++) { 96 const row: Row = {}; 97 for (const [name, colNum] of columns) { 98 const cell = getCell(result, colNum, rowNum); 99 row[name] = cell === null ? '[NULL]' : cell; 100 } 101 yield row; 102 } 103} 104 105export const NUM = 0; 106export const STR = 'str'; 107export const NUM_NULL: number|null = 1; 108export const STR_NULL: string|null = 'str_null'; 109 110/** 111 * This function allows for type safe use of RawQueryResults. 112 * The input is a RawQueryResult (|raw|) and a "spec". 113 * A spec is an object where the keys are column names and the values 114 * are constants representing the types. For example: 115 * { 116 * upid: NUM, 117 * pid: NUM_NULL, 118 * processName: STR_NULL, 119 * } 120 * The output is a iterable of rows each row looks like the given spec: 121 * { 122 * upid: 1, 123 * pid: 42, 124 * processName: null, 125 * } 126 * Each row has an appropriate typescript type based on the spec so there 127 * is no need to use ! or cast when using the result of rawQueryToRows. 128 * Note: type checking to ensure that the RawQueryResult matches the spec 129 * happens at runtime, so if a query can return null and this is not reflected 130 * in the spec this will still crash at runtime. 131 */ 132export function* 133 rawQueryToRows<T>(raw: RawQueryResult, spec: T): IterableIterator<T> { 134 const allColumns = rawQueryResultColumns(raw); 135 const columns: Array<[string, (row: number) => string | number | null]> = []; 136 for (const [key, columnSpec] of Object.entries(spec)) { 137 const i = allColumns.indexOf(key); 138 assertTrue(i !== -1, `Expected column "${key}" (cols ${allColumns})`); 139 140 const column = raw.columns[i]; 141 const isNulls = column.isNulls!; 142 const columnType = raw.columnDescriptors[i].type; 143 144 if (columnSpec === NUM || columnSpec === STR) { 145 for (let j = 0; j < raw.numRecords; j++) { 146 assertFalse(column.isNulls![i], `Unexpected null in ${key} row ${j}`); 147 } 148 } 149 150 if (columnSpec === NUM || columnSpec === NUM_NULL) { 151 if (columnType === COLUMN_TYPE_STR) { 152 throw new Error(`Expected numbers in column ${key} found strings`); 153 } 154 } else if (columnSpec === STR || columnSpec === STR_NULL) { 155 if (columnType === COLUMN_TYPE_LONG || 156 columnType === COLUMN_TYPE_DOUBLE) { 157 throw new Error(`Expected strings in column ${key} found numbers`); 158 } 159 } 160 161 let accessor; 162 switch (columnType) { 163 case COLUMN_TYPE_LONG: { 164 const values = column.longValues!; 165 accessor = (i: number) => isNulls[i] ? null : +values[i]; 166 break; 167 } 168 case COLUMN_TYPE_DOUBLE: { 169 const values = column.doubleValues!; 170 accessor = (i: number) => isNulls[i] ? null : values[i]; 171 break; 172 } 173 case COLUMN_TYPE_STR: { 174 const values = column.stringValues!; 175 accessor = (i: number) => isNulls[i] ? null : values[i]; 176 break; 177 } 178 default: 179 // We can only reach here if the column is completely null. 180 accessor = (_: number) => null; 181 break; 182 } 183 columns.push([key, accessor]); 184 } 185 186 for (let i = 0; i < raw.numRecords; i++) { 187 const row: {[_: string]: number | string | null} = {}; 188 for (const [name, accessor] of columns) { 189 row[name] = accessor(i); 190 } 191 yield row as {} as T; 192 } 193} 194 195export { 196 AndroidLogConfig, 197 AndroidLogId, 198 AndroidPowerConfig, 199 BatteryCounters, 200 BufferConfig, 201 DataSourceConfig, 202 FtraceConfig, 203 IAndroidPowerConfig, 204 IBufferConfig, 205 IProcessStatsConfig, 206 IRawQueryArgs, 207 ISysStatsConfig, 208 ITraceConfig, 209 MeminfoCounters, 210 ProcessStatsConfig, 211 RawQueryArgs, 212 RawQueryResult, 213 StatCounters, 214 SysStatsConfig, 215 TraceConfig, 216 TraceProcessor, 217 VmstatCounters, 218}; 219