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