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