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 * as protobufjs from 'protobufjs/light'; 16 17import {defer} from '../base/deferred'; 18import {WasmBridgeRequest, WasmBridgeResponse} from '../engine/wasm_bridge'; 19 20import {Engine} from './engine'; 21import {TraceProcessor} from './protos'; 22import {Method, rpc, Message} from 'protobufjs/light'; 23 24const activeWorkers = new Map<string, Worker>(); 25let warmWorker: null|Worker = null; 26 27function createWorker(): Worker { 28 return new Worker('engine_bundle.js'); 29} 30 31// Take the warm engine and start creating a new WASM engine in the background 32// for the next call. 33export function createWasmEngine(id: string): Worker { 34 if (warmWorker === null) { 35 throw new Error('warmupWasmEngine() not called'); 36 } 37 if (activeWorkers.has(id)) { 38 throw new Error(`Duplicate worker ID ${id}`); 39 } 40 const activeWorker = warmWorker; 41 warmWorker = createWorker(); 42 activeWorkers.set(id, activeWorker); 43 return activeWorker; 44} 45 46export function destroyWasmEngine(id: string) { 47 if (!activeWorkers.has(id)) { 48 throw new Error(`Cannot find worker ID ${id}`); 49 } 50 activeWorkers.get(id)!.terminate(); 51 activeWorkers.delete(id); 52} 53 54/** 55 * It's quite slow to compile WASM and (in Chrome) this happens every time 56 * a worker thread attempts to load a WASM module since there is no way to 57 * cache the compiled code currently. To mitigate this we can always keep a 58 * WASM backend 'ready to go' just waiting to be provided with a trace file. 59 * warmupWasmEngineWorker (together with getWasmEngineWorker) 60 * implement this behaviour. 61 */ 62export function warmupWasmEngine(): void { 63 if (warmWorker !== null) { 64 throw new Error('warmupWasmEngine() already called'); 65 } 66 warmWorker = createWorker(); 67} 68 69/** 70 * This implementation of Engine uses a WASM backend hosted in a seperate 71 * worker thread. 72 */ 73export class WasmEngineProxy extends Engine { 74 private readonly worker: Worker; 75 private readonly traceProcessor_: TraceProcessor; 76 private pendingCallbacks: Map<number, protobufjs.RPCImplCallback>; 77 private nextRequestId: number; 78 readonly id: string; 79 80 constructor(args: {id: string, worker: Worker}) { 81 super(); 82 this.nextRequestId = 0; 83 this.pendingCallbacks = new Map(); 84 this.id = args.id; 85 this.worker = args.worker; 86 this.worker.onmessage = this.onMessage.bind(this); 87 this.traceProcessor_ = 88 TraceProcessor.create(this.rpcImpl.bind(this, 'trace_processor')); 89 } 90 91 get rpc(): TraceProcessor { 92 return this.traceProcessor_; 93 } 94 95 parse(data: Uint8Array): Promise<void> { 96 const id = this.nextRequestId++; 97 const request: WasmBridgeRequest = 98 {id, serviceName: 'trace_processor', methodName: 'parse', data}; 99 const promise = defer<void>(); 100 this.pendingCallbacks.set(id, () => promise.resolve()); 101 this.worker.postMessage(request); 102 return promise; 103 } 104 105 notifyEof(): Promise<void> { 106 const id = this.nextRequestId++; 107 const data = Uint8Array.from([]); 108 const request: WasmBridgeRequest = 109 {id, serviceName: 'trace_processor', methodName: 'notifyEof', data}; 110 const promise = defer<void>(); 111 this.pendingCallbacks.set(id, () => promise.resolve()); 112 this.worker.postMessage(request); 113 return promise; 114 } 115 116 onMessage(m: MessageEvent) { 117 const response = m.data as WasmBridgeResponse; 118 const callback = this.pendingCallbacks.get(response.id); 119 if (callback === undefined) { 120 throw new Error(`No such request: ${response.id}`); 121 } 122 this.pendingCallbacks.delete(response.id); 123 callback(null, response.data); 124 } 125 126 rpcImpl( 127 serviceName: string, 128 method: Method | rpc.ServiceMethod<Message<{}>, Message<{}>>, 129 requestData: Uint8Array, 130 callback: protobufjs.RPCImplCallback): void { 131 const methodName = method.name; 132 const id = this.nextRequestId++; 133 this.pendingCallbacks.set(id, callback); 134 const request: WasmBridgeRequest = { 135 id, 136 serviceName, 137 methodName, 138 data: requestData, 139 }; 140 this.worker.postMessage(request); 141 } 142} 143