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 {TimeSpan} from './time'; 22 23export interface LoadingTracker { 24 beginLoading(): void; 25 endLoading(): void; 26} 27 28export class NullLoadingTracker implements LoadingTracker { 29 beginLoading(): void {} 30 endLoading(): void {} 31} 32 33/** 34 * Abstract interface of a trace proccessor. 35 * This is the TypeScript equivalent of src/trace_processor/rpc.h. 36 * 37 * Engine also defines helpers for the most common service methods 38 * (e.g. query). 39 */ 40export abstract class Engine { 41 abstract readonly id: string; 42 private _cpus?: number[]; 43 private _numGpus?: number; 44 private loadingTracker: LoadingTracker; 45 46 constructor(tracker?: LoadingTracker) { 47 this.loadingTracker = tracker ? tracker : new NullLoadingTracker(); 48 } 49 50 /** 51 * Push trace data into the engine. The engine is supposed to automatically 52 * figure out the type of the trace (JSON vs Protobuf). 53 */ 54 abstract parse(data: Uint8Array): Promise<void>; 55 56 /** 57 * Notify the engine no more data is coming. 58 */ 59 abstract notifyEof(): void; 60 61 /** 62 * Resets the trace processor state by destroying any table/views created by 63 * the UI after loading. 64 */ 65 abstract restoreInitialTables(): void; 66 67 /* 68 * Performs a SQL query and retruns a proto-encoded RawQueryResult object. 69 */ 70 abstract rawQuery(rawQueryArgs: Uint8Array): Promise<Uint8Array>; 71 72 /* 73 * Performs computation of metrics and returns a proto-encoded TraceMetrics 74 * object. 75 */ 76 abstract rawComputeMetric(computeMetricArgs: Uint8Array): Promise<Uint8Array>; 77 78 /** 79 * Shorthand for sending a SQL query to the engine. 80 * Deals with {,un}marshalling of request/response args. 81 */ 82 async query(sqlQuery: string, userQuery = false): Promise<RawQueryResult> { 83 this.loadingTracker.beginLoading(); 84 try { 85 const args = new RawQueryArgs(); 86 args.sqlQuery = sqlQuery; 87 args.timeQueuedNs = Math.floor(performance.now() * 1e6); 88 const argsEncoded = RawQueryArgs.encode(args).finish(); 89 const respEncoded = await this.rawQuery(argsEncoded); 90 const result = RawQueryResult.decode(respEncoded); 91 if (!result.error || userQuery) return result; 92 // Query failed, throw an error since it was not a user query 93 throw new Error(`Query error "${sqlQuery}": ${result.error}`); 94 } finally { 95 this.loadingTracker.endLoading(); 96 } 97 } 98 99 /** 100 * Shorthand for sending a compute metrics request to the engine. 101 * Deals with {,un}marshalling of request/response args. 102 */ 103 async computeMetric(metrics: string[]): Promise<ComputeMetricResult> { 104 const args = new ComputeMetricArgs(); 105 args.metricNames = metrics; 106 const argsEncoded = ComputeMetricArgs.encode(args).finish(); 107 const respEncoded = await this.rawComputeMetric(argsEncoded); 108 return ComputeMetricResult.decode(respEncoded); 109 } 110 111 async queryOneRow(query: string): Promise<number[]> { 112 const result = await this.query(query); 113 const res: number[] = []; 114 if (result.numRecords === 0) return res; 115 for (const col of result.columns) { 116 if (col.longValues!.length === 0) { 117 console.error( 118 `queryOneRow should only be used for queries that return long values 119 : ${query}`); 120 throw new Error( 121 `queryOneRow should only be used for queries that return long values 122 : ${query}`); 123 } 124 res.push(+col.longValues![0]); 125 } 126 return res; 127 } 128 129 // TODO(hjd): When streaming must invalidate this somehow. 130 async getCpus(): Promise<number[]> { 131 if (!this._cpus) { 132 const result = 133 await this.query('select distinct(cpu) from sched order by cpu;'); 134 if (result.numRecords === 0) return []; 135 this._cpus = result.columns[0].longValues!.map(n => +n); 136 } 137 return this._cpus; 138 } 139 140 async getNumberOfGpus(): Promise<number> { 141 if (!this._numGpus) { 142 const result = await this.query(` 143 select count(distinct(gpu_id)) as gpuCount 144 from gpu_counter_track 145 where name = 'gpufreq'; 146 `); 147 this._numGpus = +result.columns[0].longValues![0]; 148 } 149 return this._numGpus; 150 } 151 152 // TODO: This should live in code that's more specific to chrome, instead of 153 // in engine. 154 async getNumberOfProcesses(): Promise<number> { 155 const result = await this.query('select count(*) from process;'); 156 return +result.columns[0].longValues![0]; 157 } 158 159 async getTraceTimeBounds(): Promise<TimeSpan> { 160 const query = `select start_ts, end_ts from trace_bounds`; 161 const res = (await this.queryOneRow(query)); 162 return new TimeSpan(res[0] / 1e9, res[1] / 1e9); 163 } 164} 165