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