• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<html>
2<!--
3Copyright 2016 the V8 project authors. All rights reserved.  Use of this source
4code is governed by a BSD-style license that can be found in the LICENSE file.
5-->
6
7<head>
8<style>
9  html {
10    font-family: monospace;
11  }
12
13  .parse {
14    background-color: red;
15    border: 1px red solid;
16  }
17
18  .preparse {
19    background-color: orange;
20    border: 1px orange solid;
21  }
22
23  .resolution {
24    background-color: green;
25    border: 1px green solid;
26  }
27
28  .execution {
29    background-color: black;
30    border-left: 2px black solid;
31    z-index: -1;
32  }
33
34  .script {
35    margin-top: 1em;
36    overflow: visible;
37    clear: both;
38      border-top: 2px black dotted;
39  }
40  .script h3 {
41    height: 20px;
42    margin-bottom: 0.5em;
43    white-space: nowrap;
44  }
45
46  .script-details {
47    float: left;
48  }
49
50  .chart {
51    float: left;
52    margin-right: 2em;
53  }
54
55  .funktion-list {
56    float: left;
57    height: 400px;
58  }
59
60  .funktion-list > ul {
61    height: 80%;
62    overflow-y: scroll;
63  }
64
65  .funktion {
66  }
67
68  .script-size {
69    display: inline-flex;
70    background-color: #505050;
71    border-radius: 3px;
72    padding: 3px;
73    margin: 2px;
74    white-space: nowrap;
75    overflow: hidden;
76    text-decoration: none;
77    color: white;
78  }
79  .script-size.eval {
80    background-color: #ee6300fc;
81  }
82  .script-size.streaming {
83    background-color: #008aff;
84  }
85  .script-size.deserialized {
86    background-color: #1fad00fc;
87  }
88
89  .script-details {
90    padding-right: 5px;
91    margin-right: 4px;
92  }
93  /* all but the last need a border  */
94  .script-details:nth-last-child(n+2) {
95    border-right: 1px white solid;
96  }
97
98  .script-details.id {
99    min-width: 2em;
100    text-align: right;
101  }
102</style>
103<script src="./splaytree.js" type="text/javascript"></script>
104<script src="./codemap.js" type="text/javascript"></script>
105<script src="./csvparser.js" type="text/javascript"></script>
106<script src="./consarray.js" type="text/javascript"></script>
107<script src="./profile.js" type="text/javascript"></script>
108<script src="./profile_view.js" type="text/javascript"></script>
109<script src="./logreader.js" type="text/javascript"></script>
110<script src="./arguments.js" type="text/javascript"></script>
111<script src="./parse-processor.js" type="text/javascript"></script>
112<script src="./SourceMap.js" type="text/javascript"></script>
113<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
114<script type="text/javascript">
115"use strict";
116google.charts.load('current', {packages: ['corechart']});
117
118function $(query) {
119  return document.querySelector(query);
120}
121
122function loadFile() {
123  let files = $('#uploadInput').files;
124
125  let file = files[0];
126  let reader = new FileReader();
127
128  reader.onload = function(evt) {
129    const kTimerName = 'parse log file';
130    console.time(kTimerName);
131    let parseProcessor = new ParseProcessor();
132    parseProcessor.processString(this.result);
133    console.timeEnd(kTimerName);
134    renderParseResults(parseProcessor);
135    document.parseProcessor = parseProcessor;
136  }
137  reader.readAsText(file);
138}
139
140function handleOnLoad() {
141  document.querySelector("#uploadInput").focus();
142}
143
144function createNode(tag, classNames) {
145  let node = document.createElement(tag);
146  if (classNames) {
147    if (Array.isArray(classNames)) {
148      node.classList.add(...classNames);
149    } else {
150      node.className = classNames;
151    }
152  }
153  return node;
154}
155
156function div(...args) {
157  return createNode('div', ...args);
158}
159
160function h1(string) {
161  let node = createNode('h1');
162  node.appendChild(text(string));
163  return node;
164}
165
166function h3(string, ...args) {
167  let node = createNode('h3', ...args);
168  if (string) node.appendChild(text(string));
169  return node;
170}
171
172function a(href, string, ...args) {
173  let link = createNode('a', ...args);
174  if (href.length) link.href = href;
175  if (string) link.appendChild(text(string));
176  return link;
177}
178
179function text(string) {
180  return document.createTextNode(string);
181}
182
183function delay(t) {
184  return new Promise(resolve => setTimeout(resolve, t));
185}
186
187function renderParseResults(parseProcessor) {
188  let result = $('#result');
189  // clear out all existing result pages;
190  result.innerHTML = '';
191  const start = parseProcessor.firstEventTimestamp;
192  const end = parseProcessor.lastEventTimestamp;
193  renderScript(result, parseProcessor.totalScript, start, end);
194  // Build up the graphs lazily to keep the page responsive.
195  parseProcessor.scripts.forEach(
196      script => renderScript(result, script, start, end));
197  renderScriptSizes(parseProcessor);
198  // Install an intersection observer to lazily load the graphs when the script
199  // div becomes visible for the first time.
200  var io = new IntersectionObserver((entries, observer) => {
201    entries.forEach(entry => {
202      if (entry.intersectionRatio == 0) return;
203      console.assert(!entry.target.querySelector('.graph'));
204      let target = entry.target;
205      appendGraph(target.script, target, start, end);
206      observer.unobserve(entry.target);
207    });
208  }, {rootMargin: '400px'});
209  document.querySelectorAll('.script').forEach(div => io.observe(div));
210}
211
212const kTimeFactor = 10;
213const kHeight = 20;
214const kFunktionTopOffset = 50;
215
216function renderScript(result, script, start, end) {
217  // Filter out empty scripts.
218  if (script.isEmpty() || script.lastParseEvent == 0) return;
219
220  let scriptDiv = div('script');
221  scriptDiv.script = script;
222
223  let scriptTitle = h3();
224  let anchor = a("", 'Script #' + script.id);
225  anchor.name = "script"+script.id
226  scriptTitle.appendChild(anchor);
227  scriptDiv.appendChild(scriptTitle);
228  if (script.file) scriptTitle.appendChild(a(script.file, script.file));
229  let summary = createNode('pre', 'script-details');
230  summary.appendChild(text(script.summary));
231  scriptDiv.appendChild(summary);
232  result.appendChild(scriptDiv);
233}
234
235function renderScriptSizes(parseProcessor) {
236  let scriptsDiv = $('#scripts');
237  parseProcessor.scripts.forEach(
238    script => {
239      let scriptDiv = a('#script'+script.id, '', 'script-size');
240      let scriptId = div('script-details');
241      scriptId.classList.add('id');
242      scriptId.innerText = script.id;
243      scriptDiv.appendChild(scriptId);
244      let scriptSize = div('script-details');
245      scriptSize.innerText = BYTES(script.bytesTotal);
246      scriptDiv.appendChild(scriptSize);
247      let scriptUrl = div('script-details');
248      if (script.isEval) {
249        scriptUrl.innerText = "eval";
250        scriptDiv.classList.add('eval');
251      } else {
252        scriptUrl.innerText = script.file.split("/").pop();
253      }
254      if (script.isStreamingCompiled ) {
255        scriptDiv.classList.add('streaming');
256      } else if (script.deserializationTimestamp > 0) {
257        scriptDiv.classList.add('deserialized');
258      }
259      scriptDiv.appendChild(scriptUrl);
260      scriptDiv.style.width = script.bytesTotal * 0.001;
261      scriptsDiv.appendChild(scriptDiv);
262    });
263}
264
265const kMaxTime = 120 * kSecondsToMillis;
266// Resolution of the graphs
267const kTimeIncrement = 1;
268const kSelectionTimespan = 2;
269// TODO(cbruni): support compilation cache hit.
270const series = [
271    ['firstParseEvent', 'Any Parse', 'area'],
272    ['execution', '1st Exec', 'area'],
273    ['firstCompileEvent', 'Any Compile', 'area'],
274    ['compile', 'Eager Compile'],
275    ['lazyCompile', 'Lazy Compile'],
276    ['parse', 'Parsing'],
277    ['preparse', 'Preparse'],
278    ['resolution', 'Preparse with Var. Resolution'],
279    ['deserialization', 'Deserialization'],
280    ['optimization', 'Optimize'],
281];
282const metricNames = series.map(each => each[0]);
283// Display cumulative values (useuful for bytes).
284const kCumulative = true;
285// Include durations in the graphs.
286const kUseDuration = false;
287
288
289function appendGraph(script, parentNode, start, end) {
290  const timerLabel = 'graph script=' + script.id;
291  // TODO(cbruni): add support for network events
292
293  console.time(timerLabel);
294  let data = new google.visualization.DataTable();
295  data.addColumn('number', 'Duration');
296  // The series are interleave bytes processed, time spent and thus have two
297  // different vAxes.
298  let seriesOptions = [];
299  let colors = ['#4D4D4D', '#fff700', '#5DA5DA', '#FAA43A', '#60BD68',
300      '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#F15854'];
301  series.forEach(([metric, description, type]) => {
302    let color = colors.shift();
303    // Add the bytes column.
304    data.addColumn('number', description);
305    let options = {targetAxisIndex: 0, color: color};
306    if (type == 'area') options.type = 'area';
307    seriesOptions.push(options)
308    // Add the time column.
309    if (kUseDuration) {
310      data.addColumn('number', description + ' Duration');
311      seriesOptions.push(
312          {targetAxisIndex: 1, color: color, lineDashStyle: [3, 2]});
313    }
314  });
315
316  const maxTime = Math.min(kMaxTime, end);
317  console.time('metrics');
318  let metricValues =
319    script.getAccumulatedTimeMetrics(metricNames , 0, maxTime, kTimeIncrement,
320        kCumulative, kUseDuration);
321  console.timeEnd('metrics');
322  // Make sure that the series added to the graph matches the returned values.
323  console.assert(metricValues[0].length == seriesOptions.length + 1);
324  data.addRows(metricValues);
325
326  let options = {
327    explorer: {
328      actions: ['dragToZoom', 'rightClickToReset'],
329      maxZoomIn: 0.01
330    },
331    hAxis: {
332      format: '#,###.##s'
333    },
334    vAxes: {
335      0: {title: 'Bytes Touched', format: 'short'},
336      1: {title: 'Duration', format: '#,###ms'}
337    },
338    height: 400,
339    width: 1000,
340    chartArea: {left: 70, top: 0, right: 160, height: "90%"},
341    // The first series should be a area chart (total bytes touched),
342    series: seriesOptions,
343    // everthing else is a line.
344    seriesType: 'line'
345  };
346  let graphNode = createNode('div', 'chart');
347  let listNode = createNode('div', 'funktion-list');
348  parentNode.appendChild(graphNode);
349  parentNode.appendChild(listNode);
350  let chart = new google.visualization.ComboChart(graphNode);
351  google.visualization.events.addListener(chart, 'select',
352      () => selectGraphPointHandler(chart, data, script, parentNode));
353  chart.draw(data, options);
354  // Add event listeners
355  console.timeEnd(timerLabel);
356}
357
358function selectGraphPointHandler(chart, data, script, parentNode) {
359  let selection = chart.getSelection();
360  if (selection.length <= 0) return;
361  // Display a list of funktions with events at the given time.
362  let {row, column} = selection[0];
363  if (row === null|| column === null) return;
364  const kEntrySize = kUseDuration ? 2 : 1;
365  let [metric, description] = series[((column-1)/ kEntrySize) | 0];
366  let time = data.getValue(row, 0);
367  let funktions = script.getFunktionsAtTime(
368        time * kSecondsToMillis, kSelectionTimespan, metric);
369  let oldList = parentNode.querySelector('.funktion-list');
370  parentNode.replaceChild(
371      createFunktionList(metric, description, time, funktions), oldList);
372}
373
374function createFunktionList(metric, description, time, funktions) {
375  let container = createNode('div', 'funktion-list');
376  container.appendChild(h3('Changes of "' + description + '" at ' +
377        time + 's: ' + funktions.length));
378  let listNode = createNode('ul');
379  funktions.forEach(funktion => {
380    let node = createNode('li', 'funktion');
381    node.funktion = funktion;
382    node.appendChild(text(funktion.toString(false) + " "));
383    let script = funktion.script;
384    if (script) {
385      node.appendChild(a("#script" + script.id, "in script " + script.id));
386    }
387    listNode.appendChild(node);
388  });
389  container.appendChild(listNode);
390  return container;
391}
392</script>
393</head>
394
395<body onload="handleOnLoad()">
396  <h1>BEHOLD, THIS IS PARSEROR!</h1>
397
398  <h2>Usage</h2>
399  Run your script with <code>--log-function-events</code> and upload <code>v8.log</code> on this page:<br/>
400  <code>/path/to/d8 --log-function-events your_script.js</code>
401
402  <h2>Data</h2>
403  <form name="fileForm">
404    <p>
405      <input id="uploadInput" type="file" name="files" onchange="loadFile();" accept=".log"> trace entries: <span id="count">0</span>
406    </p>
407  </form>
408
409
410  <h2>Scripts</h2>
411  <div id="scripts"></div>
412
413  <h2>Result</h2>
414  <div id="result"></div>
415</body>
416
417</html>
418