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