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