• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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