• 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 {defer, Deferred} from '../base/deferred';
16import {assertTrue} from '../base/logging';
17import {WasmBridgeRequest, WasmBridgeResponse} from '../engine/wasm_bridge';
18
19import {Engine, LoadingTracker} from './engine';
20
21const activeWorkers = new Map<string, Worker>();
22let warmWorker: null|Worker = null;
23
24function createWorker(): Worker {
25  return new Worker('engine_bundle.js');
26}
27
28// Take the warm engine and start creating a new WASM engine in the background
29// for the next call.
30export function createWasmEngine(id: string): Worker {
31  if (warmWorker === null) {
32    throw new Error('warmupWasmEngine() not called');
33  }
34  if (activeWorkers.has(id)) {
35    throw new Error(`Duplicate worker ID ${id}`);
36  }
37  const activeWorker = warmWorker;
38  warmWorker = createWorker();
39  activeWorkers.set(id, activeWorker);
40  return activeWorker;
41}
42
43export function destroyWasmEngine(id: string) {
44  if (!activeWorkers.has(id)) {
45    throw new Error(`Cannot find worker ID ${id}`);
46  }
47  activeWorkers.get(id)!.terminate();
48  activeWorkers.delete(id);
49}
50
51/**
52 * It's quite slow to compile WASM and (in Chrome) this happens every time
53 * a worker thread attempts to load a WASM module since there is no way to
54 * cache the compiled code currently. To mitigate this we can always keep a
55 * WASM backend 'ready to go' just waiting to be provided with a trace file.
56 * warmupWasmEngineWorker (together with getWasmEngineWorker)
57 * implement this behaviour.
58 */
59export function warmupWasmEngine(): void {
60  if (warmWorker !== null) {
61    throw new Error('warmupWasmEngine() already called');
62  }
63  warmWorker = createWorker();
64}
65
66interface PendingRequest {
67  id: number;
68  respHandler: Deferred<Uint8Array>;
69}
70
71/**
72 * This implementation of Engine uses a WASM backend hosted in a separate
73 * worker thread.
74 */
75export class WasmEngineProxy extends Engine {
76  readonly id: string;
77  private readonly worker: Worker;
78  private pendingRequests = new Array<PendingRequest>();
79  private nextRequestId = 0;
80
81  constructor(id: string, worker: Worker, loadingTracker?: LoadingTracker) {
82    super(loadingTracker);
83    this.id = id;
84    this.worker = worker;
85    this.worker.onmessage = this.onMessage.bind(this);
86  }
87
88  async parse(reqData: Uint8Array): Promise<void> {
89    // We don't care about the response data (the method is actually a void). We
90    // just want to linearize and wait for the call to have been completed on
91    // the worker.
92    await this.queueRequest('trace_processor_parse', reqData);
93  }
94
95  async notifyEof(): Promise<void> {
96    // We don't care about the response data (the method is actually a void). We
97    // just want to linearize and wait for the call to have been completed on
98    // the worker.
99    await this.queueRequest('trace_processor_notify_eof', new Uint8Array());
100  }
101
102  restoreInitialTables(): Promise<void> {
103    // We should never get here, restoreInitialTables() should be called only
104    // when using the HttpRpcEngine.
105    throw new Error('restoreInitialTables() not supported by the WASM engine');
106  }
107
108  rawQuery(rawQueryArgs: Uint8Array): Promise<Uint8Array> {
109    return this.queueRequest('trace_processor_raw_query', rawQueryArgs);
110  }
111
112  rawComputeMetric(rawComputeMetric: Uint8Array): Promise<Uint8Array> {
113    return this.queueRequest(
114        'trace_processor_compute_metric', rawComputeMetric);
115  }
116
117  async enableMetatrace(): Promise<void> {
118    await this.queueRequest(
119        'trace_processor_enable_metatrace', new Uint8Array());
120  }
121
122  disableAndReadMetatrace(): Promise<Uint8Array> {
123    return this.queueRequest(
124        'trace_processor_disable_and_read_metatrace', new Uint8Array());
125  }
126
127  // Enqueues a request to the worker queue via postMessage(). The returned
128  // promised will be resolved once the worker replies to the postMessage()
129  // with the paylad of the response, a proto-encoded object which wraps the
130  // method return value (e.g., RawQueryResult for SQL query results).
131  private queueRequest(methodName: string, reqData: Uint8Array):
132      Deferred<Uint8Array> {
133    const respHandler = defer<Uint8Array>();
134    const id = this.nextRequestId++;
135    const request: WasmBridgeRequest = {id, methodName, data: reqData};
136    this.pendingRequests.push({id, respHandler});
137    this.worker.postMessage(request);
138    return respHandler;
139  }
140
141  onMessage(m: MessageEvent) {
142    const response = m.data as WasmBridgeResponse;
143    assertTrue(this.pendingRequests.length > 0);
144    const request = this.pendingRequests.shift()!;
145
146    // Requests should be executed and ACKed by the worker in the same order
147    // they came in.
148    assertTrue(request.id === response.id);
149
150    // If the Wasm call fails (e.g. hits a PERFETTO_CHECK) it will throw an
151    // error in wasm_bridge.ts and show the crash dialog. In no case we can
152    // gracefully handle a Wasm crash, so we fail fast there rather than
153    // propagating the error here rejecting the promise.
154    request.respHandler.resolve(response.data);
155  }
156}
157