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} from '../base/deferred';
16import {assertExists} from '../base/logging';
17import {Actions} from '../common/actions';
18import {TraceSource} from '../common/state';
19import * as trace_to_text from '../gen/trace_to_text';
20
21import {globals} from './globals';
22
23type Format = 'json'|'systrace';
24
25export function ConvertTrace(
26    trace: Blob, format: Format, truncate?: 'start'|'end') {
27  const outPath = '/trace.json';
28  const args: string[] = [format];
29  if (truncate !== undefined) {
30    args.push('--truncate', truncate);
31  }
32  args.push('/fs/trace.proto', outPath);
33  runTraceconv(trace, args).then(module => {
34    const fsNode = module.FS.lookupPath(outPath).node;
35    const data = fsNode.contents.buffer;
36    const size = fsNode.usedBytes;
37    globals.publish('LegacyTrace', {data, size}, /*transfer=*/[data]);
38    module.FS.unlink(outPath);
39  });
40}
41
42export function ConvertTraceToPprof(
43    pid: number, src: TraceSource, ts1: number, ts2?: number) {
44  const timestamps = `${ts1}${ts2 === undefined ? '' : `,${ts2}`}`;
45  const args = [
46    'profile',
47    `--pid`,
48    `${pid}`,
49    `--timestamps`,
50    timestamps,
51    '/fs/trace.proto'
52  ];
53  generateBlob(src).then(traceBlob => {
54    runTraceconv(traceBlob, args).then(module => {
55      const heapDirName =
56          Object.keys(module.FS.lookupPath('/tmp/').node.contents)[0];
57      const heapDirContents =
58          module.FS.lookupPath(`/tmp/${heapDirName}`).node.contents;
59      const heapDumpFiles = Object.keys(heapDirContents);
60      let fileNum = 0;
61      heapDumpFiles.forEach(heapDump => {
62        const fileContents =
63            module.FS.lookupPath(`/tmp/${heapDirName}/${heapDump}`)
64                .node.contents;
65        fileNum++;
66        const fileName = `/heap_dump.${fileNum}.${pid}.pb`;
67        downloadFile(new Blob([fileContents]), fileName);
68      });
69    });
70  });
71}
72
73async function runTraceconv(trace: Blob, args: string[]) {
74  const deferredRuntimeInitialized = defer<void>();
75  const module = trace_to_text({
76    noInitialRun: true,
77    locateFile: (s: string) => s,
78    print: updateStatus,
79    printErr: updateStatus,
80    onRuntimeInitialized: () => deferredRuntimeInitialized.resolve()
81  });
82  await deferredRuntimeInitialized;
83  module.FS.mkdir('/fs');
84  module.FS.mount(
85      assertExists(module.FS.filesystems.WORKERFS),
86      {blobs: [{name: 'trace.proto', data: trace}]},
87      '/fs');
88  updateStatus('Converting trace');
89  module.callMain(args);
90  updateStatus('Trace conversion completed');
91  return module;
92}
93
94async function generateBlob(src: TraceSource) {
95  let blob: Blob = new Blob();
96  if (src.type === 'URL') {
97    const resp = await fetch(src.url);
98    if (resp.status !== 200) {
99      throw new Error(`fetch() failed with HTTP error ${resp.status}`);
100    }
101    blob = await resp.blob();
102  } else if (src.type === 'ARRAY_BUFFER') {
103    blob = new Blob([new Uint8Array(src.buffer, 0, src.buffer.byteLength)]);
104  } else if (src.type === 'FILE') {
105    blob = src.file;
106  } else {
107    throw new Error(`Conversion not supported for ${JSON.stringify(src)}`);
108  }
109  return blob;
110}
111
112function downloadFile(file: Blob, name: string) {
113  globals.publish('FileDownload', {file, name});
114}
115
116function updateStatus(msg: {}) {
117  console.log(msg);
118  globals.dispatch(Actions.updateStatus({
119    msg: msg.toString(),
120    timestamp: Date.now() / 1000,
121  }));
122}
123