Commit 1e94fed0 authored by Philip ABBET's avatar Philip ABBET
Browse files

Merge bugfixes from v1.2.2

parents 11a070cc acb846b0
Pipeline #12664 failed with stage
in 17 seconds
......@@ -239,9 +239,9 @@ class Experiment(Shareable):
#_____ Fields __________
author = models.ForeignKey(User, related_name='experiments',
on_delete=models.CASCADE)
toolchain = models.ForeignKey(Toolchain,
related_name='experiments', on_delete=models.CASCADE)
on_delete=models.CASCADE)
toolchain = models.ForeignKey(Toolchain, related_name='experiments',
on_delete=models.CASCADE)
name = models.CharField(max_length=200)
short_description = models.CharField(max_length=100, default='', blank=True, help_text=Messages['short_description'])
status = models.CharField(max_length=1, choices=STATUS, default=PENDING)
......@@ -577,7 +577,7 @@ class Experiment(Shareable):
b.analyzer=algorithm.analysis()
b.environment=env
b.queue=queue
b.required_slots=job_description['nb_slots']
# TEMPORARILY DISABLED: b.required_slots=job_description['nb_slots']
b.channel=job_description['channel']
b.save()
......@@ -911,21 +911,21 @@ class Block(models.Model):
)
experiment = models.ForeignKey(Experiment, related_name='blocks',
on_delete=models.CASCADE)
on_delete=models.CASCADE)
name = models.CharField(max_length=200)
command = models.TextField(null=True, blank=True)
status = models.CharField(max_length=1, choices=STATUS, default=NOT_CACHED)
analyzer = models.BooleanField(default=False)
algorithm = models.ForeignKey(Algorithm, related_name='blocks',
on_delete=models.CASCADE)
on_delete=models.CASCADE)
creation_date = models.DateTimeField(null=True, blank=True,
auto_now_add=True)
auto_now_add=True)
start_date = models.DateTimeField(null=True, blank=True)
end_date = models.DateTimeField(null=True, blank=True)
environment = models.ForeignKey(Environment, related_name='blocks',
null=True, on_delete=models.SET_NULL)
null=True, on_delete=models.SET_NULL)
queue = models.ForeignKey(Queue, related_name='blocks', null=True,
on_delete=models.SET_NULL)
on_delete=models.SET_NULL)
required_slots = models.PositiveIntegerField(default=1)
channel = models.CharField(max_length=200, default='', blank=True,
......
......@@ -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),
}
......@@ -3385,3 +3385,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";
}
......@@ -2205,134 +2205,4 @@ beat.experiments.utils.modal_rename = function(
],
});
});
};
/**
* 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);
};
}
......@@ -56,6 +56,12 @@ beat.toolchains.common.SELECTION_INPUT = 1;
beat.toolchains.common.SELECTION_OUTPUT = 2;
beat.toolchains.common.SELECTION_FULL = 3;
// Scrollbars
beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH = 15;
beat.toolchains.common.SCROLLBAR_WIDTH = 8;
beat.toolchains.common.SCROLLBAR_OFFSET = 4;
beat.toolchains.common.SCROLLBAR_MARGIN = 200;
/*********************************** UTILITY FUNCTIONS **********************************/
......
......@@ -75,12 +75,6 @@ beat.toolchains.editor.HOVERED_CONNECTION = 10;
beat.toolchains.editor.HOVERED_CONNECTION_WITH_CHANNELS = 11;
beat.toolchains.editor.HOVERED_INPUT_HANDLE_CONNECTION = 12;