diff --git a/misc/script_proba.py b/misc/script_proba.py new file mode 100644 index 0000000..469e65c --- /dev/null +++ b/misc/script_proba.py @@ -0,0 +1,58 @@ +#!/usr/bin/python3 + +""" +Example of a script that uses SimSo. +""" + +import sys +from simso.core import Model +from simso.configuration import Configuration + + +def main(argv): + if len(argv) == 2: + # Configuration load from a file. + configuration = Configuration(argv[1]) + else: + # Manual configuration: + configuration = Configuration() + + configuration.cycles_per_ms = 1 + + configuration.etm = "pwcet" + + configuration.duration = 100 * configuration.cycles_per_ms + + # Add tasks: + configuration.add_task(name="T1", identifier=1, + activation_date=0, pwcet=[(2, 0.5), (4, 0.5)], pmit=[(5, 0.2), (6, 0.8)], deadline=5, task_type="Probabilistic", abort_on_miss=True) + + configuration.add_task(name="T2", identifier=2, + activation_date=0, pwcet=[(3, 0.5), (4, 0.5)], pmit=[(7, 0.5), (8, 0.5)], deadline=7, task_type="Probabilistic", abort_on_miss=True) + + configuration.add_task(name="T3", identifier=3, + activation_date=0, pwcet=[(3, 0.5), (4, 0.5)], pmit=[(8, 0.5), (9, 0.5)], deadline=8, task_type="Probabilistic", abort_on_miss=True) + + # Add a processor: + configuration.add_processor(name="CPU 1", identifier=1) + # configuration.add_processor(name="CPU 2", identifier=2) + + # Add a scheduler: + # configuration.scheduler_info.filename = "./simso/schedulers/RM.py" + configuration.scheduler_info.clas = "simso.schedulers.RM" + + # Check the config before trying to run it. + configuration.check_all() + + # Init a model from the configuration. + model = Model(configuration) + + # Execute the simulation. + model.run_model() + + # Print logs. + for log in model.logs: + print(log) + + +main(sys.argv) diff --git a/script.py b/script.py index 4ae9b1f..bbf1879 100755 --- a/script.py +++ b/script.py @@ -6,6 +6,7 @@ Example of a script that uses SimSo. import sys from simso.core import Model +from simso.core.Fault import ProcessorFailure, VoltageDrop, PriorityInversion, TimingError from simso.configuration import Configuration @@ -22,22 +23,24 @@ def main(argv): configuration.duration = 100 * configuration.cycles_per_ms # Add tasks: - configuration.add_task(name="T1", identifier=1, period=7, - activation_date=0, wcet=3, deadline=7) + configuration.add_task(name="T1", identifier=1, period=8, + activation_date=0, wcet=4, deadline=7) configuration.add_task(name="T2", identifier=2, period=12, activation_date=0, wcet=3, deadline=8) - configuration.add_task(name="T4", identifier=4, period=15, + configuration.add_task(name="T3", identifier=3, period=15, activation_date=0, wcet=5, deadline=14) - configuration.add_task(name="T3", identifier=3, period=20, - activation_date=0, wcet=5, deadline=9) + task = configuration.add_task(name="T4", identifier=4, period=20, + activation_date=0, wcet=6, deadline=12) # Add a processor: - configuration.add_processor(name="CPU 1", identifier=1, cs_overhead=1) - configuration.add_processor(name="CPU 2", identifier=2, cs_overhead=1) + configuration.add_processor(name="CPU 1", identifier=1) + proc = configuration.add_processor(name="CPU 2", identifier=2) + + configuration.add_fault(TimingError(task, "period", 8), 0, 21) # Add a scheduler: #configuration.scheduler_info.filename = "../simso/schedulers/RM.py" - configuration.scheduler_info.clas = "simso.schedulers.RM" + configuration.scheduler_info.clas = "simso.schedulers.EDF" # Check the config before trying to run it. configuration.check_all() diff --git a/script_proba.py b/script_proba.py index 4d26e25..3e27609 100644 --- a/script_proba.py +++ b/script_proba.py @@ -35,7 +35,7 @@ def main(argv): # Add a processor: configuration.add_processor(name="CPU 1", identifier=1) - configuration.add_processor(name="CPU 2", identifier=2) + # configuration.add_processor(name="CPU 2", identifier=2) # Add a scheduler: configuration.scheduler_info.filename = "./simso/schedulers/RM.py" diff --git a/simso/configuration/Configuration.py b/simso/configuration/Configuration.py index 539911e..5dc3cd6 100644 --- a/simso/configuration/Configuration.py +++ b/simso/configuration/Configuration.py @@ -4,10 +4,12 @@ import os import re from xml.dom import minidom +from functools import reduce 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.generator.task_generator import gen_probabilistic_arrivals from simso.generator.task_generator import cdf @@ -22,7 +24,7 @@ if not hasattr(minidom.NamedNodeMap, '__contains__'): def _gcd(*numbers): """Return the greatest common divisor of the given integers""" - from fractions import gcd + from math import gcd return reduce(gcd, numbers) @@ -60,6 +62,7 @@ class Configuration(object): self._scheduler_info = parser.scheduler_info self.penalty_preemption = parser.penalty_preemption self.penalty_migration = parser.penalty_migration + self.fault_info_list = parser.fault_info_list else: self.etm = "wcet" self.duration = 100000000 @@ -71,6 +74,7 @@ class Configuration(object): self.task_data_fields = {} self._proc_info_list = [] self.proc_data_fields = {} + self.fault_info_list = [] self.memory_access_time = 100 self._scheduler_info = SchedulerInfo() self.calc_penalty_cache() @@ -315,3 +319,10 @@ class Configuration(object): speed) self.proc_info_list.append(proc) return proc + + def add_fault(self, fault_function, start_time, end_time): + """ + 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) diff --git a/simso/configuration/parser.py b/simso/configuration/parser.py index d64e0b3..f2b79cd 100644 --- a/simso/configuration/parser.py +++ b/simso/configuration/parser.py @@ -20,6 +20,7 @@ class Parser(object): """ Simulation file parser. """ + def __init__(self, filename): self.filename = filename self.cur_dir = os.path.split(filename)[0] @@ -34,6 +35,11 @@ class Parser(object): self._parse_processors() self._parse_scheduler() self._parse_penalty() + self._parse_faults() + + def _parse_faults(self): + # TODO: implement parser for faults + self.fault_info_list = [] def _parse_caches(self): self.caches_list = [] @@ -90,29 +96,31 @@ class Parser(object): map(float, attr['list_activation_dates'].value.split(','))) t = TaskInfo( - attr['name'].value, - int(attr['id'].value), - task_type, - 'abort_on_miss' not in attr + name=attr['name'].value, + identifier=int(attr['id'].value), + task_type=task_type, + abort_on_miss='abort_on_miss' not in attr or attr['abort_on_miss'].value == 'yes', - float(attr['period'].value), - float(attr['activationDate'].value) + period=float(attr['period'].value), + activation_date=float(attr['activationDate'].value) if 'activationDate' in attr else 0, - int(attr['instructions'].value), - float(attr['mix'].value), - (self.cur_dir + '/' + attr['stack'].value, - self.cur_dir) if 'stack' in attr else ("", self.cur_dir), - float(attr['WCET'].value), - float(attr['ACET'].value) if 'ACET' in attr else 0, - float(attr['et_stddev'].value) if 'et_stddev' in attr else 0, - float(attr['deadline'].value), - float(attr['base_cpi'].value), - int(attr['followed_by'].value) + n_instr=int(attr['instructions'].value), + mix=float(attr['mix'].value), + stack_file=(self.cur_dir + '/' + attr['stack'].value, + self.cur_dir) if 'stack' in attr else ("", self.cur_dir), + wcet=float(attr['WCET'].value), + acet=float(attr['ACET'].value) if 'ACET' in attr else 0, + pwcet=float(attr['PWCET'].value) if 'PWCET' in attr else 0, + et_stddev=float( + attr['et_stddev'].value) if 'et_stddev' in attr else 0, + deadline=float(attr['deadline'].value), + base_cpi=float(attr['base_cpi'].value), + followed_by=int(attr['followed_by'].value) if 'followed_by' in attr else None, - list_activation_dates, - int(float(attr['preemption_cost'].value)) + list_activation_dates=list_activation_dates, + preemption_cost=int(float(attr['preemption_cost'].value)) if 'preemption_cost' in attr else 0, - data) + data=data) self.task_info_list.append(t) def _parse_processors(self): @@ -244,5 +252,5 @@ class Parser(object): clas=clas, overhead=overhead, overhead_activate=overhead_activate, overhead_terminate=overhead_terminate, fields=data) if filename and filename[0] != '/': - filename = self.cur_dir + '/' + filename + filename = self.cur_dir + '/' + filename self.scheduler_info.filename = filename diff --git a/simso/core/Fault.py b/simso/core/Fault.py new file mode 100644 index 0000000..5ab60a9 --- /dev/null +++ b/simso/core/Fault.py @@ -0,0 +1,160 @@ +from simpy import Environment +from typing import Callable +from simso.core.Processor import ProcInfo + + +class FaultFunction: + """ + The FaultFunction class changes the actual behaviour of the faulty element. + """ + + def __init__(self, start_function: Callable, end_function: Callable): + self.start_function = start_function + self.end_function = end_function + + +class FaultInfo: + """ + The FaultType class is responsible for causing the error. + """ + + def __init__(self, start_time: float, end_time: float, fault_function: FaultFunction): + self.start_time = start_time + self.end_time = end_time + self.fault_function = fault_function + + +class Fault: + """ + The Fault class is the simpy process for triggering a certain error. + """ + + def __init__(self, sim, fault_type: FaultInfo): + self._fault_type = fault_type + self._sim = sim + self.process = self._sim.process(self.release()) + + def release(self): + yield self._sim.timeout(self._fault_type.start_time) + + self._fault_type.fault_function.start_function(self._sim) + + yield self._sim.timeout(self._fault_type.end_time - + self._fault_type.start_time) + + self._fault_type.fault_function.end_function(self._sim) + + +class ProcessorFailure(FaultFunction): + """ + The ProcessorFailure class simulates a complete stoppage of a processor. + """ + + def __init__(self, proc_info: ProcInfo): + super().__init__(self.stop_processor, self.start_processor) + self._proc_info = proc_info + self._processor = None + + def stop_processor(self, model): + self._processor = next( + p for p in model.processors if p.identifier == self._proc_info.identifier) + self._processor.fail(self) + + def start_processor(self, model): + self._processor.fail() + + +class VoltageChange(FaultFunction): + + 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 + + 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) + + 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) + + +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) + + +# TODO: rethink this class +class PriorityInversion(FaultFunction): + """ + The PriorityInversion class simulates changes in the priority of a job. + """ + + def __init__(self, proc_info, until_sched: bool = False): + """ + lambd: Expects task and new value as parameter and returns old value + """ + super().__init__(self.start_priority_inversion, self.end_priority_inversion) + self._proc_info = proc_info + self._until_sched = until_sched + + def start_priority_inversion(self, model): + proc = next(p for p in model.processors if p.identifier == + self._proc_info.identifier) + sched = model.scheduler + running_jobs = [ + p.running for p in model.processors if p.running is not None] + job = next(t.job for t in sched.task_list if t.job.is_active() + and t.job not in running_jobs) + + if job is not None: + pass + + def end_priority_inversion(self, model): + pass + + +class TimingChange(FaultFunction): + """ + 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) + + def start_timing_change(self, model): + setattr(self._task_info, self._parameter, self._value) + + def end_timing_change(self, model): + setattr(self._task_info, self._parameter, self._old_value) + + +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) + + +class Redundancy(TimingChange): + + def __init__(self, task_info, value: float): + super().__init__(task_info, "wcet", task_info.wcet + value) diff --git a/simso/core/Job.py b/simso/core/Job.py index b0d4aec..52c163c 100644 --- a/simso/core/Job.py +++ b/simso/core/Job.py @@ -94,6 +94,7 @@ class Job: self._sim.logger.log(self.name + " Preempted! ret: " + str(self.ret), kernel=True) # TODO: what to pass as interrupted? + # TODO: Jobs that terminate after their deadline are not recorded def _on_terminated(self): self._on_stop_exec() self._etm.on_terminated(self) @@ -322,11 +323,3 @@ class Job: if ret <= 0: # End of job. self._on_terminated() - - -class SequentialJob(Job): - pass - - -class ParallelJob(Job): - pass diff --git a/simso/core/Model.py b/simso/core/Model.py index 3a3bc7a..e1ba7e4 100644 --- a/simso/core/Model.py +++ b/simso/core/Model.py @@ -8,6 +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 class Model(Environment): @@ -31,6 +32,7 @@ class Model(Environment): self._logger = Logger(self) task_info_list = configuration.task_info_list proc_info_list = configuration.proc_info_list + fault_info_list = configuration.fault_info_list self._cycles_per_ms = configuration.cycles_per_ms self.scheduler = configuration.scheduler_info.instantiate(self) @@ -59,6 +61,10 @@ class Model(Environment): proc.caches = proc_info.caches self._processors.append(proc) + self._fault_list = [] + for fault_info in fault_info_list: + self._fault_list.append(Fault(self, fault_info)) + # XXX: too specific. self.penalty_preemption = configuration.penalty_preemption self.penalty_migration = configuration.penalty_migration diff --git a/simso/core/Processor.py b/simso/core/Processor.py index 7ff4e0d..c1ebcb4 100644 --- a/simso/core/Processor.py +++ b/simso/core/Processor.py @@ -14,6 +14,7 @@ TERMINATE = 3 TIMER = 4 PREEMPT = 5 SPEED = 6 +FAIL = 7 class ProcInfo(object): @@ -75,6 +76,7 @@ class Processor: env=model, name="Monitor Timer" + proc_info.name) self._speed = proc_info.speed self.process = self._model.process(self.run()) + self._fault = None def resched(self): """ @@ -91,11 +93,14 @@ class Processor: self._running = None def preempt(self, job=None): - # self._evts = deque([e for e in self._evts if e[0] != PREEMPT]) self._evts.clear_events(PREEMPT) self._evts.append((PREEMPT,)) self._running = job + def fail(self, fault=None): + self._evts.append((FAIL, fault)) + self._fault = fault # TODO: check if I need this + def timer(self, timer): self._evts.append((TIMER, timer)) @@ -202,6 +207,13 @@ class Processor: evt[1].call_handler() elif evt[0] == SPEED: self._speed = evt[1] + elif evt[0] == FAIL: + if self._fault is not None: + self.sched.processors.remove(self) + elif not self in self.sched.processors: + self.sched.processors.append(self) + + self.resched() # TODO: changed from preempt, check if still works elif evt[0] == RESCHED: self.monitor.observe(ProcOverheadEvent("Scheduling")) self.sched.monitor_begin_schedule(self) diff --git a/simso/core/Scheduler.py b/simso/core/Scheduler.py index 884c7bb..76e9963 100644 --- a/simso/core/Scheduler.py +++ b/simso/core/Scheduler.py @@ -14,12 +14,12 @@ from simso.core.SchedulerEvent import SchedulerBeginScheduleEvent, \ SchedulerEndTerminateEvent - class SchedulerInfo(object): """ SchedulerInfo groups the data that characterize a Scheduler (such as the scheduling overhead) and do the dynamic loading of the scheduler. """ + def __init__(self, clas='', overhead=0, overhead_activate=0, overhead_terminate=0, fields=None): """ @@ -142,7 +142,7 @@ class Scheduler(object): self.sim = sim self.processors = [] self.task_list = [] - self._lock = Resource(sim) + self._lock = Resource(sim) self.overhead = scheduler_info.overhead self.overhead_activate = scheduler_info.overhead_activate self.overhead_terminate = scheduler_info.overhead_terminate @@ -220,7 +220,7 @@ class Scheduler(object): """ Release the lock. Goes in pair with :meth:`get_lock`. """ - self._lock.release(request) + self._lock.release(request) def monitor_begin_schedule(self, cpu): self.monitor.observe(SchedulerBeginScheduleEvent(cpu)) diff --git a/simso/core/Timer.py b/simso/core/Timer.py index 0a4011b..982269b 100644 --- a/simso/core/Timer.py +++ b/simso/core/Timer.py @@ -16,7 +16,6 @@ class InstanceTimer: self.cpu = timer.cpu self.running = False self.overhead = timer.overhead - self.process = self._sim.process(self.run()) def call_handler(self): if self.running: diff --git a/simso/core/etm/WCET.py b/simso/core/etm/WCET.py index 7848171..7213bd0 100644 --- a/simso/core/etm/WCET.py +++ b/simso/core/etm/WCET.py @@ -42,6 +42,7 @@ class WCET(AbstractExecutionTimeModel): def get_ret(self, job): wcet_cycles = int(job.wcet * self.sim.cycles_per_ms) + # TODO: this leads to rounding errors, use ceil? return int(wcet_cycles - self.get_executed(job)) def update(self): diff --git a/simso/core/results.py b/simso/core/results.py index 60340b3..27edf25 100644 --- a/simso/core/results.py +++ b/simso/core/results.py @@ -8,6 +8,7 @@ class ProcessorR(object): Add information about a processor such as the number of CxtSave and CxtLoad and their total overhead. """ + def __init__(self): self.context_save_overhead = 0 self.context_save_count = 0 @@ -20,6 +21,7 @@ class SchedulerR(object): Add information about the scheduler such as the number of scheduling events and their total overhead. """ + def __init__(self): self.schedule_overhead = 0 self.activate_overhead = 0 @@ -36,6 +38,7 @@ class TaskR(object): The attribute jobs contains a list of JobR, sorted by activation date. """ + def __init__(self, task, delta_preemption=100): self.task = task self.delta_preemption = delta_preemption @@ -144,6 +147,7 @@ class JobR(object): Add a set of metrics to a job. Such metrics include: preemption count, migration count, response time, etc. """ + def __init__(self, date, job): self.job = job self.preemption_count = 0 @@ -206,6 +210,7 @@ class Results(object): . """ + def __init__(self, model): self.model = model self.error = None @@ -235,7 +240,7 @@ class Results(object): for task in self.model.task_list: if indices[task] < len(monitors[task]): evt = monitors[task][indices[task]] - if m is None or evt[1].id_ < m[0][1].id_: + if m is None or evt[1].id_ < m[0][1].id_: # pylint: disable=E1136 m = (evt, task) if m is None: break diff --git a/simso/schedulers/EDF.py b/simso/schedulers/EDF.py index 422f108..4f32388 100644 --- a/simso/schedulers/EDF.py +++ b/simso/schedulers/EDF.py @@ -5,9 +5,11 @@ architectures. from simso.core import Scheduler from simso.schedulers import scheduler + @scheduler("simso.schedulers.EDF") class EDF(Scheduler): """Earliest Deadline First""" + def on_activate(self, job): job.cpu.resched() @@ -22,7 +24,7 @@ class EDF(Scheduler): if ready_jobs: # Select a free processor or, if none, # the one with the greatest deadline (self in case of equality): - key = lambda x: ( + def key(x): return ( 1 if not x.running else 0, x.running.absolute_deadline if x.running else 0, 1 if x is cpu else 0 @@ -34,5 +36,5 @@ class EDF(Scheduler): if (cpu_min.running is None or cpu_min.running.absolute_deadline > job.absolute_deadline): - print(self.sim.now, job.name, cpu_min.name) + # print(self.sim.now, job.name, cpu_min.name) return (job, cpu_min)