blob: 4ba65da36c317b495510651202f6e51067f3b739 [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {defer, Deferred} from '../base/deferred';
import {assertExists, assertTrue} from '../base/logging';
import {exists} from '../base/utils';
const SLICE_SIZE = 32 * 1024 * 1024;
// The object returned by TraceStream.readChunk() promise.
export interface TraceChunk {
data: Uint8Array;
eof: boolean;
bytesRead: number;
bytesTotal: number;
}
// Base interface for loading trace data in chunks.
// The caller has to call readChunk() until TraceChunk.eof == true.
export interface TraceStream {
readChunk(): Promise<TraceChunk>;
}
// Loads a trace from a File object. For the "open file" use case.
export class TraceFileStream implements TraceStream {
private traceFile: Blob;
private reader: FileReader;
private pendingRead?: Deferred<TraceChunk>;
private bytesRead = 0;
constructor(traceFile: Blob) {
this.traceFile = traceFile;
this.reader = new FileReader();
this.reader.onloadend = () => this.onLoad();
}
private onLoad() {
const pendingRead = assertExists(this.pendingRead);
this.pendingRead = undefined;
if (this.reader.error) {
pendingRead.reject(this.reader.error);
return;
}
const res = assertExists(this.reader.result) as ArrayBuffer;
this.bytesRead += res.byteLength;
pendingRead.resolve({
data: new Uint8Array(res),
eof: this.bytesRead >= this.traceFile.size,
bytesRead: this.bytesRead,
bytesTotal: this.traceFile.size,
});
}
readChunk(): Promise<TraceChunk> {
const sliceEnd = Math.min(this.bytesRead + SLICE_SIZE, this.traceFile.size);
const slice = this.traceFile.slice(this.bytesRead, sliceEnd);
this.pendingRead = defer<TraceChunk>();
this.reader.readAsArrayBuffer(slice);
return this.pendingRead;
}
}
// Loads a trace from an ArrayBuffer. For the window.open() + postMessage
// use-case, used by other dashboards (see post_message_handler.ts).
export class TraceBufferStream implements TraceStream {
private traceBuf: ArrayBuffer;
private bytesRead = 0;
constructor(traceBuf: ArrayBuffer) {
this.traceBuf = traceBuf;
}
readChunk(): Promise<TraceChunk> {
assertTrue(this.bytesRead <= this.traceBuf.byteLength);
const len = Math.min(SLICE_SIZE, this.traceBuf.byteLength - this.bytesRead);
const data = new Uint8Array(this.traceBuf, this.bytesRead, len);
this.bytesRead += len;
return Promise.resolve({
data,
eof: this.bytesRead >= this.traceBuf.byteLength,
bytesRead: this.bytesRead,
bytesTotal: this.traceBuf.byteLength,
});
}
}
// Loads a stream from a URL via fetch(). For the permalink (?s=UUID) and
// open url (?url=http://...) cases.
export class TraceHttpStream implements TraceStream {
private bytesRead = 0;
private bytesTotal = 0;
private uri: string;
private httpStream?: ReadableStreamDefaultReader<Uint8Array>;
constructor(uri: string) {
assertTrue(uri.startsWith('http://') || uri.startsWith('https://'));
this.uri = uri;
}
async readChunk(): Promise<TraceChunk> {
// Initialize the fetch() job on the first read request.
if (this.httpStream === undefined) {
const response = await fetch(this.uri);
if (response.status !== 200) {
throw new Error(`HTTP ${response.status} - ${response.statusText}`);
}
const len = response.headers.get('Content-Length');
this.bytesTotal = exists(len) ? Number.parseInt(len, 10) : 0;
this.httpStream = response.body!.getReader();
}
let eof = false;
let bytesRead = 0;
const chunks = [];
// httpStream can return very small chunks which can slow down
// TraceProcessor. Here we accumulate chunks until we get at least 32mb
// or hit EOF.
while (!eof && bytesRead < 32 * 1024 * 1024) {
const res = await this.httpStream.read();
if (res.value) {
chunks.push(res.value);
bytesRead += res.value.length;
}
eof = res.done;
}
let data;
if (chunks.length === 1) {
data = chunks[0];
} else {
// Stitch all the chunks into one big array:
data = new Uint8Array(bytesRead);
let offset = 0;
for (const chunk of chunks) {
data.set(chunk, offset);
offset += chunk.length;
}
}
this.bytesRead += data.length;
return {
data,
eof,
bytesRead: this.bytesRead,
bytesTotal: this.bytesTotal,
};
}
}