Commit cb243beb authored by Philip ABBET's avatar Philip ABBET
Browse files

[experiments] Refactoring of the 'Experiment results' page

parent 208d89dc
......@@ -26,6 +26,7 @@
###############################################################################
from rest_framework import serializers
from django.contrib.humanize.templatetags.humanize import naturaltime
from ..common.serializers import ShareableSerializer
from ..common.fields import JSONSerializerField
......@@ -198,18 +199,22 @@ class ExperimentResultsSerializer(ShareableSerializer):
attestation = serializers.IntegerField(source='attestation.number')
blocks_status = serializers.SerializerMethodField()
execution_info = serializers.SerializerMethodField()
execution_order = serializers.SerializerMethodField()
status = serializers.CharField(source="get_status_display")
results = serializers.SerializerMethodField()
errors = serializers.SerializerMethodField()
html_description = serializers.SerializerMethodField()
description = serializers.SerializerMethodField()
declaration = JSONSerializerField()
display_start_date = serializers.SerializerMethodField()
display_end_date = serializers.SerializerMethodField()
class Meta(ShareableSerializer.Meta):
model = Experiment
default_fields = ['started', 'done', 'failed', 'blocks_status',
default_fields = ['started', 'done', 'failed', 'status', 'blocks_status',
'results', 'attestation', 'declaration',
'errors', 'sharing', 'accessibility',
'execution_info']
'execution_info', 'execution_order']
def get_started(self, obj):
return obj.status not in [Experiment.PENDING, Experiment.SCHEDULED, Experiment.CANCELING]
......@@ -229,6 +234,8 @@ class ExperimentResultsSerializer(ShareableSerializer):
results[block.name] = 'pending'
elif block.status == Block.FAILED:
results[block.name] = 'failed'
elif block.status == Block.CANCELLED:
results[block.name] = 'cancelled'
else:
results[block.name] = 'processing'
return results
......@@ -244,6 +251,12 @@ class ExperimentResultsSerializer(ShareableSerializer):
}
return results
def get_execution_order(self, obj):
results = []
for block in obj.blocks.order_by('execution_order').iterator():
results.append(block.name)
return results
def get_results(self, obj):
results = {}
for k in obj.blocks.filter(analyzer=True).all():
......@@ -263,3 +276,21 @@ class ExperimentResultsSerializer(ShareableSerializer):
def get_description(self, obj):
return obj.description.decode('utf-8')
def get_display_start_date(self, obj):
if obj.start_date is None:
return None
return {
'date': obj.start_date.strftime("%b %d, %Y, %-H:%M"),
'natural': naturaltime(obj.start_date),
}
def get_display_end_date(self, obj):
if obj.end_date is None:
return None
return {
'date': obj.end_date.strftime("%b %d, %Y, %-H:%M"),
'natural': naturaltime(obj.end_date),
}
......@@ -3375,3 +3375,296 @@ beat.experiments.panels.BlockParameters.prototype._createStringControls = functi
$(select).addClass('default');
});
}
/********************** CLASS: ExecutionDetails ************************************/
//----------------------------------------------------------------------
// Constructor
//
// panel_id: ID of the DOM element to use
//----------------------------------------------------------------------
beat.experiments.panels.ExecutionDetails = function(panel_id, url_prefix) {
this.id = panel_id;
this.element = $('#' + panel_id)[0];
this.configuration = null;
this.url_prefix = url_prefix;
}
//----------------------------------------------------------------------
// Setup the content of the panel from an experiment configuration
//----------------------------------------------------------------------
beat.experiments.panels.ExecutionDetails.prototype.setup = function(
configuration, templates, urls, experiment_results)
{
this.configuration = configuration;
var rendered = this._createGlobalsSection(templates, urls);
rendered += this._createDatasetsSection(templates, urls);
rendered += this._createBlockSections(templates, urls, experiment_results);
$(this.element).html(rendered);
}
//----------------------------------------------------------------------
// Update the content of the panel from the experiment results
//----------------------------------------------------------------------
beat.experiments.panels.ExecutionDetails.prototype.update = function(experiment_results)
{
for (var i = 0; i < experiment_results.execution_order.length; ++i) {
var block_name = experiment_results.execution_order[i];
var status = experiment_results.blocks_status[block_name];
var div = $(this.element).find('#block-' + block_name);
if (div.data('beat-status') != status) {
div.data('beat-status', status);
div.find('#block-' + block_name + '-status-icon')[0].className = this._getIcon(status);
if ((status == 'failed') || (status == 'generated')) {
var execution_info = experiment_results.execution_info[block_name];
var span = div.find('#block-' + block_name + '-execution-time');
span.text(' @ ' + execution_info.linear_execution_time.toFixed(2) + 's')
span.show();
var li = div.find('#block-' + block_name + '-linear-execution-time');
li.text('Total execution time: ' + execution_info.linear_execution_time.toFixed(2) + 's')
li.show();
if (this.configuration.blockNbSlots(block_name) > 1) {
li = div.find('#block-' + block_name + '-speed-up-real');
li.text('Slots: ' + this.configuration.blockNbSlots(block_name) + ' (speed-up achieved: ' + execution_info.speed_up_real.toFixed(1) + 'x')
li.show();
li = div.find('#block-' + block_name + '-speed-up-maximal');
li.text('Maximum speed-up achievable: ' + execution_info.speed_up_maximal.toFixed(1) + 'x')
li.show();
} else {
div.find('#block-' + block_name + '-slots').show();
}
li = div.find('#block-' + block_name + '-queuing-time');
li.text('Queuing time: ' + execution_info.queuing_time.toFixed(2) + 's')
li.show();
if (experiment_results.errors) {
for (var j = 0; j < experiment_results.errors.length; ++j) {
if (experiment_results.errors[j].block == block_name) {
if (experiment_results.errors[j].details) {
li = div.find('#block-' + block_name + '-error-report');
li.find('.console-output').text(experiment_results.errors[j].details);
li.show();
}
if (experiment_results.errors[j].stdout) {
li = div.find('#block-' + block_name + '-stdout');
li.find('.console-output').text(experiment_results.errors[j].stdout);
li.show();
}
if (experiment_results.errors[j].stderr) {
li = div.find('#block-' + block_name + '-stderr');
li.find('.console-output').text(experiment_results.errors[j].stderr);
li.show();
}
break;
}
}
}
}
}
}
}
//----------------------------------------------------------------------
// Creates the 'Global Parameters' section
//----------------------------------------------------------------------
beat.experiments.panels.ExecutionDetails.prototype._createGlobalsSection = function(
templates, urls)
{
var template = $(templates.globals).html();
Mustache.parse(template);
var environment = this.configuration.defaultEnvironment();
var tags = {
environment: {
name: environment.name,
version: environment.version,
url: urls.environment.replace('NAME', environment.name).replace('VERSION', environment.version),
},
queue: this.configuration.defaultQueue(),
algorithms: [],
}
var algorithm_names = this.configuration.algorithms();
for (var i = 0; i < algorithm_names.length; ++i) {
var algorithm_name = algorithm_names[i];
var algorithm_globals = this.configuration.algorithmGlobals(algorithm_name);
var items = [];
var parameters = Object.keys(algorithm_globals);
for (var j = 0; j < parameters.length; ++j) {
items.push({
name: parameters[j],
value: algorithm_globals[parameters[j]],
});
}
if (items.length > 0) {
tags.algorithms.push({
name: algorithm_name,
url: urls.algorithm.replace('AUTHOR/NAME/0', algorithm_name),
items: items,
});
}
}
return Mustache.render(template, tags);
}
//----------------------------------------------------------------------
// Creates the 'Datasets' section
//----------------------------------------------------------------------
beat.experiments.panels.ExecutionDetails.prototype._createDatasetsSection = function(
templates, urls)
{
var template = $(templates.datasets).html();
Mustache.parse(template);
var tags = {
datasets: [],
}
var dataset_names = this.configuration.datasetNames();
for (var i = 0; i < dataset_names.length; ++i) {
var dataset_name = dataset_names[i];
var dataset = this.configuration.dataset(dataset_name);
tags.datasets.push({
block_name: dataset_name,
url: urls.database.replace('NAME/0', dataset.database),
name: dataset.database,
protocol: dataset.protocol,
set: dataset.set,
});
}
return Mustache.render(template, tags);
}
//----------------------------------------------------------------------
// Creates the 'Block' sections
//----------------------------------------------------------------------
beat.experiments.panels.ExecutionDetails.prototype._createBlockSections = function(
templates, urls, results)
{
var template = $(templates.block).html();
Mustache.parse(template);
var tags = {
blocks: [],
}
for (var i = 0; i < results.execution_order.length; ++i) {
var block_name = results.execution_order[i];
var block = this.configuration.component(block_name);
var execution_info = results.execution_info[block_name];
var status = results.blocks_status[block_name];
var environment = this.configuration.blockEnvironment(block_name);
var entry = {
name: block_name,
analyzer: this.configuration.is_analyzer(block_name),
algorithm: block.algorithm,
url: urls.algorithm.replace('AUTHOR/NAME/0', block.algorithm),
status: status,
failed: status == 'failed',
cached: status == 'generated',
cached_or_failed: (status == 'failed') || (status == 'generated'),
nb_slots: (this.configuration.blockNbSlots(block_name) > 1 ? this.configuration.blockNbSlots(block_name) : 0),
environment: {
name: environment.name,
version: environment.version,
url: urls.environment.replace('NAME', environment.name).replace('VERSION', environment.version),
},
queue: this.configuration.blockQueue(block_name),
has_parameters: this.configuration.isParametrized(block_name),
parameters: [],
queuing_time: execution_info.queuing_time.toFixed(2),
linear_execution_time: execution_info.linear_execution_time.toFixed(2),
speed_up_real: execution_info.speed_up_real.toFixed(1),
speed_up_maximal: execution_info.speed_up_maximal.toFixed(1),
error_report: null,
stdout: null,
stderr: null,
icon: null,
}
if (block.parameters !== undefined) {
var parameters = Object.keys(block.parameters);
for (var j = 0; j < parameters.length; ++j) {
entry.parameters.push({
name: parameters[j],
value: block.parameters[parameters[j]],
});
}
}
if (results.errors) {
for (var j = 0; j < results.errors.length; ++j) {
if (results.errors[j].block == block_name) {
entry.error_report = results.errors[j].details;
entry.stdout = results.errors[j].stdout;
entry.stderr = results.errors[j].stderr;
break;
}
}
}
entry.icon = this._getIcon(entry.status);
tags.blocks.push(entry);
}
return Mustache.render(template, tags);
}
//---------------------------------------
beat.experiments.panels.ExecutionDetails.prototype._getIcon = function(status) {
if (status == 'pending')
return "icon-scheduled fa fa-play";
else if (status == 'generated')
return "icon-done fa fa-check";
else if (status == 'failed')
return "icon-failed fa fa-bug";
else if (status == 'processing')
return "icon-running fa fa-spin fa-refresh";
else if (status == 'failed')
return "icon-failed fa fa-bug";
else if (status == 'cancelled')
return "icon-failed fa fa-power-off";
else
return "icon-pending fa fa-asterisk";
}
......@@ -2219,114 +2219,3 @@ beat.experiments.utils.modal_rename = function (userid, current_name, list_url,
});
});
}
/**
* Updates the toolchain viewer with status information from the blocks
*
* Parameters:
*
* viewer (beat.toolchains.viewer.ToolchainViewer): An instance of the BEAT
* toolchain viewer that will be used to update the blocks graphically
* objects (jQuery.object): A jQuery object that contains the block
* information for the current experiment
* dt_block_name (String): The name of the data object inside each block
* where the current block name is stored.
* dt_block_status (String): The name of the data object inside each block
* where the current block state is stored.
*/
beat.experiments.utils.update_viewer = function (viewer, objects, dt_block_name, dt_block_status) {
//gather block information
block_status = {};
objects.each(function(idx) {
var bk_name = $(this).data(dt_block_name);
if (!bk_name) return;
var bk_status = $(this).data(dt_block_status);
if (bk_status === 'cached') bk_status = 'generated'; //< viewer quirk
block_status[bk_name] = bk_status;
});
viewer.updateBlocksStatus(block_status);
}
/**
* Updates blocks of a running experiment on the experiment "view" page
*
* Parameters:
*
* url (String): The url to probe for new versions of the blocks to replace
* st (String): A jQuery selector for the div containing the data item
* 'beat-experiment-status', that will be probed for the current experiment
* status. The blocks are only updated if the current experiment status is
* one of 'Scheduled', 'Running' or 'Canceling'.
* dt (String): The name of the data object inside the `st` selector where
* the current experiment state is stored.
* unveil (String): A jQuery selector of objects to unveil in case we're
* updating and the experiment is finished
* objects (String): A jQuery selector that will make us search for the
* replacement blocks on the `url' above.
* dt_block_name (String): The name of the data object inside each block
* where the current block name is stored.
* dt_block_status (String): The name of the data object inside each block
* where the current block state is stored.
* viewer (beat.toolchains.viewer.ToolchainViewer): An instance of the BEAT
* toolchain viewer that will be used to update the blocks graphically
* interval (Int): A timeout in milliseconds to use for updating the block
* representations and the toolchain viewer.
*/
beat.experiments.utils.update_blocks = function (url, st, dt, unveil, objects, dt_block_name, viewer, interval) {
var _status = $(st).data(dt);
if (_status === 'Failed') {
beat.experiments.utils.update_viewer(viewer, $(objects), dt_block_name, dt);
return;
}
//only updates if in one of the "interesting" states
var interesting_states = ['Scheduled', 'Running', 'Canceling'];
if (interesting_states.indexOf(_status) <= -1) return;
function _do_update() {
var _status = $(st).data(dt);
if (interesting_states.indexOf(_status) <= -1) {
//experiment changed status - should reload
$(unveil).show();
if (viewer.running) viewer.onExperimentDone();
if (_status === 'Failed') {
beat.experiments.utils.update_viewer(viewer, $(objects), dt_block_name,
dt);
}
return;
}
var d = $.get(url);
d.done(function(data) {
var parsed = $($.parseHTML(data));
$(objects).each(function(idx) {
var _self = $(this);
var r = parsed.find('#' + _self.attr('id'));
//only replaces if it changed
var old_status = _self.data(dt);
var new_status = r.data(dt);
if (r && old_status !== new_status) _self.replaceWith(r);
});
if (!viewer.running) viewer.onExperimentStarted();
beat.experiments.utils.update_viewer(viewer, $(objects), dt_block_name, dt);
});
}
//if we get to this point, we install the interval function
$.ajaxSetup({
beforeSend: function(xhr, settings) {
var csrftoken = $.cookie('csrftoken');
xhr.setRequestHeader('X-CSRFToken', csrftoken);
}
});
var timeout_id = window.setInterval(_do_update, interval);
}
......@@ -1491,6 +1491,15 @@ beat.toolchains.models.Configuration.prototype.setAlgorithmGlobal = function(alg
}
//----------------------------------------------------------------------------------------
// Retrieves the name of all the datasets
//----------------------------------------------------------------------------------------
beat.toolchains.models.Configuration.prototype.datasetNames = function()
{
return Object.keys(this.declaration.datasets);
}
//----------------------------------------------------------------------------------------
// Retrieves the name of all the blocks
//----------------------------------------------------------------------------------------
......@@ -2159,6 +2168,15 @@ beat.toolchains.models.Configuration.prototype.removeDefaultQueueListener = func
}
//----------------------------------------------------------------------------------------
// Indicates if a component is an analyzer
//----------------------------------------------------------------------------------------
beat.toolchains.models.Configuration.prototype.is_analyzer = function(name)
{
return this.declaration.analyzers[name] !== undefined;
}
//-----------------------------------------------
......
......@@ -134,6 +134,7 @@ packages = jquery#~1.11.3
bootstrap3-typeahead#~4.0.0
bootstrap-datepicker#~1.5.1
mousetrap#~1.5.3
mustache#2.3.0
raphael#~2.1.4
spectrum#~1.7.1
https://github.com/joshaven/string_score.git#~0.1.22
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment