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 geturl(url) { 164 console.log('Fetch:', url); 165 if (gCache.has(url)) return Promise.resolve(gCache.get(url)); 166 return fetch(url).then(r => r.text()).then(text => { 167 gCache.set(url, text); 168 return text; 169 }); 170} 171 172function listdir(url) { 173 return geturl(url).then(text => { 174 let re = new RegExp('<li><a href="(.+)">(.+)</a>', 'g'); 175 if (window.location.href.indexOf('x20') != -1) 176 re = new RegExp('[^>]</td>\n<td>\n<a href="(.+)">(.+)</a>', 'g'); 177 let match; 178 let matches = []; 179 while (match = re.exec(text)) { 180 matches.push(match[1]); 181 } 182 return matches; 183 }); 184} 185 186function getfiletext(url) { 187 if (gCache.has(url)) return gCache.get(url); 188 geturl(url).then(() => m.redraw()); 189 return ""; 190} 191 192function makeFormatFileRecord(base_url, device, group_name, event_name) { 193 let url = base_url + device + 'events/' + group_name + event_name + 'format'; 194 let group = group_name.replace('/', ''); 195 let name = event_name.replace('/', ''); 196 return new FormatFileRecord(device, group, name, url); 197} 198 199function findFormatFilesByDirectory() { 200 let url = getdir(THIS_URL) + 'data/'; 201 let directoryToFormatFiles = new Map(); 202 return getdirectories(url).then(directories => { 203 return Promise.all(directories.map(device => { 204 directoryToFormatFiles.set(device, []); 205 return getdirectories(url + device + 'events/').then(groups => { 206 return Promise.all(groups.map(group_name => { 207 let innerUrl = url + device + 'events/' + group_name; 208 return getdirectories(innerUrl).then(event_names => { 209 event_names.map(event_name => { 210 let record = makeFormatFileRecord( 211 url, 212 device, 213 group_name, 214 event_name); 215 directoryToFormatFiles.get(device).push(record); 216 }); 217 }); 218 })); 219 }); 220 })); 221 }).then(_ => { 222 return directoryToFormatFiles 223 }); 224} 225 226class FormatFileRecord { 227 constructor(device, group, name, url) { 228 this.device = device; 229 this.group = group; 230 this.name = name; 231 this.url = url; 232 } 233} 234 235function fuzzyMatch(query) { 236 let re = new RegExp(Array.from(query).join('.*')); 237 return text => text.match(re); 238} 239 240function contextView(filterText, namesToRecords) { 241 let matcher = fuzzyMatch(filterText); 242 return m('.context', [ 243 m('h1', {class: 'title'}, 'Ftrace Format Explorer'), 244 m('input[type=text][placeholder=Filter]', { 245 oninput: m.withAttr('value', value => gFilterText = value), 246 value: filterText, 247 }), 248 m('ul', 249 Array.from(namesToRecords.entries()) 250 .filter(e => matcher(e[0])).map(e => m('li[tabindex=0]', { 251 onfocus: () => { gDisplayedRecords = e[1]; gDisplayedName = e[0]; 252 }, 253 class: gDisplayedName == e[0] ? 'selected' : '', 254 }, e[0] + ' (' + e[1].length + ')' ))), 255 ]); 256} 257 258function focusView(records) { 259 if (records == null) { 260 return m('div.focus'); 261 } 262 263 let r1 = records.filter(r => r.device == gADevice)[0]; 264 let r2 = records.filter(r => r.device == gBDevice)[0]; 265 if (!r1) r1 = records[0]; 266 if (!r2) r2 = records[0]; 267 let f1 = getfiletext(r1.url); 268 let f2 = getfiletext(r2.url); 269 let diff = JsDiff.diffChars(f1, f2); 270 271 let es = diff.map(part => { 272 let color = part.added ? 'green' : part.removed ? 'red' : 'grey'; 273 let e = m('span.' + color, part.value); 274 return e; 275 }); 276 return m('.focus', [ 277 m('ul.version', gDevices.map(device => m('li', { 278 onclick: () => gADevice = device, 279 class: device == gADevice ? 'selected' : '', 280 }, device))), 281 m('ul.version', gDevices.map(device => m('li', { 282 onclick: () => gBDevice = device, 283 class: device == gBDevice ? 'selected' : '', 284 }, device))), 285 m('.files', [ 286 m('.file', [m('h2', gADevice), m('pre', f1)]), 287 gADevice == gBDevice ? undefined : [ 288 m('.file', [m('h2', gBDevice), m('pre', f2)]), 289 m('.file', [m('h2', 'Delta'), m('pre', es)]), 290 ] 291 ]), 292 ]); 293} 294 295let root = document.getElementById('content'); 296let App = { 297 view: function() { 298 if (!gDirectoryToFormatFiles) 299 return m('.main', 'Loading...'); 300 return m('.main', [ 301 contextView(gFilterText, gNamesToRecords), 302 focusView(gDisplayedRecords), 303 ]) 304 } 305} 306m.mount(root, App); 307 308findFormatFilesByDirectory().then(data => { 309 gDirectoryToFormatFiles = data; 310 gNamesToRecords = new Map(); 311 gDevices = Array.from(gDirectoryToFormatFiles.keys()); 312 for (let records of gDirectoryToFormatFiles.values()) { 313 for (let record of records) { 314 geturl(record.url); 315 if (gNamesToRecords.get(record.name) == null) { 316 gNamesToRecords.set(record.name, []); 317 } 318 gNamesToRecords.get(record.name).push(record); 319 } 320 } 321 [gADevice, gBDevice] = gDevices; 322 m.redraw(); 323}); 324 325</script> 326 327<!-- 328EOF 329#--> 330