Bug 930143 - Make it easier to generate a webpage of results using get-metric-for-build;r=davehunt

This commit is contained in:
William Lachance 2013-11-26 18:02:26 -05:00
Родитель b6223ba4a7
Коммит 9af8377b04
9 изменённых файлов: 141 добавлений и 76 удалений

Просмотреть файл

@ -4,12 +4,14 @@ import datetime
import eideticker import eideticker
import json import json
import os import os
import shutil
import sys import sys
import time import time
import videocapture import videocapture
CAPTURE_DIR = os.path.join(os.path.dirname(__file__), "../captures") CAPTURE_DIR = os.path.join(os.path.dirname(__file__), "../captures")
PROFILE_DIR = os.path.join(os.path.dirname(__file__), "../profiles") PROFILE_DIR = os.path.join(os.path.dirname(__file__), "../profiles")
DASHBOARD_DIR = os.path.join(os.path.dirname(__file__), "../src/dashboard")
def runtest(device_prefs, testname, options, apk=None, appname = None, def runtest(device_prefs, testname, options, apk=None, appname = None,
appdate = None): appdate = None):
@ -67,18 +69,29 @@ def runtest(device_prefs, testname, options, apk=None, appname = None,
capture_result['file'] = capture_file capture_result['file'] = capture_file
capture = videocapture.Capture(capture_file) capture = videocapture.Capture(capture_file)
capture_result['capture_fps'] = capture.fps capture_result['captureFPS'] = capture.fps
if stableframecapture: if stableframecapture:
capture_result['stableframetime'] = eideticker.get_stable_frame_time(capture) capture_result['timetostableframe'] = eideticker.get_stable_frame_time(capture)
else: else:
capture_result.update( capture_result.update(
eideticker.get_standard_metrics(capture, testlog.actions)) eideticker.get_standard_metrics(capture, testlog.actions))
if options.outputdir: if options.outputdir:
video_path = os.path.join('videos', 'video-%s.webm' % time.time()) # video
video_file = os.path.join(options.outputdir, video_path) video_relpath = os.path.join('videos', 'video-%s.webm' % time.time())
open(video_file, 'w').write(capture.get_video().read()) video_path = os.path.join(options.outputdir, video_relpath)
capture_result['video'] = video_path open(video_path, 'w').write(capture.get_video().read())
capture_result['video'] = video_relpath
# framediff
framediff_relpath = os.path.join('framediffs', 'framediff-%s.json' % time.time())
framediff_path = os.path.join(options.outputdir, framediff_relpath)
with open(framediff_path, 'w') as f:
framediff = videocapture.get_framediff_sums(capture)
f.write(json.dumps({ 'diffsums': framediff }))
capture_result['frameDiff'] = framediff_relpath
if options.enable_profiling: if options.enable_profiling:
capture_result['profile'] = profile_file capture_result['profile'] = profile_file
@ -91,7 +104,7 @@ def runtest(device_prefs, testname, options, apk=None, appname = None,
if options.devicetype == "b2g": if options.devicetype == "b2g":
# FIXME: get information from sources.xml and application.ini on # FIXME: get information from sources.xml and application.ini on
# device, as we do in update-dashboard.py # device, as we do in update-dashboard.py
display_key = "FirefoxOS" display_key = appkey = "FirefoxOS"
else: else:
appkey = appname appkey = appname
if appdate: if appdate:
@ -109,7 +122,7 @@ def runtest(device_prefs, testname, options, apk=None, appname = None,
if not options.no_capture: if not options.no_capture:
if stableframecapture: if stableframecapture:
print " Times to first stable frame (seconds):" print " Times to first stable frame (seconds):"
print " %s" % map(lambda c: c['stableframetime'], capture_results) print " %s" % map(lambda c: c['timetostableframe'], capture_results)
print print
else: else:
print " Number of unique frames:" print " Number of unique frames:"
@ -143,7 +156,7 @@ def runtest(device_prefs, testname, options, apk=None, appname = None,
print print
if options.outputdir: if options.outputdir:
outputfile = os.path.join(options.outputdir, "metric-test-%s.json" % time.time()) outputfile = os.path.join(options.outputdir, "metric.json")
resultdict = { 'title': testname, 'data': {} } resultdict = { 'title': testname, 'data': {} }
if os.path.isfile(outputfile): if os.path.isfile(outputfile):
resultdict.update(json.loads(open(outputfile).read())) resultdict.update(json.loads(open(outputfile).read()))
@ -164,7 +177,7 @@ def main(args=sys.argv[1:]):
help = "number of runs (default: 1)") help = "number of runs (default: 1)")
parser.add_option("--output-dir", action="store", parser.add_option("--output-dir", action="store",
type="string", dest="outputdir", type="string", dest="outputdir",
help="output results to json file") help="output results to web site")
parser.add_option("--no-capture", action="store_true", parser.add_option("--no-capture", action="store_true",
dest = "no_capture", dest = "no_capture",
help = "run through the test, but don't actually " help = "run through the test, but don't actually "
@ -222,6 +235,35 @@ def main(args=sys.argv[1:]):
device_prefs = eideticker.getDevicePrefs(options) device_prefs = eideticker.getDevicePrefs(options)
if options.outputdir:
for dirname in [ options.outputdir,
os.path.join(options.outputdir, 'css'),
os.path.join(options.outputdir, 'js'),
os.path.join(options.outputdir, 'videos'),
os.path.join(options.outputdir, 'framediffs'),
]:
if not os.path.exists(dirname):
os.makedirs(dirname)
for filename in [
'metric.html',
'framediff-view.html',
'css/bootstrap.min.css',
'js/ICanHaz.min.js',
'js/SS.min.js',
'js/common.js',
'js/framediff.js',
'js/jquery-1.7.1.min.js',
'js/jquery.flot.axislabels.js',
'js/jquery.flot.js',
'js/jquery.flot.stack.js',
'js/metric.js' ]:
if filename == 'metric.html':
outfilename = 'index.html'
else:
outfilename = filename
shutil.copyfile(os.path.join(DASHBOARD_DIR, filename),
os.path.join(options.outputdir, outfilename))
if options.devicetype == "b2g": if options.devicetype == "b2g":
runtest(device_prefs, testname, options) runtest(device_prefs, testname, options)
elif appnames: elif appnames:

Просмотреть файл

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Eideticker Dashboard</title> <title>Frame Difference View</title>
<link href="css/bootstrap.min.css" rel="stylesheet"> <link href="css/bootstrap.min.css" rel="stylesheet">
@ -70,6 +70,7 @@
<script src="js/jquery.flot.stack.js"></script> <script src="js/jquery.flot.stack.js"></script>
<script src="js/jquery.flot.axislabels.js"></script> <script src="js/jquery.flot.axislabels.js"></script>
<script src="js/jquery.flot.navigate.js"></script> <script src="js/jquery.flot.navigate.js"></script>
<script src="js/common.js"></script>
<script src="js/framediff.js"></script> <script src="js/framediff.js"></script>
<script id="pageHeader" type="text/html"> <script id="pageHeader" type="text/html">

Просмотреть файл

@ -112,6 +112,7 @@
<script src="js/jquery.flot.axislabels.js"></script> <script src="js/jquery.flot.axislabels.js"></script>
<script src="js/jquery.flot.hiddengraphs.js"></script> <script src="js/jquery.flot.hiddengraphs.js"></script>
<script src="js/jquery.flot.navigate.js"></script> <script src="js/jquery.flot.navigate.js"></script>
<script src="js/common.js"></script>
<script src="js/index.js"></script> <script src="js/index.js"></script>
<script id="graph" type="text/html"> <script id="graph" type="text/html">

1
src/dashboard/js/bootstrap.min.js поставляемый Symbolic link
Просмотреть файл

@ -0,0 +1 @@
../../bootstrap/js/bootstrap.min.js

Просмотреть файл

@ -0,0 +1,32 @@
// various utility functions used by all eideticker web interface pages
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
if (!match)
return null;
return decodeURIComponent(match[1].replace(/\+/g, ' ')).replace(
/[\"\']/g, ' ');
}
function parseDate(datestr) {
var parsed = datestr.split("-");
var year = parsed[0];
var month = parsed[1] - 1; //Javascript months index from 0 instead of 1
var day = parsed[2];
return Date.UTC(year, month, day);
}
var measures = {
'checkerboard': { 'shortDesc': 'Checkerboard',
'longDesc': 'The measure is the sum of the percentages of frames that are checkerboarded over the entire capture. Lower values are better.' },
'uniqueframes': { 'shortDesc': 'Unique frames',
'longDesc': 'The measure is a calculation of the average number of UNIQUE frames per second of capture. The theoretical maximum is 60 fps (which is what we are capturing), but note that if there periods where the page being captured is unchanging this number may be aritifically low.' },
'fps': { 'shortDesc': 'Frames per second',
'longDesc': 'The measure is a calculation of the average number of UNIQUE frames per second of capture. The theoretical maximum is 60 fps (which is what we are capturing), but note that if there periods where the page being captured is unchanging this number may be aritifically low.' },
'timetostableframe': { 'shortDesc': 'Time to first stable frame (seconds)',
'longDesc': 'The time to the first frame of the capture where the image is stable (i.e. mostly unchanging). This is a startup metric that indicates roughly when things have stopped loading. Lower values are better.' },
'timetoresponse': { 'shortDesc': 'Time to visible response (seconds)',
'longDesc': 'Time between event being first sent to device and an observable response. A long pause may indicate that the application is unresponsive.' }
}

Просмотреть файл

@ -1,15 +1,6 @@
"use strict"; "use strict";
$(function() { $(function() {
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
if (!match)
return null;
return decodeURIComponent(match[1].replace(/\+/g, ' ')).replace(
/[\"\']/g, ' ');
}
function render(diffsums, actions) { function render(diffsums, actions) {
var seriesList = []; var seriesList = [];
var currentSeries = null; var currentSeries = null;
@ -20,6 +11,9 @@ $(function() {
var i = 0.0; var i = 0.0;
var actionCount = {}; var actionCount = {};
var fps = getParameterByName('fps');
if (!fps) fps = 60;
diffsums.forEach(function(diffsum) { diffsums.forEach(function(diffsum) {
// if we have a current action, check to make sure // if we have a current action, check to make sure
// we're still within it // we're still within it
@ -70,7 +64,7 @@ $(function() {
lastSeries = null; lastSeries = null;
} }
currentSeries.data.push([ i, diffsum ]); currentSeries.data.push([ i, diffsum ]);
i+=(1.0/60.0); i+=(1.0/fps);
}); });
if (currentSeries) if (currentSeries)

Просмотреть файл

@ -8,29 +8,6 @@ function getResourceURL(path) {
return serverPrefix + path; return serverPrefix + path;
} }
function parseDate(datestr) {
var parsed = datestr.split("-");
var year = parsed[0];
var month = parsed[1] - 1; //Javascript months index from 0 instead of 1
var day = parsed[2];
return Date.UTC(year, month, day);
}
var measures = {
'checkerboard': { 'shortDesc': 'Checkerboard',
'longDesc': 'The measure is the sum of the percentages of frames that are checkerboarded over the entire capture. Lower values are better.' },
'uniqueframes': { 'shortDesc': 'Unique frames',
'longDesc': 'The measure is a calculation of the average number of UNIQUE frames per second of capture. The theoretical maximum is 60 fps (which is what we are capturing), but note that if there periods where the page being captured is unchanging this number may be aritifically low.' },
'fps': { 'shortDesc': 'Frames per second',
'longDesc': 'The measure is a calculation of the average number of UNIQUE frames per second of capture. The theoretical maximum is 60 fps (which is what we are capturing), but note that if there periods where the page being captured is unchanging this number may be aritifically low.' },
'timetostableframe': { 'shortDesc': 'Time to first stable frame (seconds)',
'longDesc': 'The time to the first frame of the capture where the image is stable (i.e. mostly unchanging). This is a startup metric that indicates roughly when things have stopped loading. Lower values are better.' },
'timetoresponse': { 'shortDesc': 'Time to visible response (seconds)',
'longDesc': 'Time between event being first sent to device and an observable response. A long pause may indicate that the application is unresponsive.' }
}
function updateContent(testInfo, deviceId, testId, measureId) { function updateContent(testInfo, deviceId, testId, measureId) {
$.getJSON(getResourceURL(deviceId + '/' + testId + '.json'), function(dict) { $.getJSON(getResourceURL(deviceId + '/' + testId + '.json'), function(dict) {
if (!dict || !dict['testdata']) { if (!dict || !dict['testdata']) {

Просмотреть файл

@ -1,24 +1,19 @@
"use strict"; "use strict";
function getParameterByName(name) { var availableMeasures = [];
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function updateContent(title, measure) { function updateContent(title, measure) {
var measureDescription; var measureDescription = measures[measure].longDesc;
if (measure === "checkerboard") {
measureDescription = 'The measure is the sum of the percentages of frames that are checkerboarded over the entire capture. Lower values are better.';
} else if (measure === "fps") {
measureDescription = 'The measure is a calculation of the average number of UNIQUE frames per second of capture. The theoretical maximum is 60 fps (which is what we are capturing), but note that if there periods where the page being captured is unchanging this number may be artifically low.';
} else {
measureDescription = 'The measure is a calculation of the total number of UNIQUE frames in the capture. Higher values generally indicate more fluid animations.';
}
$('#content').html(ich.graph({'title': title, $('#content').html(ich.graph({'title': title,
'measureDescription': measureDescription 'measureDescription': measureDescription,
'measures': availableMeasures.map(
function(measureId) {
return { 'id': measureId,
'desc': measures[measureId].shortDesc
};
})
})); }));
$('#measure-'+measure).attr("selected", "true"); $('#measure-'+measure).attr("selected", "true");
$('#measure').change(function() { $('#measure').change(function() {
@ -66,7 +61,7 @@ function updateGraph(rawdata, measure) {
rawdata[appname].forEach(function(sample) { rawdata[appname].forEach(function(sample) {
series.data.push([ barPosition, sample[measure] ]); series.data.push([ barPosition, sample[measure] ]);
metadataHash[seriesIndex].push({'videoURL': sample.video, 'appDate': sample.appdate, 'revision': sample.revision, 'buildId': sample.buildid }); metadataHash[seriesIndex].push({'videoURL': sample.video, 'appDate': sample.appdate, 'revision': sample.revision, 'buildId': sample.buildid, 'frameDiff': sample.frameDiff, 'fps': sample.captureFPS });
barPosition++; barPosition++;
}); });
graphdata.push(series); graphdata.push(series);
@ -106,7 +101,9 @@ function updateGraph(rawdata, measure) {
'appDate': metadata.appDate, 'appDate': metadata.appDate,
'revision': metadata.revision, 'revision': metadata.revision,
'buildId': metadata.buildId, 'buildId': metadata.buildId,
'measureValue': Math.round(100.0*item.datapoint[1])/100.0 'measureValue': Math.round(100.0*item.datapoint[1])/100.0,
'frameDiff': metadata.frameDiff,
'fps': metadata.fps
})); }));
$('#video').css('width', $('#video').parent().width()); $('#video').css('width', $('#video').parent().width());
$('#video').css('max-height', $('#graph-container').height()); $('#video').css('max-height', $('#graph-container').height());
@ -119,16 +116,31 @@ function updateGraph(rawdata, measure) {
} }
$(function() { $(function() {
var router = Router({ '/(checkerboard|fps|uniqueframes)': {
on: function(measure) {
var jsonFile = getParameterByName('data'); var dataFilename = getParameterByName('data');
$.getJSON(jsonFile, function(data) { if (!dataFilename)
console.log(data); dataFilename = 'metric.json';
updateContent(data['title'], measure);
updateGraph(data['data'], measure); $.getJSON(dataFilename, function(data) {
// figure out which measures could apply to this graph
Object.keys(data['data']).forEach(function(appname) {
data['data'][appname].forEach(function(sample) {
Object.keys(sample).forEach(function(potentialMeasure) {
if (jQuery.inArray(potentialMeasure, Object.keys(measures)) !== -1 &&
jQuery.inArray(potentialMeasure, availableMeasures) === -1) {
availableMeasures.push(potentialMeasure);
}
});
}); });
} });
} }).init('/fps');
var defaultMeasure = availableMeasures[0];
var router = Router({ '/:measureId': {
on: function(measureId) {
updateContent(data['title'], measureId);
updateGraph(data['data'], measureId);
}
} }).init('/' + defaultMeasure);
});
}); });

Просмотреть файл

@ -37,12 +37,12 @@
</div> </div>
<script src="js/jquery-1.7.1.min.js"></script> <script src="js/jquery-1.7.1.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/ICanHaz.min.js"></script> <script src="js/ICanHaz.min.js"></script>
<script src="js/SS.min.js"></script> <script src="js/SS.min.js"></script>
<script src="js/jquery.flot.js"></script> <script src="js/jquery.flot.js"></script>
<script src="js/jquery.flot.stack.js"></script> <script src="js/jquery.flot.stack.js"></script>
<script src="js/jquery.flot.axislabels.js"></script> <script src="js/jquery.flot.axislabels.js"></script>
<script src="js/common.js"></script>
<script src="js/metric.js"></script> <script src="js/metric.js"></script>
<script id="graph" type="text/html"> <script id="graph" type="text/html">
@ -58,13 +58,15 @@
<div id="measure-form"> <div id="measure-form">
<form> <form>
<select id="measure" type="text"> <select id="measure" type="text">
<option id="measure-fps" value="fps">Frames Per Second</option> {{#measures}}
<option id="measure-uniqueframes" value="uniqueframes">Unique Frames</option> <option id="measure-{{id}}" value="{{id}}">{{desc}}</option>
<option id="measure-checkerboard" value="checkerboard">Checkerboard</option> {{/measures}}
</select> </select>
<span class="help-inline"> <p>
{{measureDescription}} <span class="help-inline">
</span> {{measureDescription}}
</span>
</p>
</form> </form>
</div> </div>
<p class="help-block"><strong>Hint:</strong> Click on the bars to see a video of the testrun!</p> <p class="help-block"><strong>Hint:</strong> Click on the bars to see a video of the testrun!</p>
@ -107,6 +109,9 @@
<dt>{{measureName}}</dt> <dt>{{measureName}}</dt>
<dd>{{measureValue}}</dd> <dd>{{measureValue}}</dd>
</dl> </dl>
{{#frameDiff}}
<p><a href="framediff-view.html?title=Frame Difference&video={{videoURL}}&framediff={{frameDiff}}&fps={{fps}}" target="_blank">Frame difference view</a></p>
{{/frameDiff}}
</div> </div>
</script> </script>