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