• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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