diff --git a/simso/configuration/Configuration.py b/simso/configuration/Configuration.py index 5dc3cd6..c9628d1 100644 --- a/simso/configuration/Configuration.py +++ b/simso/configuration/Configuration.py @@ -9,7 +9,7 @@ from simso.core.Scheduler import SchedulerInfo from simso.core import Scheduler from simso.core.Task import TaskInfo from simso.core.Processor import ProcInfo -from simso.core.Fault import FaultInfo, FaultFunction +from simso.core.Fault import FaultInfo, FaultFactory from simso.generator.task_generator import gen_probabilistic_arrivals from simso.generator.task_generator import cdf @@ -63,6 +63,7 @@ class Configuration(object): self.penalty_preemption = parser.penalty_preemption self.penalty_migration = parser.penalty_migration self.fault_info_list = parser.fault_info_list + self.fault_data_fields = parser.fault_data_fields else: self.etm = "wcet" self.duration = 100000000 @@ -75,6 +76,7 @@ class Configuration(object): self._proc_info_list = [] self.proc_data_fields = {} self.fault_info_list = [] + self.fault_data_fields = {} self.memory_access_time = 100 self._scheduler_info = SchedulerInfo() self.calc_penalty_cache() @@ -193,20 +195,20 @@ class Configuration(object): "Activation date must be positive." # Period >= 0: - assert task.period >= 0, "Tasks' periods must be positives." + assert task.period > 0, "Tasks' periods must be positives." # Deadline >= 0: - assert task.deadline >= 0, "Tasks' deadlines must be positives." + assert task.deadline > 0, "Tasks' deadlines must be positives." # N_instr >= 0: assert task.n_instr >= 0, \ "A number of instructions must be positive." # WCET >= 0: - assert task.wcet >= 0, "WCET must be positive." + assert task.wcet > 0, "WCET must be positive." # ACET >= 0: - assert task.acet >= 0, "ACET must be positive." + assert task.acet > 0, "ACET must be positive." # ET-STDDEV >= 0: assert task.et_stddev >= 0, \ @@ -286,7 +288,7 @@ class Configuration(object): def add_task(self, name, identifier, task_type="Periodic", abort_on_miss=True, period=10, activation_date=0, - n_instr=0, mix=0.5, stack_file="", wcet=0, acet=0, pwcet=(0, 1.0), pmit=(0, 1.0), + n_instr=0, mix=0.5, stack_file="", wcet=1, acet=1, pwcet=(1, 1.0), pmit=(0, 1.0), et_stddev=0, deadline=10, base_cpi=1.0, followed_by=None, list_activation_dates=[], preemption_cost=0, data=None): """ @@ -320,9 +322,11 @@ class Configuration(object): self.proc_info_list.append(proc) return proc - def add_fault(self, fault_function, start_time, end_time): + def add_fault(self, fault_type: str, start_time, end_time, target, value=None, parameter=None): """ Helper method to create a FaultInfo and add it to the list of faults. """ - fault = FaultInfo(start_time, end_time, fault_function) - self.fault_info_list.append(fault) + fault_info = FaultInfo(fault_type, start_time, + end_time, target, value, parameter) + self.fault_info_list.append(fault_info) + return fault_info diff --git a/simso/core/Fault.py b/simso/core/Fault.py index 5ab60a9..ad14697 100644 --- a/simso/core/Fault.py +++ b/simso/core/Fault.py @@ -1,6 +1,8 @@ from simpy import Environment from typing import Callable from simso.core.Processor import ProcInfo +from simso.core.Monitor import Monitor +from simso.core.FaultEvent import ProcessorFailureEvent, VoltageChangeEvent, TimingChangeEvent class FaultFunction: @@ -18,72 +20,91 @@ class FaultInfo: The FaultType class is responsible for causing the error. """ - def __init__(self, start_time: float, end_time: float, fault_function: FaultFunction): + def __init__(self, fault_type: str, start_time: float, end_time: float, + fault_target, value, parameter: str): + self.fault_type = fault_type self.start_time = start_time self.end_time = end_time - self.fault_function = fault_function + self.fault_target = fault_target + self.value = value + self.parameter = parameter class Fault: """ - The Fault class is the simpy process for triggering a certain error. + The Fault base class is the simpy process for triggering a certain error. """ - def __init__(self, sim, fault_type: FaultInfo): - self._fault_type = fault_type + def __init__(self, sim, fault_info: FaultInfo, start_function: Callable, end_function: Callable): + self._start_function = start_function + self._end_function = end_function + self._fault_info = fault_info self._sim = sim + self._monitor = Monitor(name="Monitor" + fault_info.fault_type, + env=sim) self.process = self._sim.process(self.release()) + @property + def fault_info(self): + return self._fault_info + + @property + def duration_ms(self): + return self._fault_info.start_time - self._fault_info.end_time + + @property + def duration_cycles(self): + return (self._fault_info.start_time - self._fault_info.end_time) * self._sim.cycles_per_ms + def release(self): - yield self._sim.timeout(self._fault_type.start_time) + yield self._sim.timeout(self._fault_info.start_time * self._sim.cycles_per_ms) - self._fault_type.fault_function.start_function(self._sim) + self._start_function(self._sim) - yield self._sim.timeout(self._fault_type.end_time - - self._fault_type.start_time) + yield self._sim.timeout((self._fault_info.end_time - + self._fault_info.start_time) * self._sim.y) - self._fault_type.fault_function.end_function(self._sim) + self._end_function(self._sim) -class ProcessorFailure(FaultFunction): +# TODO: Does not work when only 1 processor in model, maybe use speed = 0 ? +class ProcessorFailure(Fault): """ The ProcessorFailure class simulates a complete stoppage of a processor. """ + fields = ['fault_target'] - def __init__(self, proc_info: ProcInfo): - super().__init__(self.stop_processor, self.start_processor) - self._proc_info = proc_info + def __init__(self, sim, fault_info): + super().__init__(sim, fault_info, self.stop_processor, self.start_processor) self._processor = None def stop_processor(self, model): self._processor = next( - p for p in model.processors if p.identifier == self._proc_info.identifier) + p for p in model.processors if p.identifier == self._fault_info.fault_target.identifier) self._processor.fail(self) + self._monitor.observe(ProcessorFailureEvent(self)) def start_processor(self, model): self._processor.fail() -class VoltageChange(FaultFunction): +class VoltageChange(Fault): - def __init__(self, proc_info_list, scaling: float): - super().__init__(self.start_voltage_change, self.end_voltage_change) - self._proc_info_list = proc_info_list - self._processor_list = [] - self._scaling = scaling + fields = ['fault_target', 'value'] + + def __init__(self, sim, fault_info): + super().__init__(sim, fault_info, self.start_voltage_change, self.end_voltage_change) + self._processor = None def start_voltage_change(self, model): - affected_ids = [p.identifier for p in self._proc_info_list] - self._processor_list = [ - p for p in model.processors if p.identifier in affected_ids] - for p in self._processor_list: - p.set_speed(self._scaling * p.speed) + self._processor = next( + p for p in model.processors if p.identifier == self._fault_info.fault_target.identifier) + self._processor.set_speed( + self._fault_info.value * self._processor.speed) + self._monitor.observe(VoltageChangeEvent(self)) def end_voltage_change(self, model): - for p in self._processor_list: - speed = next( - pi for pi in self._proc_info_list if pi.identifier == p.identifier).speed - p.set_speed(speed) + self._processor.set_speed(self._fault_info.fault_target.speed) class VoltageDrop(VoltageChange): @@ -91,9 +112,9 @@ class VoltageDrop(VoltageChange): The VoltageDrop class simulates the slowdown of one or more processors due to voltage drops. """ - def __init__(self, proc_info_list, scaling: float): - assert scaling >= 0 and scaling <= 1.0, "Voltage drop reduces the speed of a processor, i.e. scaling has to be >= 0 and <= 1" - super().__init__(proc_info_list, scaling) + def __init__(self, sim, fault_info): + assert fault_info.value >= 0 and fault_info.value <= 1.0, "Voltage drop reduces the speed of a processor, i.e. scaling has to be >= 0 and <= 1" + super().__init__(sim, fault_info) # TODO: rethink this class @@ -126,23 +147,26 @@ class PriorityInversion(FaultFunction): pass -class TimingChange(FaultFunction): +class TimingChange(Fault): """ The TimingError class simulates changes in the timing parameters, e.g. deadline, period etc. """ - def __init__(self, task_info, parameter: str, value: float): - super().__init__(self.start_timing_change, self.end_timing_change) - self._task_info = task_info - self._parameter = parameter - self._value = value - self._old_value = getattr(self._task_info, parameter) + fields = ['fault_target', 'parameter', 'value'] + + def __init__(self, sim, fault_info): + super().__init__(sim, fault_info, self.start_timing_change, self.end_timing_change) + self._old_value = getattr( + self._fault_info.fault_target, fault_info.parameter) def start_timing_change(self, model): - setattr(self._task_info, self._parameter, self._value) + setattr(self._fault_info.fault_target, self._fault_info.parameter, + self._fault_info.value) + self._monitor.observe(TimingChangeEvent(self)) def end_timing_change(self, model): - setattr(self._task_info, self._parameter, self._old_value) + setattr(self._fault_info.fault_target, + self._fault_info.parameter, self._old_value) class TimingError(TimingChange): @@ -150,11 +174,34 @@ class TimingError(TimingChange): The TimingError class simulates changes in the timing parameters, e.g. deadline, period etc. """ - def __init__(self, task_info, parameter: str, value: float): - super().__init__(task_info, parameter, value) + def __init__(self, sim, fault_info): + super().__init__(sim, fault_info) class Redundancy(TimingChange): - def __init__(self, task_info, value: float): - super().__init__(task_info, "wcet", task_info.wcet + value) + def __init__(self, sim, fault_info): + fault_info.parameter = "wcet" + fault_info.value = fault_info.fault_target.wcet + fault_info.value + super().__init__(sim, fault_info) + + +fault_types = { + "TimingError": TimingError, + "TimingChange": TimingChange, + "Redundancy": Redundancy, + "VoltageChange": VoltageChange, + "VoltageDrop": VoltageDrop, + "ProcessorFailure": ProcessorFailure +} + +fault_types_names = ["TimingError", "TimingChange", "Redundancy", + "VoltageChange", "VoltageDrop", "ProcessorFailure"] + + +def FaultFactory(sim, fault_info): + """ + Task factory. Return and instantiate the correct class according to the + task_info. + """ + return fault_types[fault_info.fault_type](sim, fault_info) diff --git a/simso/core/FaultEvent.py b/simso/core/FaultEvent.py new file mode 100644 index 0000000..57b8dc5 --- /dev/null +++ b/simso/core/FaultEvent.py @@ -0,0 +1,26 @@ +class FaultEvent: + PROCESSOR_FAILURE = 1 + VOLTAGE_DROP = 2 + VOLTAGE_CHANGE = 3 + TIMING_ERROR = 4 + TIMING_CHANGE = 5 + REDUNDANCY = 6 + + def __init__(self, event=0, args=None): + self.event = event + self.args = args + + +class ProcessorFailureEvent(FaultEvent): + def __init__(self, fault): + FaultEvent.__init__(self, FaultEvent.PROCESSOR_FAILURE, fault) + + +class VoltageChangeEvent(FaultEvent): + def __init__(self, fault): + FaultEvent.__init__(self, FaultEvent.VOLTAGE_CHANGE, fault) + + +class TimingChangeEvent(FaultEvent): + def __init__(self, fault): + FaultEvent.__init__(self, FaultEvent.TIMING_CHANGE, fault) diff --git a/simso/core/Model.py b/simso/core/Model.py index e1ba7e4..3f28e30 100644 --- a/simso/core/Model.py +++ b/simso/core/Model.py @@ -8,7 +8,7 @@ from simso.core.Timer import Timer from simso.core.etm import execution_time_models from simso.core.Logger import Logger from simso.core.results import Results -from simso.core.Fault import Fault +from simso.core.Fault import FaultFactory class Model(Environment): @@ -63,7 +63,7 @@ class Model(Environment): self._fault_list = [] for fault_info in fault_info_list: - self._fault_list.append(Fault(self, fault_info)) + self._fault_list.append(FaultFactory(self, fault_info)) # XXX: too specific. self.penalty_preemption = configuration.penalty_preemption @@ -124,6 +124,13 @@ class Model(Environment): return self._task_list @property + def fault_list(self): + """ + List of all faults + """ + return self._fault_list + + @property def duration(self): """ Duration of the simulation. @@ -140,12 +147,6 @@ class Model(Environment): self.scheduler.init() self.progress.start() - # for cpu in self._processors: - # self.process(cpu.run()) - - # for task in self._task_list: - # self.process(task.execute()) - try: self.run(until=self._duration) finally: diff --git a/simso/core/ProcEvent.py b/simso/core/ProcEvent.py index 1003406..efdf692 100644 --- a/simso/core/ProcEvent.py +++ b/simso/core/ProcEvent.py @@ -5,6 +5,7 @@ class ProcEvent(object): RUN = 1 IDLE = 2 OVERHEAD = 3 + FAILED = 4 def __init__(self, event=0, args=None): self.event = event @@ -20,6 +21,9 @@ class ProcIdleEvent(ProcEvent): def __init__(self): ProcEvent.__init__(self, ProcEvent.IDLE) +class ProcFailedEvent(ProcEvent): + def __init__(self, fault): + ProcEvent.__init__(self, ProcEvent.FAILED, fault) class ProcOverheadEvent(ProcEvent): def __init__(self, type_overhead): diff --git a/simso/core/Processor.py b/simso/core/Processor.py index c1ebcb4..e4d379a 100644 --- a/simso/core/Processor.py +++ b/simso/core/Processor.py @@ -3,7 +3,7 @@ from collections import deque from simpy import Process # from SimPy.Simulation import Process, Monitor, hold, waituntil -from simso.core.ProcEvent import ProcRunEvent, ProcIdleEvent, \ +from simso.core.ProcEvent import ProcRunEvent, ProcIdleEvent, ProcFailedEvent, \ ProcOverheadEvent, ProcCxtSaveEvent, ProcCxtLoadEvent from simso.core.Monitor import Monitor from simso.core.EventQueue import EventQueue @@ -210,6 +210,8 @@ class Processor: elif evt[0] == FAIL: if self._fault is not None: self.sched.processors.remove(self) + self.monitor.observe(ProcFailedEvent(self._fault)) + # self._model.timeout() elif not self in self.sched.processors: self.sched.processors.append(self)