1#!/bin/bash 2# Loading... <!-- 3# Copyright (C) 2017 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17cd $(dirname $0) 18python -m webbrowser -t "http://localhost:8000/$(basename $0)" 19python -m SimpleHTTPServer 20 21<<-EOF 22--> 23<body> 24<style> 25* { 26 box-sizing: border-box; 27} 28 29.main { 30 display: flex; 31 font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 32 font-weight: 300; 33} 34 35pre { 36 font-size: 12px; 37} 38 39ul { 40 margin: 0; 41 padding: 0; 42} 43 44li { 45 list-style: none; 46 border-radius: 3px; 47 border: solid rgba(0, 0, 0, 0) 1px; 48 padding: 3px; 49 margin-right: 5px 0; 50} 51 52li.selected { 53 border: solid rgba(0, 0, 0, 0.89) 1px; 54} 55 56h1 { 57 font-weight: 200; 58 margin-bottom: 0; 59} 60 61h2 { 62 font-size: smaller; 63} 64 65.focus { 66 flex: 1; 67 margin: 20px; 68} 69 70.context { 71 flex: 0 0 25%; 72} 73 74.green { 75 color: green; 76} 77 78.red { 79 color: red; 80} 81 82.files { 83 position: sticky; 84 top: 15px; 85} 86 87.file { 88 display: flex; 89 justify-content: flex-start; 90 flex-direction: row; 91} 92 93.file *:first-child { 94 flex: 0 0 300px; 95} 96 97.file *:last-child { 98 flex-grow: 1; 99} 100 101.version { 102 display: flex; 103 margin-bottom: 4px; 104} 105 106.version li { 107 margin-right: 20px; 108} 109 110input { 111 font-size: large; 112 margin: 20px 0; 113} 114 115</style> 116<script src="//unpkg.com/mithril"></script> 117<script src="//unpkg.com/diff"></script> 118 119<div id="content"></div> 120 121<script> 122// Remove hash bang. 123document.body.firstChild.remove(); 124 125let THIS_URL = window.location.href; 126let gDirectoryToFormatFiles; 127let gNamesToRecords = new Map(); 128let gFilterText = ''; 129let gDisplayedRecords = null; 130let gDisplayedName = null; 131let gADevice = null; 132let gBDevice = null; 133let gDevices = [] 134let gCache = new Map(); 135 136function isdir(url) { 137 return url[url.length - 1] == '/'; 138} 139 140function isfile(url) { 141 return !isdir(url); 142} 143 144function getdir(url) { 145 return url.slice(0, url.lastIndexOf('/')+1); 146} 147 148let getdirectories = url => listdir(url).then(xs => xs.filter(isdir)); 149let getfiles = url => listdir(url).then(xs => xs.filter(isfile)); 150 151function fetch(url) { 152 return new Promise(function(resolve, reject) { 153 let xhr = new XMLHttpRequest(); 154 xhr.open("GET", url, true); 155 xhr.onload = e => resolve({ 156 text: () => Promise.resolve(xhr.responseText), 157 }); 158 xhr.onerror = e => reject(xhr.statusText); 159 xhr.send(null); 160 }); 161} 162 163function delay(t, v) { 164 return new Promise(resolve => { 165 setTimeout(resolve.bind(null, v), t) 166 }); 167} 168 169// Limit the number of outstanding fetch requests to avoid causing 170// problems in the browser. 171let GET_URL_TOKENS = 400; 172async function geturl(url) { 173 console.log('Fetch:', url); 174 if (gCache.has(url)) return Promise.resolve(gCache.get(url)); 175 176 while (GET_URL_TOKENS === 0) { 177 // Retry in 1000ms +/- 250ms to avoid all the requests lining up. 178 await delay(1000 + 500 * (Math.random() - 0.5)); 179 } 180 GET_URL_TOKENS -= 1; 181 182 return fetch(url).then(r => r.text()).then(text => { 183 gCache.set(url, text); 184 return text; 185 }).finally(() => { 186 GET_URL_TOKENS += 1; 187 }); 188} 189 190function listdir(url) { 191 return geturl(url).then(text => { 192 let re = new RegExp('<li><a href="(.+)">(.+)</a>', 'g'); 193 if (window.location.href.indexOf('x20') != -1) 194 re = new RegExp('[^>]</td>\n<td>\n<a href="(.+)">(.+)</a>', 'g'); 195 let match; 196 let matches = []; 197 while (match = re.exec(text)) { 198 matches.push(match[1]); 199 } 200 return matches; 201 }); 202} 203 204function getfiletext(url) { 205 if (gCache.has(url)) return gCache.get(url); 206 geturl(url).then(() => m.redraw()); 207 return ""; 208} 209 210function makeFormatFileRecord(base_url, device, group_name, event_name) { 211 let url = base_url + device + 'events/' + group_name + event_name + 'format'; 212 let group = group_name.replace('/', ''); 213 let name = event_name.replace('/', ''); 214 return new FormatFileRecord(device, group, name, url); 215} 216 217function findFormatFilesByDirectory() { 218 let url = getdir(THIS_URL) + 'data/'; 219 let directoryToFormatFiles = new Map(); 220 return getdirectories(url).then(directories => { 221 return Promise.all(directories.map(device => { 222 directoryToFormatFiles.set(device, []); 223 return getdirectories(url + device + 'events/').then(groups => { 224 return Promise.all(groups.map(group_name => { 225 let innerUrl = url + device + 'events/' + group_name; 226 return getdirectories(innerUrl).then(event_names => { 227 event_names.map(event_name => { 228 let record = makeFormatFileRecord( 229 url, 230 device, 231 group_name, 232 event_name); 233 directoryToFormatFiles.get(device).push(record); 234 }); 235 }); 236 })); 237 }); 238 })); 239 }).then(_ => { 240 return directoryToFormatFiles 241 }); 242} 243 244class FormatFileRecord { 245 constructor(device, group, name, url) { 246 this.device = device; 247 this.group = group; 248 this.name = name; 249 this.url = url; 250 } 251} 252 253function fuzzyMatch(query) { 254 let re = new RegExp(Array.from(query).join('.*')); 255 return text => text.match(re); 256} 257 258function contextView(filterText, namesToRecords) { 259 let matcher = fuzzyMatch(filterText); 260 return m('.context', [ 261 m('h1', {class: 'title'}, 'Ftrace Format Explorer'), 262 m('input[type=text][placeholder=Filter]', { 263 oninput: e => gFilterText = e.target.value, 264 value: filterText, 265 }), 266 m('ul', 267 Array.from(namesToRecords.entries()) 268 .filter(e => matcher(e[0])).map(e => m('li[tabindex=0]', { 269 onfocus: () => { gDisplayedRecords = e[1]; gDisplayedName = e[0]; 270 }, 271 class: gDisplayedName == e[0] ? 'selected' : '', 272 }, e[0] + ' (' + e[1].length + ')' ))), 273 ]); 274} 275 276function focusView(records) { 277 if (records == null) { 278 return m('div.focus'); 279 } 280 281 let r1 = records.filter(r => r.device == gADevice)[0]; 282 let r2 = records.filter(r => r.device == gBDevice)[0]; 283 if (!r1) r1 = records[0]; 284 if (!r2) r2 = records[0]; 285 let f1 = getfiletext(r1.url); 286 let f2 = getfiletext(r2.url); 287 let diff = Diff.diffChars(f1, f2); 288 289 let es = diff.map(part => { 290 let color = part.added ? 'green' : part.removed ? 'red' : 'grey'; 291 let e = m('span.' + color, part.value); 292 return e; 293 }); 294 return m('.focus', [ 295 m('ul.version', gDevices.map(device => m('li', { 296 onclick: () => gADevice = device, 297 class: device == gADevice ? 'selected' : '', 298 }, device))), 299 m('ul.version', gDevices.map(device => m('li', { 300 onclick: () => gBDevice = device, 301 class: device == gBDevice ? 'selected' : '', 302 }, device))), 303 m('.files', [ 304 m('.file', [m('h2', gADevice), m('pre', f1)]), 305 gADevice == gBDevice ? undefined : [ 306 m('.file', [m('h2', gBDevice), m('pre', f2)]), 307 m('.file', [m('h2', 'Delta'), m('pre', es)]), 308 ] 309 ]), 310 ]); 311} 312 313let root = document.getElementById('content'); 314let App = { 315 view: function() { 316 if (!gDirectoryToFormatFiles) 317 return m('.main', 'Loading...'); 318 return m('.main', [ 319 contextView(gFilterText, gNamesToRecords), 320 focusView(gDisplayedRecords), 321 ]) 322 } 323} 324m.mount(root, App); 325 326findFormatFilesByDirectory().then(data => { 327 gDirectoryToFormatFiles = data; 328 gNamesToRecords = new Map(); 329 gDevices = Array.from(gDirectoryToFormatFiles.keys()); 330 for (let records of gDirectoryToFormatFiles.values()) { 331 for (let record of records) { 332 geturl(record.url); 333 if (gNamesToRecords.get(record.name) == null) { 334 gNamesToRecords.set(record.name, []); 335 } 336 gNamesToRecords.get(record.name).push(record); 337 } 338 } 339 [gADevice, gBDevice] = gDevices; 340 m.redraw(); 341}); 342 343</script> 344 345<!-- 346EOF 347#--> 348