xref: /aosp_15_r20/external/perfetto/ui/src/traceconv/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2021 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport {defer} from '../base/deferred';
16*6dbdd20aSAndroid Build Coastguard Workerimport {
17*6dbdd20aSAndroid Build Coastguard Worker  addErrorHandler,
18*6dbdd20aSAndroid Build Coastguard Worker  assertExists,
19*6dbdd20aSAndroid Build Coastguard Worker  ErrorDetails,
20*6dbdd20aSAndroid Build Coastguard Worker  reportError,
21*6dbdd20aSAndroid Build Coastguard Worker} from '../base/logging';
22*6dbdd20aSAndroid Build Coastguard Workerimport {time} from '../base/time';
23*6dbdd20aSAndroid Build Coastguard Workerimport traceconv from '../gen/traceconv';
24*6dbdd20aSAndroid Build Coastguard Worker
25*6dbdd20aSAndroid Build Coastguard Workerconst selfWorker = self as {} as Worker;
26*6dbdd20aSAndroid Build Coastguard Worker
27*6dbdd20aSAndroid Build Coastguard Worker// TODO(hjd): The trace ends up being copied too many times due to how
28*6dbdd20aSAndroid Build Coastguard Worker// blob works. We should reduce the number of copies.
29*6dbdd20aSAndroid Build Coastguard Worker
30*6dbdd20aSAndroid Build Coastguard Workertype Format = 'json' | 'systrace';
31*6dbdd20aSAndroid Build Coastguard Workertype Args =
32*6dbdd20aSAndroid Build Coastguard Worker  | ConvertTraceAndDownloadArgs
33*6dbdd20aSAndroid Build Coastguard Worker  | ConvertTraceAndOpenInLegacyArgs
34*6dbdd20aSAndroid Build Coastguard Worker  | ConvertTraceToPprofArgs;
35*6dbdd20aSAndroid Build Coastguard Worker
36*6dbdd20aSAndroid Build Coastguard Workerfunction updateStatus(status: string) {
37*6dbdd20aSAndroid Build Coastguard Worker  selfWorker.postMessage({
38*6dbdd20aSAndroid Build Coastguard Worker    kind: 'updateStatus',
39*6dbdd20aSAndroid Build Coastguard Worker    status,
40*6dbdd20aSAndroid Build Coastguard Worker  });
41*6dbdd20aSAndroid Build Coastguard Worker}
42*6dbdd20aSAndroid Build Coastguard Worker
43*6dbdd20aSAndroid Build Coastguard Workerfunction notifyJobCompleted() {
44*6dbdd20aSAndroid Build Coastguard Worker  selfWorker.postMessage({kind: 'jobCompleted'});
45*6dbdd20aSAndroid Build Coastguard Worker}
46*6dbdd20aSAndroid Build Coastguard Worker
47*6dbdd20aSAndroid Build Coastguard Workerfunction downloadFile(buffer: Uint8Array, name: string) {
48*6dbdd20aSAndroid Build Coastguard Worker  selfWorker.postMessage(
49*6dbdd20aSAndroid Build Coastguard Worker    {
50*6dbdd20aSAndroid Build Coastguard Worker      kind: 'downloadFile',
51*6dbdd20aSAndroid Build Coastguard Worker      buffer,
52*6dbdd20aSAndroid Build Coastguard Worker      name,
53*6dbdd20aSAndroid Build Coastguard Worker    },
54*6dbdd20aSAndroid Build Coastguard Worker    [buffer.buffer],
55*6dbdd20aSAndroid Build Coastguard Worker  );
56*6dbdd20aSAndroid Build Coastguard Worker}
57*6dbdd20aSAndroid Build Coastguard Worker
58*6dbdd20aSAndroid Build Coastguard Workerfunction openTraceInLegacy(buffer: Uint8Array) {
59*6dbdd20aSAndroid Build Coastguard Worker  selfWorker.postMessage({
60*6dbdd20aSAndroid Build Coastguard Worker    kind: 'openTraceInLegacy',
61*6dbdd20aSAndroid Build Coastguard Worker    buffer,
62*6dbdd20aSAndroid Build Coastguard Worker  });
63*6dbdd20aSAndroid Build Coastguard Worker}
64*6dbdd20aSAndroid Build Coastguard Worker
65*6dbdd20aSAndroid Build Coastguard Workerfunction forwardError(error: ErrorDetails) {
66*6dbdd20aSAndroid Build Coastguard Worker  selfWorker.postMessage({
67*6dbdd20aSAndroid Build Coastguard Worker    kind: 'error',
68*6dbdd20aSAndroid Build Coastguard Worker    error,
69*6dbdd20aSAndroid Build Coastguard Worker  });
70*6dbdd20aSAndroid Build Coastguard Worker}
71*6dbdd20aSAndroid Build Coastguard Worker
72*6dbdd20aSAndroid Build Coastguard Workerfunction fsNodeToBuffer(fsNode: traceconv.FileSystemNode): Uint8Array {
73*6dbdd20aSAndroid Build Coastguard Worker  const fileSize = assertExists(fsNode.usedBytes);
74*6dbdd20aSAndroid Build Coastguard Worker  return new Uint8Array(fsNode.contents.buffer, 0, fileSize);
75*6dbdd20aSAndroid Build Coastguard Worker}
76*6dbdd20aSAndroid Build Coastguard Worker
77*6dbdd20aSAndroid Build Coastguard Workerasync function runTraceconv(trace: Blob, args: string[]) {
78*6dbdd20aSAndroid Build Coastguard Worker  const deferredRuntimeInitialized = defer<void>();
79*6dbdd20aSAndroid Build Coastguard Worker  const module = traceconv({
80*6dbdd20aSAndroid Build Coastguard Worker    noInitialRun: true,
81*6dbdd20aSAndroid Build Coastguard Worker    locateFile: (s: string) => s,
82*6dbdd20aSAndroid Build Coastguard Worker    print: updateStatus,
83*6dbdd20aSAndroid Build Coastguard Worker    printErr: updateStatus,
84*6dbdd20aSAndroid Build Coastguard Worker    onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(),
85*6dbdd20aSAndroid Build Coastguard Worker  });
86*6dbdd20aSAndroid Build Coastguard Worker  await deferredRuntimeInitialized;
87*6dbdd20aSAndroid Build Coastguard Worker  module.FS.mkdir('/fs');
88*6dbdd20aSAndroid Build Coastguard Worker  module.FS.mount(
89*6dbdd20aSAndroid Build Coastguard Worker    assertExists(module.FS.filesystems.WORKERFS),
90*6dbdd20aSAndroid Build Coastguard Worker    {blobs: [{name: 'trace.proto', data: trace}]},
91*6dbdd20aSAndroid Build Coastguard Worker    '/fs',
92*6dbdd20aSAndroid Build Coastguard Worker  );
93*6dbdd20aSAndroid Build Coastguard Worker  updateStatus('Converting trace');
94*6dbdd20aSAndroid Build Coastguard Worker  module.callMain(args);
95*6dbdd20aSAndroid Build Coastguard Worker  updateStatus('Trace conversion completed');
96*6dbdd20aSAndroid Build Coastguard Worker  return module;
97*6dbdd20aSAndroid Build Coastguard Worker}
98*6dbdd20aSAndroid Build Coastguard Worker
99*6dbdd20aSAndroid Build Coastguard Workerinterface ConvertTraceAndDownloadArgs {
100*6dbdd20aSAndroid Build Coastguard Worker  kind: 'ConvertTraceAndDownload';
101*6dbdd20aSAndroid Build Coastguard Worker  trace: Blob;
102*6dbdd20aSAndroid Build Coastguard Worker  format: Format;
103*6dbdd20aSAndroid Build Coastguard Worker  truncate?: 'start' | 'end';
104*6dbdd20aSAndroid Build Coastguard Worker}
105*6dbdd20aSAndroid Build Coastguard Worker
106*6dbdd20aSAndroid Build Coastguard Workerfunction isConvertTraceAndDownload(
107*6dbdd20aSAndroid Build Coastguard Worker  msg: Args,
108*6dbdd20aSAndroid Build Coastguard Worker): msg is ConvertTraceAndDownloadArgs {
109*6dbdd20aSAndroid Build Coastguard Worker  if (msg.kind !== 'ConvertTraceAndDownload') {
110*6dbdd20aSAndroid Build Coastguard Worker    return false;
111*6dbdd20aSAndroid Build Coastguard Worker  }
112*6dbdd20aSAndroid Build Coastguard Worker  if (msg.trace === undefined) {
113*6dbdd20aSAndroid Build Coastguard Worker    throw new Error('ConvertTraceAndDownloadArgs missing trace');
114*6dbdd20aSAndroid Build Coastguard Worker  }
115*6dbdd20aSAndroid Build Coastguard Worker  if (msg.format !== 'json' && msg.format !== 'systrace') {
116*6dbdd20aSAndroid Build Coastguard Worker    throw new Error('ConvertTraceAndDownloadArgs has bad format');
117*6dbdd20aSAndroid Build Coastguard Worker  }
118*6dbdd20aSAndroid Build Coastguard Worker  return true;
119*6dbdd20aSAndroid Build Coastguard Worker}
120*6dbdd20aSAndroid Build Coastguard Worker
121*6dbdd20aSAndroid Build Coastguard Workerasync function ConvertTraceAndDownload(
122*6dbdd20aSAndroid Build Coastguard Worker  trace: Blob,
123*6dbdd20aSAndroid Build Coastguard Worker  format: Format,
124*6dbdd20aSAndroid Build Coastguard Worker  truncate?: 'start' | 'end',
125*6dbdd20aSAndroid Build Coastguard Worker): Promise<void> {
126*6dbdd20aSAndroid Build Coastguard Worker  const outPath = '/trace.json';
127*6dbdd20aSAndroid Build Coastguard Worker  const args: string[] = [format];
128*6dbdd20aSAndroid Build Coastguard Worker  if (truncate !== undefined) {
129*6dbdd20aSAndroid Build Coastguard Worker    args.push('--truncate', truncate);
130*6dbdd20aSAndroid Build Coastguard Worker  }
131*6dbdd20aSAndroid Build Coastguard Worker  args.push('/fs/trace.proto', outPath);
132*6dbdd20aSAndroid Build Coastguard Worker  try {
133*6dbdd20aSAndroid Build Coastguard Worker    const module = await runTraceconv(trace, args);
134*6dbdd20aSAndroid Build Coastguard Worker    const fsNode = module.FS.lookupPath(outPath).node;
135*6dbdd20aSAndroid Build Coastguard Worker    downloadFile(fsNodeToBuffer(fsNode), `trace.${format}`);
136*6dbdd20aSAndroid Build Coastguard Worker    module.FS.unlink(outPath);
137*6dbdd20aSAndroid Build Coastguard Worker  } finally {
138*6dbdd20aSAndroid Build Coastguard Worker    notifyJobCompleted();
139*6dbdd20aSAndroid Build Coastguard Worker  }
140*6dbdd20aSAndroid Build Coastguard Worker}
141*6dbdd20aSAndroid Build Coastguard Worker
142*6dbdd20aSAndroid Build Coastguard Workerinterface ConvertTraceAndOpenInLegacyArgs {
143*6dbdd20aSAndroid Build Coastguard Worker  kind: 'ConvertTraceAndOpenInLegacy';
144*6dbdd20aSAndroid Build Coastguard Worker  trace: Blob;
145*6dbdd20aSAndroid Build Coastguard Worker  truncate?: 'start' | 'end';
146*6dbdd20aSAndroid Build Coastguard Worker}
147*6dbdd20aSAndroid Build Coastguard Worker
148*6dbdd20aSAndroid Build Coastguard Workerfunction isConvertTraceAndOpenInLegacy(
149*6dbdd20aSAndroid Build Coastguard Worker  msg: Args,
150*6dbdd20aSAndroid Build Coastguard Worker): msg is ConvertTraceAndOpenInLegacyArgs {
151*6dbdd20aSAndroid Build Coastguard Worker  if (msg.kind !== 'ConvertTraceAndOpenInLegacy') {
152*6dbdd20aSAndroid Build Coastguard Worker    return false;
153*6dbdd20aSAndroid Build Coastguard Worker  }
154*6dbdd20aSAndroid Build Coastguard Worker  return true;
155*6dbdd20aSAndroid Build Coastguard Worker}
156*6dbdd20aSAndroid Build Coastguard Worker
157*6dbdd20aSAndroid Build Coastguard Workerasync function ConvertTraceAndOpenInLegacy(
158*6dbdd20aSAndroid Build Coastguard Worker  trace: Blob,
159*6dbdd20aSAndroid Build Coastguard Worker  truncate?: 'start' | 'end',
160*6dbdd20aSAndroid Build Coastguard Worker) {
161*6dbdd20aSAndroid Build Coastguard Worker  const outPath = '/trace.json';
162*6dbdd20aSAndroid Build Coastguard Worker  const args: string[] = ['json'];
163*6dbdd20aSAndroid Build Coastguard Worker  if (truncate !== undefined) {
164*6dbdd20aSAndroid Build Coastguard Worker    args.push('--truncate', truncate);
165*6dbdd20aSAndroid Build Coastguard Worker  }
166*6dbdd20aSAndroid Build Coastguard Worker  args.push('/fs/trace.proto', outPath);
167*6dbdd20aSAndroid Build Coastguard Worker  try {
168*6dbdd20aSAndroid Build Coastguard Worker    const module = await runTraceconv(trace, args);
169*6dbdd20aSAndroid Build Coastguard Worker    const fsNode = module.FS.lookupPath(outPath).node;
170*6dbdd20aSAndroid Build Coastguard Worker    const data = fsNode.contents.buffer;
171*6dbdd20aSAndroid Build Coastguard Worker    const size = fsNode.usedBytes;
172*6dbdd20aSAndroid Build Coastguard Worker    const buffer = new Uint8Array(data, 0, size);
173*6dbdd20aSAndroid Build Coastguard Worker    openTraceInLegacy(buffer);
174*6dbdd20aSAndroid Build Coastguard Worker    module.FS.unlink(outPath);
175*6dbdd20aSAndroid Build Coastguard Worker  } finally {
176*6dbdd20aSAndroid Build Coastguard Worker    notifyJobCompleted();
177*6dbdd20aSAndroid Build Coastguard Worker  }
178*6dbdd20aSAndroid Build Coastguard Worker}
179*6dbdd20aSAndroid Build Coastguard Worker
180*6dbdd20aSAndroid Build Coastguard Workerinterface ConvertTraceToPprofArgs {
181*6dbdd20aSAndroid Build Coastguard Worker  kind: 'ConvertTraceToPprof';
182*6dbdd20aSAndroid Build Coastguard Worker  trace: Blob;
183*6dbdd20aSAndroid Build Coastguard Worker  pid: number;
184*6dbdd20aSAndroid Build Coastguard Worker  ts: time;
185*6dbdd20aSAndroid Build Coastguard Worker}
186*6dbdd20aSAndroid Build Coastguard Worker
187*6dbdd20aSAndroid Build Coastguard Workerfunction isConvertTraceToPprof(msg: Args): msg is ConvertTraceToPprofArgs {
188*6dbdd20aSAndroid Build Coastguard Worker  if (msg.kind !== 'ConvertTraceToPprof') {
189*6dbdd20aSAndroid Build Coastguard Worker    return false;
190*6dbdd20aSAndroid Build Coastguard Worker  }
191*6dbdd20aSAndroid Build Coastguard Worker  return true;
192*6dbdd20aSAndroid Build Coastguard Worker}
193*6dbdd20aSAndroid Build Coastguard Worker
194*6dbdd20aSAndroid Build Coastguard Workerasync function ConvertTraceToPprof(trace: Blob, pid: number, ts: time) {
195*6dbdd20aSAndroid Build Coastguard Worker  const args = [
196*6dbdd20aSAndroid Build Coastguard Worker    'profile',
197*6dbdd20aSAndroid Build Coastguard Worker    `--pid`,
198*6dbdd20aSAndroid Build Coastguard Worker    `${pid}`,
199*6dbdd20aSAndroid Build Coastguard Worker    `--timestamps`,
200*6dbdd20aSAndroid Build Coastguard Worker    `${ts}`,
201*6dbdd20aSAndroid Build Coastguard Worker    '/fs/trace.proto',
202*6dbdd20aSAndroid Build Coastguard Worker  ];
203*6dbdd20aSAndroid Build Coastguard Worker
204*6dbdd20aSAndroid Build Coastguard Worker  try {
205*6dbdd20aSAndroid Build Coastguard Worker    const module = await runTraceconv(trace, args);
206*6dbdd20aSAndroid Build Coastguard Worker    const heapDirName = Object.keys(
207*6dbdd20aSAndroid Build Coastguard Worker      module.FS.lookupPath('/tmp/').node.contents,
208*6dbdd20aSAndroid Build Coastguard Worker    )[0];
209*6dbdd20aSAndroid Build Coastguard Worker    const heapDirContents = module.FS.lookupPath(`/tmp/${heapDirName}`).node
210*6dbdd20aSAndroid Build Coastguard Worker      .contents;
211*6dbdd20aSAndroid Build Coastguard Worker    const heapDumpFiles = Object.keys(heapDirContents);
212*6dbdd20aSAndroid Build Coastguard Worker    for (let i = 0; i < heapDumpFiles.length; ++i) {
213*6dbdd20aSAndroid Build Coastguard Worker      const heapDump = heapDumpFiles[i];
214*6dbdd20aSAndroid Build Coastguard Worker      const fileNode = module.FS.lookupPath(
215*6dbdd20aSAndroid Build Coastguard Worker        `/tmp/${heapDirName}/${heapDump}`,
216*6dbdd20aSAndroid Build Coastguard Worker      ).node;
217*6dbdd20aSAndroid Build Coastguard Worker      const fileName = `/heap_dump.${i}.${pid}.pb`;
218*6dbdd20aSAndroid Build Coastguard Worker      downloadFile(fsNodeToBuffer(fileNode), fileName);
219*6dbdd20aSAndroid Build Coastguard Worker    }
220*6dbdd20aSAndroid Build Coastguard Worker  } finally {
221*6dbdd20aSAndroid Build Coastguard Worker    notifyJobCompleted();
222*6dbdd20aSAndroid Build Coastguard Worker  }
223*6dbdd20aSAndroid Build Coastguard Worker}
224*6dbdd20aSAndroid Build Coastguard Worker
225*6dbdd20aSAndroid Build Coastguard WorkerselfWorker.onmessage = (msg: MessageEvent) => {
226*6dbdd20aSAndroid Build Coastguard Worker  self.addEventListener('error', (e) => reportError(e));
227*6dbdd20aSAndroid Build Coastguard Worker  self.addEventListener('unhandledrejection', (e) => reportError(e));
228*6dbdd20aSAndroid Build Coastguard Worker  addErrorHandler((error: ErrorDetails) => forwardError(error));
229*6dbdd20aSAndroid Build Coastguard Worker  const args = msg.data as Args;
230*6dbdd20aSAndroid Build Coastguard Worker  if (isConvertTraceAndDownload(args)) {
231*6dbdd20aSAndroid Build Coastguard Worker    ConvertTraceAndDownload(args.trace, args.format, args.truncate);
232*6dbdd20aSAndroid Build Coastguard Worker  } else if (isConvertTraceAndOpenInLegacy(args)) {
233*6dbdd20aSAndroid Build Coastguard Worker    ConvertTraceAndOpenInLegacy(args.trace, args.truncate);
234*6dbdd20aSAndroid Build Coastguard Worker  } else if (isConvertTraceToPprof(args)) {
235*6dbdd20aSAndroid Build Coastguard Worker    ConvertTraceToPprof(args.trace, args.pid, args.ts);
236*6dbdd20aSAndroid Build Coastguard Worker  } else {
237*6dbdd20aSAndroid Build Coastguard Worker    throw new Error(`Unknown method call ${JSON.stringify(args)}`);
238*6dbdd20aSAndroid Build Coastguard Worker  }
239*6dbdd20aSAndroid Build Coastguard Worker};
240