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 * as m from 'mithril'; 16import {inflate} from 'pako'; 17import {assertTrue} from '../base/logging'; 18import {globals} from './globals'; 19import {showModal} from './modal'; 20 21const CTRACE_HEADER = 'TRACE:\n'; 22 23async function isCtrace(file: File): Promise<boolean> { 24 const fileName = file.name.toLowerCase(); 25 26 if (fileName.endsWith('.ctrace')) { 27 return true; 28 } 29 30 // .ctrace files sometimes end with .txt. We can detect these via 31 // the presence of TRACE: near the top of the file. 32 if (fileName.endsWith('.txt')) { 33 const header = await readText(file.slice(0, 128)); 34 if (header.includes(CTRACE_HEADER)) { 35 return true; 36 } 37 } 38 39 return false; 40} 41 42function readText(blob: Blob): Promise<string> { 43 return new Promise((resolve, reject) => { 44 const reader = new FileReader(); 45 reader.onload = () => { 46 if (typeof reader.result === 'string') { 47 return resolve(reader.result); 48 } 49 }; 50 reader.onerror = err => { 51 reject(err); 52 }; 53 reader.readAsText(blob); 54 }); 55} 56 57export async function isLegacyTrace(file: File): Promise<boolean> { 58 const fileName = file.name.toLowerCase(); 59 if (fileName.endsWith('.json') || fileName.endsWith('.json.gz') || 60 fileName.endsWith('.zip') || fileName.endsWith('.html')) { 61 return true; 62 } 63 64 if (await isCtrace(file)) { 65 return true; 66 } 67 68 // Sometimes systrace formatted traces end with '.trace'. This is a 69 // little generic to assume all such traces are systrace format though 70 // so we read the beginning of the file and check to see if is has the 71 // systrace header (several comment lines): 72 if (fileName.endsWith('.trace')) { 73 const header = await readText(file.slice(0, 512)); 74 const lines = header.split('\n'); 75 let commentCount = 0; 76 for (const line of lines) { 77 if (line.startsWith('#')) { 78 commentCount++; 79 } 80 } 81 if (commentCount > 5) { 82 return true; 83 } 84 } 85 86 return false; 87} 88 89export async function openFileWithLegacyTraceViewer(file: File) { 90 const reader = new FileReader(); 91 reader.onload = () => { 92 if (reader.result instanceof ArrayBuffer) { 93 return openBufferWithLegacyTraceViewer( 94 file.name, reader.result, reader.result.byteLength); 95 } else { 96 const str = reader.result as string; 97 return openBufferWithLegacyTraceViewer(file.name, str, str.length); 98 } 99 }; 100 reader.onerror = err => { 101 console.error(err); 102 }; 103 if (file.name.endsWith('.gz') || file.name.endsWith('.zip') || 104 await isCtrace(file)) { 105 reader.readAsArrayBuffer(file); 106 } else { 107 reader.readAsText(file); 108 } 109} 110 111export function openBufferWithLegacyTraceViewer( 112 name: string, data: ArrayBuffer|string, size: number) { 113 if (data instanceof ArrayBuffer) { 114 assertTrue(size <= data.byteLength); 115 if (size !== data.byteLength) { 116 data = data.slice(0, size); 117 } 118 119 // Handle .ctrace files. 120 const enc = new TextDecoder('utf-8'); 121 const header = enc.decode(data.slice(0, 128)); 122 if (header.includes(CTRACE_HEADER)) { 123 const offset = header.indexOf(CTRACE_HEADER) + CTRACE_HEADER.length; 124 data = inflate(new Uint8Array(data.slice(offset)), {to: 'string'}); 125 } 126 } 127 128 // The location.pathname mangling is to make this code work also when hosted 129 // in a non-root sub-directory, for the case of CI artifacts. 130 const catapultUrl = globals.root + 'assets/catapult_trace_viewer.html'; 131 const newWin = window.open(catapultUrl) as Window; 132 if (newWin) { 133 // Popup succeedeed. 134 newWin.addEventListener('load', (e: Event) => { 135 const doc = (e.target as Document); 136 const ctl = doc.querySelector('x-profiling-view') as TraceViewerAPI; 137 ctl.setActiveTrace(name, data); 138 }); 139 return; 140 } 141 142 // Popup blocker detected. 143 showModal({ 144 title: 'Open trace in the legacy Catapult Trace Viewer', 145 content: m( 146 'div', 147 m('div', 'You are seeing this interstitial because popups are blocked'), 148 m('div', 'Enable popups to skip this dialog next time.')), 149 buttons: [{ 150 text: 'Open legacy UI', 151 primary: true, 152 id: 'open_legacy', 153 action: () => openBufferWithLegacyTraceViewer(name, data, size), 154 }], 155 }); 156} 157 158// TraceViewer method that we wire up to trigger the file load. 159interface TraceViewerAPI extends Element { 160 setActiveTrace(name: string, data: ArrayBuffer|string): void; 161} 162