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 { 16 ComputeMetricArgs, 17 ComputeMetricResult, 18 RawQueryArgs, 19 RawQueryResult 20} from './protos'; 21import {iter, NUM_NULL, slowlyCountRows, STR} from './query_iterator'; 22import {TimeSpan} from './time'; 23 24export interface LoadingTracker { 25 beginLoading(): void; 26 endLoading(): void; 27} 28 29export class NullLoadingTracker implements LoadingTracker { 30 beginLoading(): void {} 31 endLoading(): void {} 32} 33 34export class QueryError extends Error {} 35 36/** 37 * Abstract interface of a trace proccessor. 38 * This is the TypeScript equivalent of src/trace_processor/rpc.h. 39 * 40 * Engine also defines helpers for the most common service methods 41 * (e.g. query). 42 */ 43export abstract class Engine { 44 abstract readonly id: string; 45 private _cpus?: number[]; 46 private _numGpus?: number; 47 private loadingTracker: LoadingTracker; 48 49 constructor(tracker?: LoadingTracker) { 50 this.loadingTracker = tracker ? tracker : new NullLoadingTracker(); 51 } 52 53 /** 54 * Push trace data into the engine. The engine is supposed to automatically 55 * figure out the type of the trace (JSON vs Protobuf). 56 */ 57 abstract parse(data: Uint8Array): Promise<void>; 58 59 /** 60 * Notify the engine no more data is coming. 61 */ 62 abstract notifyEof(): void; 63 64 /** 65 * Resets the trace processor state by destroying any table/views created by 66 * the UI after loading. 67 */ 68 abstract restoreInitialTables(): void; 69 70 /* 71 * Performs a SQL query and retruns a proto-encoded RawQueryResult object. 72 */ 73 abstract rawQuery(rawQueryArgs: Uint8Array): Promise<Uint8Array>; 74 75 /* 76 * Performs computation of metrics and returns metric result and any errors. 77 * Metric result is a proto binary or text encoded TraceMetrics object. 78 */ 79 abstract rawComputeMetric(computeMetricArgs: Uint8Array): Promise<Uint8Array>; 80 81 /** 82 * Shorthand for sending a SQL query to the engine. 83 * Deals with {,un}marshalling of request/response args. 84 */ 85 async query(sqlQuery: string): Promise<RawQueryResult> { 86 const result = await this.uncheckedQuery(sqlQuery); 87 if (result.error) { 88 throw new QueryError(`Query error "${sqlQuery}": ${result.error}`); 89 } 90 return result; 91 } 92 93 // This method is for noncritical queries that shouldn't throw an error 94 // on failure. The caller must handle the failure. 95 async uncheckedQuery(sqlQuery: string): Promise<RawQueryResult> { 96 this.loadingTracker.beginLoading(); 97 try { 98 const args = new RawQueryArgs(); 99 args.sqlQuery = sqlQuery; 100 args.timeQueuedNs = Math.floor(performance.now() * 1e6); 101 const argsEncoded = RawQueryArgs.encode(args).finish(); 102 const respEncoded = await this.rawQuery(argsEncoded); 103 const result = RawQueryResult.decode(respEncoded); 104 return result; 105 } finally { 106 this.loadingTracker.endLoading(); 107 } 108 } 109 110 /** 111 * Shorthand for sending a compute metrics request to the engine. 112 * Deals with {,un}marshalling of request/response args. 113 */ 114 async computeMetric(metrics: string[]): Promise<ComputeMetricResult> { 115 const args = new ComputeMetricArgs(); 116 args.metricNames = metrics; 117 args.format = ComputeMetricArgs.ResultFormat.TEXTPROTO; 118 const argsEncoded = ComputeMetricArgs.encode(args).finish(); 119 const respEncoded = await this.rawComputeMetric(argsEncoded); 120 const result = ComputeMetricResult.decode(respEncoded); 121 if (result.error.length > 0) { 122 throw new QueryError(result.error); 123 } 124 return result; 125 } 126 127 async queryOneRow(query: string): Promise<number[]> { 128 const result = await this.query(query); 129 const res: number[] = []; 130 if (slowlyCountRows(result) === 0) return res; 131 for (const col of result.columns) { 132 if (col.longValues!.length === 0) { 133 console.error( 134 `queryOneRow should only be used for queries that return long values 135 : ${query}`); 136 throw new Error( 137 `queryOneRow should only be used for queries that return long values 138 : ${query}`); 139 } 140 res.push(+col.longValues![0]); 141 } 142 return res; 143 } 144 145 // TODO(hjd): When streaming must invalidate this somehow. 146 async getCpus(): Promise<number[]> { 147 if (!this._cpus) { 148 const result = 149 await this.query('select distinct(cpu) from sched order by cpu;'); 150 if (slowlyCountRows(result) === 0) return []; 151 this._cpus = result.columns[0].longValues!.map(n => +n); 152 } 153 return this._cpus; 154 } 155 156 async getNumberOfGpus(): Promise<number> { 157 if (!this._numGpus) { 158 const result = await this.query(` 159 select count(distinct(gpu_id)) as gpuCount 160 from gpu_counter_track 161 where name = 'gpufreq'; 162 `); 163 this._numGpus = +result.columns[0].longValues![0]; 164 } 165 return this._numGpus; 166 } 167 168 // TODO: This should live in code that's more specific to chrome, instead of 169 // in engine. 170 async getNumberOfProcesses(): Promise<number> { 171 const result = await this.query('select count(*) from process;'); 172 return +result.columns[0].longValues![0]; 173 } 174 175 async getTraceTimeBounds(): Promise<TimeSpan> { 176 const query = `select start_ts, end_ts from trace_bounds`; 177 const res = (await this.queryOneRow(query)); 178 return new TimeSpan(res[0] / 1e9, res[1] / 1e9); 179 } 180 181 async getTracingMetadataTimeBounds(): Promise<TimeSpan> { 182 const query = await this.query(`select name, int_value from metadata 183 where name = 'tracing_started_ns' or name = 'tracing_disabled_ns' 184 or name = 'all_data_source_started_ns'`); 185 let startBound = -Infinity; 186 let endBound = Infinity; 187 const it = iter({'name': STR, 'int_value': NUM_NULL}, query); 188 for (; it.valid(); it.next()) { 189 const columnName = it.row.name; 190 const timestamp = it.row.int_value; 191 if (timestamp === null) continue; 192 if (columnName === 'tracing_disabled_ns') { 193 endBound = Math.min(endBound, timestamp / 1e9); 194 } else { 195 startBound = Math.max(startBound, timestamp / 1e9); 196 } 197 } 198 199 return new TimeSpan(startBound, endBound); 200 } 201} 202