blob: d100264562e22eb5a6bbc95c318c12b1be773ce7 [file] [log] [blame]
#!/bin/bash
# Loading... <!--
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cd $(dirname $0)
python3 -m webbrowser -t "http://localhost:8000/$(basename $0)"
python3 -m http.server
<<-EOF
-->
<body>
<style>
* {
box-sizing: border-box;
}
.main {
display: flex;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 300;
}
pre {
font-size: 12px;
}
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
border-radius: 3px;
border: solid rgba(0, 0, 0, 0) 1px;
padding: 3px;
margin-right: 5px 0;
}
li.selected {
border: solid rgba(0, 0, 0, 0.89) 1px;
}
h1 {
font-weight: 200;
margin-bottom: 0;
}
h2 {
font-size: smaller;
}
.focus {
flex: 1;
margin: 20px;
}
.context {
flex: 0 0 25%;
}
.green {
color: green;
}
.red {
color: red;
}
.files {
position: sticky;
top: 15px;
}
.file {
display: flex;
justify-content: flex-start;
flex-direction: row;
}
.file *:first-child {
flex: 0 0 300px;
}
.file *:last-child {
flex-grow: 1;
}
.version {
display: flex;
margin-bottom: 4px;
}
.version li {
margin-right: 20px;
}
input {
font-size: large;
margin: 20px 0;
}
</style>
<script src="//unpkg.com/mithril"></script>
<script src="//unpkg.com/diff"></script>
<div id="content"></div>
<script>
// Remove hash bang.
document.body.firstChild.remove();
let THIS_URL = window.location.href;
let gDirectoryToFormatFiles;
let gNamesToRecords = new Map();
let gFilterText = '';
let gDisplayedRecords = null;
let gDisplayedName = null;
let gADevice = null;
let gBDevice = null;
let gDevices = []
let gCache = new Map();
function isdir(url) {
return url[url.length - 1] == '/';
}
function isfile(url) {
return !isdir(url);
}
function getdir(url) {
return url.slice(0, url.lastIndexOf('/')+1);
}
let getdirectories = url => listdir(url).then(xs => xs.filter(isdir));
let getfiles = url => listdir(url).then(xs => xs.filter(isfile));
function fetch(url) {
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = e => resolve({
text: () => Promise.resolve(xhr.responseText),
});
xhr.onerror = e => reject(xhr.statusText);
xhr.send(null);
});
}
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t)
});
}
// Limit the number of outstanding fetch requests to avoid causing
// problems in the browser.
let GET_URL_TOKENS = 400;
async function geturl(url) {
console.log('Fetch:', url);
if (gCache.has(url)) return Promise.resolve(gCache.get(url));
while (GET_URL_TOKENS === 0) {
// Retry in 1000ms +/- 250ms to avoid all the requests lining up.
await delay(1000 + 500 * (Math.random() - 0.5));
}
GET_URL_TOKENS -= 1;
return fetch(url).then(r => r.text()).then(text => {
gCache.set(url, text);
return text;
}).finally(() => {
GET_URL_TOKENS += 1;
});
}
function listdir(url) {
return geturl(url).then(text => {
let re = new RegExp('<li><a href="(.+)">(.+)</a>', 'g');
if (window.location.href.indexOf('x20') != -1)
re = new RegExp('[^>]</td>\n<td>\n<a href="(.+)">(.+)</a>', 'g');
let match;
let matches = [];
while (match = re.exec(text)) {
matches.push(match[1]);
}
return matches;
});
}
function getfiletext(url) {
if (gCache.has(url)) return gCache.get(url);
geturl(url).then(() => m.redraw());
return "";
}
function makeFormatFileRecord(base_url, device, group_name, event_name) {
let url = base_url + device + 'events/' + group_name + event_name + 'format';
let group = group_name.replace('/', '');
let name = event_name.replace('/', '');
return new FormatFileRecord(device, group, name, url);
}
function findFormatFilesByDirectory() {
let url = getdir(THIS_URL) + 'data/';
let directoryToFormatFiles = new Map();
return getdirectories(url).then(directories => {
return Promise.all(directories.map(device => {
directoryToFormatFiles.set(device, []);
return getdirectories(url + device + 'events/').then(groups => {
return Promise.all(groups.map(group_name => {
let innerUrl = url + device + 'events/' + group_name;
return getdirectories(innerUrl).then(event_names => {
event_names.map(event_name => {
let record = makeFormatFileRecord(
url,
device,
group_name,
event_name);
directoryToFormatFiles.get(device).push(record);
});
});
}));
});
}));
}).then(_ => {
return directoryToFormatFiles
});
}
class FormatFileRecord {
constructor(device, group, name, url) {
this.device = device;
this.group = group;
this.name = name;
this.url = url;
}
}
function fuzzyMatch(query) {
let re = new RegExp(Array.from(query).join('.*'));
return text => text.match(re);
}
function contextView(filterText, namesToRecords) {
let matcher = fuzzyMatch(filterText);
return m('.context', [
m('h1', {class: 'title'}, 'Ftrace Format Explorer'),
m('input[type=text][placeholder=Filter]', {
oninput: e => gFilterText = e.target.value,
value: filterText,
}),
m('ul',
Array.from(namesToRecords.entries())
.filter(e => matcher(e[0])).map(e => m('li[tabindex=0]', {
onfocus: () => { gDisplayedRecords = e[1]; gDisplayedName = e[0];
},
class: gDisplayedName == e[0] ? 'selected' : '',
}, e[0] + ' (' + e[1].length + ')' ))),
]);
}
function focusView(records) {
if (records == null) {
return m('div.focus');
}
let r1 = records.filter(r => r.device == gADevice)[0];
let r2 = records.filter(r => r.device == gBDevice)[0];
if (!r1) r1 = records[0];
if (!r2) r2 = records[0];
let f1 = getfiletext(r1.url);
let f2 = getfiletext(r2.url);
let diff = Diff.diffChars(f1, f2);
let es = diff.map(part => {
let color = part.added ? 'green' : part.removed ? 'red' : 'grey';
let e = m('span.' + color, part.value);
return e;
});
return m('.focus', [
m('ul.version', gDevices.map(device => m('li', {
onclick: () => gADevice = device,
class: device == gADevice ? 'selected' : '',
}, device))),
m('ul.version', gDevices.map(device => m('li', {
onclick: () => gBDevice = device,
class: device == gBDevice ? 'selected' : '',
}, device))),
m('.files', [
m('.file', [m('h2', gADevice), m('pre', f1)]),
gADevice == gBDevice ? undefined : [
m('.file', [m('h2', gBDevice), m('pre', f2)]),
m('.file', [m('h2', 'Delta'), m('pre', es)]),
]
]),
]);
}
let root = document.getElementById('content');
let App = {
view: function() {
if (!gDirectoryToFormatFiles)
return m('.main', 'Loading...');
return m('.main', [
contextView(gFilterText, gNamesToRecords),
focusView(gDisplayedRecords),
])
}
}
m.mount(root, App);
findFormatFilesByDirectory().then(data => {
gDirectoryToFormatFiles = data;
gNamesToRecords = new Map();
gDevices = Array.from(gDirectoryToFormatFiles.keys());
for (let records of gDirectoryToFormatFiles.values()) {
for (let record of records) {
geturl(record.url);
if (gNamesToRecords.get(record.name) == null) {
gNamesToRecords.set(record.name, []);
}
gNamesToRecords.get(record.name).push(record);
}
}
[gADevice, gBDevice] = gDevices;
m.redraw();
});
</script>
<!--
EOF
#-->