271 lines
16 KiB
HTML
271 lines
16 KiB
HTML
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Javascript Spaghetti Profiler</title>
|
|
<link rel="stylesheet" type="text/css" href="/public/styles/main.css">
|
|
<link rel="stylesheet" type="text/css" href="/public/styles/results.css">
|
|
<script src="/bower_components/angular/angular.min.js"></script>
|
|
<script src="/public/scripts/app.js"></script>
|
|
<script src="/public/scripts/resultsCtrl.js"></script>
|
|
<script src="/public/scripts/filters.js"></script>
|
|
</head>
|
|
<body ng-app="YellowLabTools" ng-controller="ResultsCtrl">
|
|
<h1>Javascript Spaghetti Profiler</h1>
|
|
|
|
<div ng-if="undefined">Untangling and counting the spaghettis...</div>
|
|
|
|
<div class="ng-cloak">
|
|
<div>Tested url: <a class="testedUrl" href="{{phantomasResults.url}}" target="_blank">{{phantomasResults.url}}</a></div>
|
|
|
|
<div class="resultsMenu">
|
|
<a class="menuItem" href="/"><span>New test<span></a>
|
|
<div class="menuItem" ng-if="!phantomasResults.error" ng-class="{active: view == 'summary'}" ng-click="setView('summary')"><span>Summary</span></div>
|
|
<div class="menuItem" ng-if="!phantomasResults.error" ng-class="{active: view == 'execution'}" ng-click="setView('execution')"><span>Spaghetti</span></div>
|
|
<div class="menuItem" ng-if="!phantomasResults.error" ng-class="{active: view == 'metrics'}" ng-click="setView('metrics')"><span>Metrics</span></div>
|
|
</div>
|
|
|
|
<div ng-if="phantomasResults.error">
|
|
<h2>Error: {{phantomasResults.error}}</h2>
|
|
<div ng-if="phantomasResults.error == 252">Phantomas timed out</div>
|
|
<div ng-if="phantomasResults.error == 253">Phantomas config error</div>
|
|
<div ng-if="phantomasResults.error == 254">Phantomas failed to load page</div>
|
|
<div ng-if="phantomasResults.error == 255">Phantomas internal error</div>
|
|
<div ng-if="phantomasResults.error == 1001">Javascript execution tree error</div>
|
|
</div>
|
|
|
|
<div ng-show="view == 'summary' && !phantomasResults.error" class="summary">
|
|
<h2>Summary</h2>
|
|
|
|
<div class="notations">
|
|
<div>
|
|
<div ng-class="notations.domComplexity">{{notations.domComplexity}}</div>
|
|
<div class="notation">DOM complexity</div>
|
|
<div class="criteria">
|
|
<ul>
|
|
<li ng-if="phantomasResults.metrics.DOMelementsCount">DOM elements count: {{phantomasResults.metrics.DOMelementsCount}}</li>
|
|
<li ng-if="phantomasResults.metrics.DOMelementMaxDepth">DOM max depth: {{phantomasResults.metrics.DOMelementMaxDepth}}</li>
|
|
<li ng-if="phantomasResults.metrics.iframesCount">Number of iframes: {{phantomasResults.metrics.iframesCount}}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div ng-class="notations.domManipulations">{{notations.domManipulations}}</div>
|
|
<div class="notation">DOM read and write</div>
|
|
<div class="criteria">
|
|
<ul>
|
|
<li ng-if="phantomasResults.metrics.DOMinserts">DOM inserts: {{phantomasResults.metrics.DOMinserts}}</li>
|
|
<li ng-if="phantomasResults.metrics.DOMqueries">DOM queries: {{phantomasResults.metrics.DOMqueries}}</li>
|
|
<li ng-if="totalJSTime">Time it took: {{totalJSTime}} ms</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div ng-class="notations.duplicatedDomQueries">{{notations.duplicatedDomQueries}}</div>
|
|
<div class="notation">Duplicated DOM queries</div>
|
|
<div class="criteria">
|
|
<ul>
|
|
<li ng-if="duplicatedQueriesCountAll">Avoidable queries: {{duplicatedQueriesCountAll}}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div ng-class="notations.eventsBound">{{notations.eventsBound}}</div>
|
|
<div class="notation">Events bound</div>
|
|
<div class="criteria">
|
|
<ul>
|
|
<li ng-if="phantomasResults.metrics.eventsBound">Events bound: {{phantomasResults.metrics.eventsBound}}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div ng-class="notations.badPractices">{{notations.badPractices}}</div>
|
|
<div class="notation">Evil Javascript</div>
|
|
<div class="criteria">
|
|
<ul>
|
|
<li ng-if="phantomasResults.metrics.jsErrors">Javascript errors: {{phantomasResults.metrics.jsErrors}}</li>
|
|
<li ng-if="phantomasResults.metrics.evalCalls">eval calls: {{phantomasResults.metrics.evalCalls}}</li>
|
|
<li ng-if="phantomasResults.metrics.documentWriteCalls">document.write calls: {{phantomasResults.metrics.documentWriteCalls}}</li>
|
|
<li ng-if="phantomasResults.metrics.consoleMessages">Console messages: {{phantomasResults.metrics.consoleMessages}}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div ng-class="notations.scripts">{{notations.scripts}}</div>
|
|
<div class="notation">Scripts loaded</div>
|
|
<div class="criteria">
|
|
<ul>
|
|
<li ng-if="phantomasResults.metrics.jsCount">Scripts count: {{phantomasResults.metrics.jsCount}}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div ng-class="notations.jQueryLoading">{{notations.jQueryLoading}}</div>
|
|
<div class="notation">jQuery version</div>
|
|
<div class="criteria">
|
|
<ul>
|
|
<li ng-if="phantomasResults.metrics.jQueryDifferentVersions == 1">jQuery version: {{phantomasResults.metrics.jQueryVersion}}</li>
|
|
<li ng-if="phantomasResults.metrics.jQueryDifferentVersions > 1">{{phantomasResults.metrics.jQueryDifferentVersions}} versions loaded:
|
|
<span ng-repeat="version in phantomasResults.offenders.jQueryDifferentVersions">{{version}}<span ng-show="!$last"> & </span></span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div ng-show="view == 'execution' && !phantomasResults.error" class="execution">
|
|
<h2>Javascript Spaghetti Profiler (interactions with the DOM)</h2>
|
|
<div class="filters">
|
|
<div>
|
|
<input type="checkbox" ng-model="textFilterOn" />
|
|
Filter by
|
|
<input type="text" ng-model="textFilter" placeholder="search..." class="textFilter" ng-change="textFilterOn = true" />
|
|
</div>
|
|
<div>
|
|
<input type="checkbox" ng-model="slowRequestsOn" id="slowRequests" />
|
|
<label for="slowRequests">Requests slower than</label>
|
|
<input type="number" value="5" min="0" ng-model="slowRequestsLimit" class="slowRequestsLimit"> ms
|
|
</div>
|
|
</div>
|
|
<div class="table">
|
|
<div class="headers">
|
|
<div><!-- index --></div>
|
|
<div>Type</div>
|
|
<div>Params</div>
|
|
<div><!-- details --></div>
|
|
<div>Duration</div>
|
|
<div>Timestamp</div>
|
|
</div>
|
|
<div ng-repeat="node in javascript.children"
|
|
ng-if="(!slowRequestsOn || node.data.time > slowRequestsLimit)
|
|
&& (!textFilterOn
|
|
|| !textFilter.length
|
|
|| node.data.type.indexOf(textFilter) >= 0
|
|
|| node.data.callDetails.arguments[0].indexOf(textFilter) >= 0
|
|
|| node.data.callDetails.arguments[1].indexOf(textFilter) >= 0
|
|
|| node.data.callDetails.arguments[2].indexOf(textFilter) >= 0
|
|
|| node.data.callDetails.arguments[3].indexOf(textFilter) >= 0)"
|
|
ng-class="{showingDetails: node.data.showDetails, jsError: node.data.type == 'error' || node.data.type == 'jQuery version change'}">
|
|
<div class="index">{{$index}}</div>
|
|
<div class="type">{{node.data.type}}</div>
|
|
|
|
<div class="value">
|
|
{{node.data.callDetails.arguments[0]}}
|
|
<span ng-if="node.data.callDetails.arguments.length > 1"> : {{node.data.callDetails.arguments[1]}}</span>
|
|
<span ng-if="node.data.callDetails.arguments.length > 2"> : {{node.data.callDetails.arguments[2]}}</span>
|
|
<span ng-if="node.data.callDetails.arguments.length > 3"> : {{node.data.callDetails.arguments[3]}}</span>
|
|
</div>
|
|
|
|
<div class="details">
|
|
<button ng-click="onNodeDetailsClick(node)"
|
|
ng-class="{
|
|
'warningDetails': node.data.type == 'jQuery - bind' && node.data.callDetails.context.length > 5
|
|
}"
|
|
ng-if="node.data.type != 'script loaded' && node.data.type != 'jQuery loaded'">i</button>
|
|
<div class="detailsOverlay" ng-show="node.data.showDetails">
|
|
<div class="closeBtn" ng-click="onNodeDetailsClick(node)">✖</div>
|
|
|
|
<div ng-if="node.data.callDetails.context.domElement">
|
|
<h4>Called on DOM element</h4>
|
|
<div>{{node.data.callDetails.context.domElement}}</div>
|
|
</div>
|
|
|
|
<div ng-if="node.data.callDetails.context.length == 1 && node.data.callDetails.context.firstElementPath">
|
|
<h4>Called on 1 jQuery element</h4>
|
|
<div>{{node.data.callDetails.context.firstElementPath}}</div>
|
|
</div>
|
|
|
|
<div ng-if="node.data.callDetails.context.length > 1">
|
|
<h4>Called on {{node.data.callDetails.context.length}} jQuery elements</h4>
|
|
<p class="advice" ng-if="node.data.callDetails.context.length > 5">The .bind() method attaches the event listener to each jQuery element one by one. Using the .on() method is preferred if available (from v1.7).</p>
|
|
<p ng-if="node.data.callDetails.context.firstElementPath"><b>First one is:</b> {{node.data.callDetails.context.firstElementPath}}</p>
|
|
</div>
|
|
|
|
<div ng-if="node.data.parsedBacktrace">
|
|
<h4>Backtrace</h4>
|
|
<div class="table">
|
|
<div ng-repeat="trace in node.data.parsedBacktrace track by $index">
|
|
<div>{{trace.fnName || '(anonymous)'}}</div>
|
|
<div class="trace"><a href="{{trace.filePath}}" title="{{trace.filePath}}" target="_blank">{{trace.fileName || 'HTML'}}</a>:{{trace.line}}</div>
|
|
</div>
|
|
<div ng-if="node.data.parsedBacktrace.length == 0 && node.data.type != 'script loaded'">
|
|
<div>can't find any backtrace :/</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div ng-if="node.children.length > 0">
|
|
<h4>Sub processes</h4>
|
|
<div class="table">
|
|
<div class="headers">
|
|
<div><!-- index --></div>
|
|
<div>Type</div>
|
|
<div>Params</div>
|
|
<div>Duration</div>
|
|
</div>
|
|
<div ng-repeat="node in node.children">
|
|
<div class="index">{{$index}}</div>
|
|
<div class="type">{{node.data.type}}</div>
|
|
<div class="value">
|
|
{{node.data.callDetails.arguments[0]}}
|
|
<span ng-if="node.data.callDetails.arguments.length > 1"> : {{node.data.callDetails.arguments[1]}}</span>
|
|
<span ng-if="node.data.callDetails.arguments.length > 2"> : {{node.data.callDetails.arguments[2]}}</span>
|
|
<span ng-if="node.data.callDetails.arguments.length > 3"> : {{node.data.callDetails.arguments[3]}}</span>
|
|
</div>
|
|
<div class="duration" ng-if="node.data.time != undefined">{{node.data.time}} ms</div>
|
|
<div class="duration" ng-if="node.data.time == undefined"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="duration" ng-if="node.data.time != undefined">
|
|
{{node.data.time}} ms
|
|
<div ng-if="node.data.time > slowRequestsLimit" class="warningIcon" title="Slower than {{slowRequestsLimit}} ms"></div>
|
|
</div>
|
|
<div class="duration" ng-if="node.data.time == undefined"></div>
|
|
<div class="startTime">{{node.data.timestamp}}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div ng-show="view == 'metrics' && !phantomasResults.error" class="metrics">
|
|
<h2>Metrics from Phantomas report</h2>
|
|
<p><a href="https://github.com/macbre/phantomas" target="_blank">Phantomas</a> is a web performance metrics collector. You can find here the (almost) entire list of metrics from Phantomas.</p>
|
|
|
|
<div ng-repeat="(moduleName, module) in metricsModule">
|
|
<h4>{{moduleName}}</h4>
|
|
<div ng-repeat="(metricName, metric) in module" ng-if="phantomasResults.metrics[metricName] >= 0" class="module" id="metric_{{metricName}}">
|
|
<div class="value">
|
|
{{metricName}}: {{phantomasResults.metrics[metricName]}}
|
|
<span ng-if="metric.unit == 'ms' || metric.unit == 'bytes' || metric.unit == '%'">{{metric.unit}}</span>
|
|
</div>
|
|
<div class="legend">
|
|
{{metric.desc}}
|
|
<span ng-if="metric.unreliable">(unreliable)</span>
|
|
</div>
|
|
<div ng-if="phantomasResults.offenders[metricName]" class="offenders">
|
|
<div ng-if="!metric.showingOffenders" ng-click="metric.showingOffenders = true">Show offenders</div>
|
|
<div ng-if="metric.showingOffenders" ng-click="metric.showingOffenders = false">Hide offenders</div>
|
|
<ul ng-if="metric.showingOffenders">
|
|
<li ng-repeat="offender in phantomasResults.offenders[metricName] track by $index">
|
|
{{offender}}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
|
var _phantomas_results = %%RESULTS%%;
|
|
|
|
var _phantomas_metadata = %%METADATA%%;
|
|
|
|
</script>
|
|
</body>
|
|
</html> |