Home | History | Annotate | Download | only in common
      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 
     15 import * as protobufjs from 'protobufjs/light';
     16 
     17 import {defer} from '../base/deferred';
     18 import {WasmBridgeRequest, WasmBridgeResponse} from '../engine/wasm_bridge';
     19 
     20 import {Engine} from './engine';
     21 import {TraceProcessor} from './protos';
     22 import {Method, rpc, Message} from 'protobufjs/light';
     23 
     24 const activeWorkers = new Map<string, Worker>();
     25 let warmWorker: null|Worker = null;
     26 
     27 function 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.
     33 export 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 
     46 export 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  */
     62 export 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  */
     73 export 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