#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
###############################################################################
# #
# Copyright (c) 2017 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.web module of the BEAT platform. #
# #
# Commercial License Usage #
# Licensees holding valid commercial BEAT licenses may use this file in #
# accordance with the terms contained in a written agreement between you #
# and Idiap. For further information contact tto@idiap.ch #
# #
# Alternatively, this file may be used under the terms of the GNU Affero #
# Public License version 3 as published by the Free Software and appearing #
# in the file LICENSE.AGPL included in the packaging of this file. #
# The BEAT platform is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
# or FITNESS FOR A PARTICULAR PURPOSE. #
# #
# You should have received a copy of the GNU Affero Public License along #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
from datetime import datetime
from django.db import models
from ...algorithms.models import Algorithm
from ...backend.models import Environment
from ...backend.models import Queue
# ----------------------------------------------------------
[docs]class BlockManager(models.Manager):
[docs] def get_by_natural_key(
self,
name,
experiment_author,
toolchain_author,
toolchain_name,
toolchain_version,
experiment_name,
):
return self.get(
name=name,
experiment__author__username=experiment_author,
experiment__toolchain__author__username=toolchain_author,
experiment__toolchain__name=toolchain_name,
experiment__toolchain__version=toolchain_version,
experiment__name=experiment_name,
)
# ----------------------------------------------------------
[docs]class Block(models.Model):
PENDING = "N"
PROCESSING = "P"
DONE = "C"
FAILED = "F"
CANCELLED = "L"
STATUS = (
(PENDING, "Pending"),
(PROCESSING, "Processing"),
(DONE, "Done"),
(FAILED, "Failed"),
(CANCELLED, "Cancelled"),
)
experiment = models.ForeignKey(
"Experiment", related_name="blocks", 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=PENDING)
analyzer = models.BooleanField(default=False)
algorithm = models.ForeignKey(
Algorithm, related_name="blocks", on_delete=models.CASCADE
)
creation_date = models.DateTimeField(null=True, blank=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
)
queue = models.ForeignKey(
Queue, related_name="blocks", null=True, on_delete=models.SET_NULL
)
required_slots = models.PositiveIntegerField(default=1)
channel = models.CharField(
max_length=200,
default="",
blank=True,
help_text="Synchronization channel within the toolchain",
)
# relationship to blocks to which this block depends on
dependencies = models.ManyToManyField(
"self", related_name="dependents", blank=True, symmetrical=False,
)
# order of this block within the experiment - useful for the `backup'
# command, so we can dump the blocks in the right dependence order
execution_order = models.PositiveIntegerField(null=True, blank=True)
objects = BlockManager()
class Meta:
unique_together = ("experiment", "name")
# setup ordering so that the dump order respects self dependencies
ordering = ["experiment_id", "execution_order"]
def __str__(self):
return (
self.experiment.fullname()
+ ", "
+ self.name
+ " (%s)" % self.get_status_display()
)
[docs] def natural_key(self):
return (
self.name,
self.experiment.author.username,
self.experiment.toolchain.author.username,
self.experiment.toolchain.name,
self.experiment.toolchain.version,
self.experiment.name,
)
natural_key.dependencies = ["experiments.experiment"]
[docs] def save(self, *args, **kwargs):
# Ensure that the state of the block is consistent, just in case, but
# we expect the caller to do it properly
if self.status == Block.PENDING:
try:
self.results.all().delete()
except Exception: # nosec
pass
self.start_date = None
self.end_date = None
elif self.status == Block.PROCESSING:
if self.start_date is None:
self.start_date = datetime.now()
self.end_date = None
else:
if self.end_date is None:
self.end_date = datetime.now()
super(Block, self).save(*args, **kwargs)
# Accessors for statistics
def __return_first__(self, field, default=None):
return getattr(self.outputs.first(), field, default)
[docs] def first_cache(self):
return self.outputs.first()
[docs] def error_report(self):
return self.__return_first__("error_report")
[docs] def stdout(self):
return self.__return_first__("stdout")
[docs] def stderr(self):
return self.__return_first__("stderr")
[docs] def speed_up_real(self):
return self.__return_first__("speed_up_real")
[docs] def speed_up_maximal(self):
return self.__return_first__("speed_up_maximal")
[docs] def linear_execution_time(self):
return self.__return_first__("linear_execution_time")
[docs] def queuing_time(self):
return self.__return_first__("queuing_time")
[docs] def cpu_time(self):
return self.__return_first__("cpu_time")
[docs] def max_memory(self):
return self.__return_first__("max_memory")
[docs] def data_read_size(self):
return self.__return_first__("data_read_size")
[docs] def data_read_nb_blocks(self):
return self.__return_first__("data_read_nb_blocks")
[docs] def data_read_time(self):
return self.__return_first__("data_read_time")
[docs] def data_written_size(self):
return self.__return_first__("data_written_size")
[docs] def data_written_nb_blocks(self):
return self.__return_first__("data_written_nb_blocks")
[docs] def data_written_time(self):
return self.__return_first__("data_written_time")
# Accessor for results
results = property(lambda self: self.__return_first__("results"))
[docs] def done(self):
"""Says whether the block has finished or not"""
return self.status not in (Block.PENDING, Block.PROCESSING)
[docs] def is_runnable(self):
"""Checks if a block is runnable presently"""
return all([k.status == Block.DONE for k in self.dependencies.all()])
[docs] def set_canceled(self, end_date=None):
"""Update the block state to canceled
Parameters:
end_date (datetime): If provided sets the end_date otherwise
datetime.now() will be used.
"""
self.status = Block.CANCELLED
if end_date is None:
end_date = datetime.now()
self.end_date = end_date
if self.start_date is None:
self.start_date = self.end_date
self.save()
[docs] def set_failed(self, end_date):
"""Update the block state to failed
Parameters:
end_date (datetime): end date on failure
"""
self.status = Block.FAILED
self.end_date = end_date
self.save()