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