• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2021 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} from '../base/deferred';
16import {
17  addErrorHandler,
18  assertExists,
19  ErrorDetails,
20  reportError,
21} from '../base/logging';
22import {time} from '../base/time';
23import {
24  ConversionJobName,
25  ConversionJobStatus,
26} from '../common/conversion_jobs';
27import traceconv from '../gen/traceconv';
28
29const selfWorker = self as {} as Worker;
30
31// TODO(hjd): The trace ends up being copied too many times due to how
32// blob works. We should reduce the number of copies.
33
34type Format = 'json' | 'systrace';
35type Args =
36  | ConvertTraceAndDownloadArgs
37  | ConvertTraceAndOpenInLegacyArgs
38  | ConvertTraceToPprofArgs;
39
40function updateStatus(status: string) {
41  selfWorker.postMessage({
42    kind: 'updateStatus',
43    status,
44  });
45}
46
47function updateJobStatus(name: ConversionJobName, status: ConversionJobStatus) {
48  selfWorker.postMessage({
49    kind: 'updateJobStatus',
50    name,
51    status,
52  });
53}
54
55function downloadFile(buffer: Uint8Array, name: string) {
56  selfWorker.postMessage(
57    {
58      kind: 'downloadFile',
59      buffer,
60      name,
61    },
62    [buffer.buffer],
63  );
64}
65
66function openTraceInLegacy(buffer: Uint8Array) {
67  selfWorker.postMessage({
68    kind: 'openTraceInLegacy',
69    buffer,
70  });
71}
72
73function forwardError(error: ErrorDetails) {
74  selfWorker.postMessage({
75    kind: 'error',
76    error,
77  });
78}
79
80function fsNodeToBuffer(fsNode: traceconv.FileSystemNode): Uint8Array {
81  const fileSize = assertExists(fsNode.usedBytes);
82  return new Uint8Array(fsNode.contents.buffer, 0, fileSize);
83}
84
85async function runTraceconv(trace: Blob, args: string[]) {
86  const deferredRuntimeInitialized = defer<void>();
87  const module = traceconv({
88    noInitialRun: true,
89    locateFile: (s: string) => s,
90    print: updateStatus,
91    printErr: updateStatus,
92    onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(),
93  });
94  await deferredRuntimeInitialized;
95  module.FS.mkdir('/fs');
96  module.FS.mount(
97    assertExists(module.FS.filesystems.WORKERFS),
98    {blobs: [{name: 'trace.proto', data: trace}]},
99    '/fs',
100  );
101  updateStatus('Converting trace');
102  module.callMain(args);
103  updateStatus('Trace conversion completed');
104  return module;
105}
106
107interface ConvertTraceAndDownloadArgs {
108  kind: 'ConvertTraceAndDownload';
109  trace: Blob;
110  format: Format;
111  truncate?: 'start' | 'end';
112}
113
114function isConvertTraceAndDownload(
115  msg: Args,
116): msg is ConvertTraceAndDownloadArgs {
117  if (msg.kind !== 'ConvertTraceAndDownload') {
118    return false;
119  }
120  if (msg.trace === undefined) {
121    throw new Error('ConvertTraceAndDownloadArgs missing trace');
122  }
123  if (msg.format !== 'json' && msg.format !== 'systrace') {
124    throw new Error('ConvertTraceAndDownloadArgs has bad format');
125  }
126  return true;
127}
128
129async function ConvertTraceAndDownload(
130  trace: Blob,
131  format: Format,
132  truncate?: 'start' | 'end',
133): Promise<void> {
134  const jobName = format === 'json' ? 'convert_json' : 'convert_systrace';
135  updateJobStatus(jobName, ConversionJobStatus.InProgress);
136  const outPath = '/trace.json';
137  const args: string[] = [format];
138  if (truncate !== undefined) {
139    args.push('--truncate', truncate);
140  }
141  args.push('/fs/trace.proto', outPath);
142  try {
143    const module = await runTraceconv(trace, args);
144    const fsNode = module.FS.lookupPath(outPath).node;
145    downloadFile(fsNodeToBuffer(fsNode), `trace.${format}`);
146    module.FS.unlink(outPath);
147  } finally {
148    updateJobStatus(jobName, ConversionJobStatus.NotRunning);
149  }
150}
151
152interface ConvertTraceAndOpenInLegacyArgs {
153  kind: 'ConvertTraceAndOpenInLegacy';
154  trace: Blob;
155  truncate?: 'start' | 'end';
156}
157
158function isConvertTraceAndOpenInLegacy(
159  msg: Args,
160): msg is ConvertTraceAndOpenInLegacyArgs {
161  if (msg.kind !== 'ConvertTraceAndOpenInLegacy') {
162    return false;
163  }
164  return true;
165}
166
167async function ConvertTraceAndOpenInLegacy(
168  trace: Blob,
169  truncate?: 'start' | 'end',
170) {
171  const jobName = 'open_in_legacy';
172  updateJobStatus(jobName, ConversionJobStatus.InProgress);
173  const outPath = '/trace.json';
174  const args: string[] = ['json'];
175  if (truncate !== undefined) {
176    args.push('--truncate', truncate);
177  }
178  args.push('/fs/trace.proto', outPath);
179  try {
180    const module = await runTraceconv(trace, args);
181    const fsNode = module.FS.lookupPath(outPath).node;
182    const data = fsNode.contents.buffer;
183    const size = fsNode.usedBytes;
184    const buffer = new Uint8Array(data, 0, size);
185    openTraceInLegacy(buffer);
186    module.FS.unlink(outPath);
187  } finally {
188    updateJobStatus(jobName, ConversionJobStatus.NotRunning);
189  }
190}
191
192interface ConvertTraceToPprofArgs {
193  kind: 'ConvertTraceToPprof';
194  trace: Blob;
195  pid: number;
196  ts: time;
197}
198
199function isConvertTraceToPprof(msg: Args): msg is ConvertTraceToPprofArgs {
200  if (msg.kind !== 'ConvertTraceToPprof') {
201    return false;
202  }
203  return true;
204}
205
206async function ConvertTraceToPprof(trace: Blob, pid: number, ts: time) {
207  const jobName = 'convert_pprof';
208  updateJobStatus(jobName, ConversionJobStatus.InProgress);
209  const args = [
210    'profile',
211    `--pid`,
212    `${pid}`,
213    `--timestamps`,
214    `${ts}`,
215    '/fs/trace.proto',
216  ];
217
218  try {
219    const module = await runTraceconv(trace, args);
220    const heapDirName = Object.keys(
221      module.FS.lookupPath('/tmp/').node.contents,
222    )[0];
223    const heapDirContents = module.FS.lookupPath(`/tmp/${heapDirName}`).node
224      .contents;
225    const heapDumpFiles = Object.keys(heapDirContents);
226    for (let i = 0; i < heapDumpFiles.length; ++i) {
227      const heapDump = heapDumpFiles[i];
228      const fileNode = module.FS.lookupPath(
229        `/tmp/${heapDirName}/${heapDump}`,
230      ).node;
231      const fileName = `/heap_dump.${i}.${pid}.pb`;
232      downloadFile(fsNodeToBuffer(fileNode), fileName);
233    }
234  } finally {
235    updateJobStatus(jobName, ConversionJobStatus.NotRunning);
236  }
237}
238
239selfWorker.onmessage = (msg: MessageEvent) => {
240  self.addEventListener('error', (e) => reportError(e));
241  self.addEventListener('unhandledrejection', (e) => reportError(e));
242  addErrorHandler((error: ErrorDetails) => forwardError(error));
243  const args = msg.data as Args;
244  if (isConvertTraceAndDownload(args)) {
245    ConvertTraceAndDownload(args.trace, args.format, args.truncate);
246  } else if (isConvertTraceAndOpenInLegacy(args)) {
247    ConvertTraceAndOpenInLegacy(args.trace, args.truncate);
248  } else if (isConvertTraceToPprof(args)) {
249    ConvertTraceToPprof(args.trace, args.pid, args.ts);
250  } else {
251    throw new Error(`Unknown method call ${JSON.stringify(args)}`);
252  }
253};
254