1// Copyright (C) 2019 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, Deferred} from '../base/deferred'; 16import {assertExists, assertTrue} from '../base/logging'; 17 18const SLICE_SIZE = 32 * 1024 * 1024; 19 20// The object returned by TraceStream.readChunk() promise. 21export interface TraceChunk { 22 data: Uint8Array; 23 eof: boolean; 24 bytesRead: number; 25 bytesTotal: number; 26} 27 28// Base interface for loading trace data in chunks. 29// The caller has to call readChunk() until TraceChunk.eof == true. 30export interface TraceStream { 31 readChunk(): Promise<TraceChunk>; 32} 33 34// Loads a trace from a File object. For the "open file" use case. 35export class TraceFileStream implements TraceStream { 36 private traceFile: Blob; 37 private reader: FileReader; 38 private pendingRead?: Deferred<TraceChunk>; 39 private bytesRead = 0; 40 41 constructor(traceFile: Blob) { 42 this.traceFile = traceFile; 43 this.reader = new FileReader(); 44 this.reader.onloadend = () => this.onLoad(); 45 } 46 47 onLoad() { 48 const res = assertExists(this.reader.result) as ArrayBuffer; 49 const pendingRead = assertExists(this.pendingRead); 50 this.pendingRead = undefined; 51 if (this.reader.error) { 52 pendingRead.reject(this.reader.error); 53 return; 54 } 55 this.bytesRead += res.byteLength; 56 pendingRead.resolve({ 57 data: new Uint8Array(res), 58 eof: this.bytesRead >= this.traceFile.size, 59 bytesRead: this.bytesRead, 60 bytesTotal: this.traceFile.size, 61 }); 62 } 63 64 readChunk(): Promise<TraceChunk> { 65 const sliceEnd = Math.min(this.bytesRead + SLICE_SIZE, this.traceFile.size); 66 const slice = this.traceFile.slice(this.bytesRead, sliceEnd); 67 this.pendingRead = defer<TraceChunk>(); 68 this.reader.readAsArrayBuffer(slice); 69 return this.pendingRead; 70 } 71} 72 73// Loads a trace from an ArrayBuffer. For the window.open() + postMessage 74// use-case, used by other dashboards (see post_message_handler.ts). 75export class TraceBufferStream implements TraceStream { 76 private traceBuf: ArrayBuffer; 77 private bytesRead = 0; 78 79 constructor(traceBuf: ArrayBuffer) { 80 this.traceBuf = traceBuf; 81 } 82 83 readChunk(): Promise<TraceChunk> { 84 assertTrue(this.bytesRead <= this.traceBuf.byteLength); 85 const len = Math.min(SLICE_SIZE, this.traceBuf.byteLength - this.bytesRead); 86 const data = new Uint8Array(this.traceBuf, this.bytesRead, len); 87 this.bytesRead += len; 88 return Promise.resolve({ 89 data, 90 eof: this.bytesRead >= this.traceBuf.byteLength, 91 bytesRead: this.bytesRead, 92 bytesTotal: this.traceBuf.byteLength, 93 }); 94 } 95} 96 97// Loads a stream from a URL via fetch(). For the permalink (?s=UUID) and 98// open url (?url=http://...) cases. 99export class TraceHttpStream implements TraceStream { 100 private bytesRead = 0; 101 private bytesTotal = 0; 102 private uri: string; 103 private httpStream?: ReadableStreamReader; 104 105 constructor(uri: string) { 106 assertTrue(uri.startsWith('http://') || uri.startsWith('https://')); 107 this.uri = uri; 108 } 109 110 async readChunk(): Promise<TraceChunk> { 111 // Initialize the fetch() job on the first read request. 112 if (this.httpStream === undefined) { 113 const response = await fetch(this.uri); 114 if (response.status !== 200) { 115 throw new Error(`HTTP ${response.status} - ${response.statusText}`); 116 } 117 const len = response.headers.get('Content-Length'); 118 this.bytesTotal = len ? Number.parseInt(len, 10) : 0; 119 // tslint:disable-next-line no-any 120 this.httpStream = (response.body as any).getReader(); 121 } 122 123 let eof = false; 124 let bytesRead = 0; 125 const chunks = []; 126 127 // httpStream can return very small chunks which can slow down 128 // TraceProcessor. Here we accumulate chunks until we get at least 32mb 129 // or hit EOF. 130 while (!eof && bytesRead < 32 * 1024 * 1024) { 131 const res = (await this.httpStream!.read()) as 132 {value?: Uint8Array, done: boolean}; 133 if (res.value) { 134 chunks.push(res.value); 135 bytesRead += res.value.length; 136 } 137 eof = res.done; 138 } 139 140 let data; 141 if (chunks.length === 1) { 142 data = chunks[0]; 143 } else { 144 // Stitch all the chunks into one big array: 145 data = new Uint8Array(bytesRead); 146 let offset = 0; 147 for (const chunk of chunks) { 148 data.set(chunk, offset); 149 offset += chunk.length; 150 } 151 } 152 153 this.bytesRead += data.length; 154 155 return { 156 data, 157 eof, 158 bytesRead: this.bytesRead, 159 bytesTotal: this.bytesTotal, 160 }; 161 } 162} 163