diff --git a/misc/__init__.py b/misc/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/misc/__init__.py diff --git a/misc/context.py b/misc/context.py new file mode 100644 index 0000000..c84ce54 --- /dev/null +++ b/misc/context.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +import sys +import os +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) + +import simso # nopep8 diff --git a/misc/experiments.py b/misc/experiments.py new file mode 100644 index 0000000..7f47a6a --- /dev/null +++ b/misc/experiments.py @@ -0,0 +1,96 @@ +#!/usr/bin/python3 + +""" +Example of a script that uses SimSo. +""" + +import sys +import random +from numpy import arange +from context import simso +from simso.core import Model +from simso.configuration import Configuration +from simso.generator.task_generator import gen_periods_uniform, UUniFastDiscard, gen_tasksets, gen_faults, gen_arrivals + +import pickle + + + +def main(argv): + # Manual configuration: + schedulers = ["./simso/schedulers/RM.py", "./simso/schedulers/EDF.py","./simso/schedulers/P_RM.py","./simso/schedulers/P_EDF.py"] + # schedulers = [] + configuration = Configuration() + + configuration.cycles_per_ms = 1000 + + configuration.etm = "wcet" + + configuration.duration = 1000 * configuration.cycles_per_ms + + num_processors = 8 + # Add a processor: + for np in range(0, num_processors): + configuration.add_processor(name=f"CPU {np}" , identifier=np) + + + nsets = 20 + + for u in arange(1,num_processors +0.1, 0.5): + print(f"Utilization = {u}") + utilizations = UUniFastDiscard(20, u, nsets) + periods = gen_periods_uniform(20, nsets, 2, 100) + tasksets = gen_tasksets(utilizations, periods) + + missed_deadlines = {} + for sched in schedulers: + missed_deadlines[sched] = 0 + + for n in range(nsets): + configuration._task_info_list = [] + + for t in tasksets[n]: + identifier = tasksets[n].index(t) + configuration.add_task(name="t" + str(identifier), identifier=identifier,period=t[1],wcet=t[0],deadline=t[1]) + + faults = gen_faults(n_faults=5,task_info_list=configuration._task_info_list,processor_info_list=configuration._proc_info_list, sim_duration=configuration.duration_ms) + + for f in faults: + configuration.add_fault(fault_type=f['fault_type'], start_time=f['start_time'], end_time=f['end_time'], + fault_target=f['fault_target'], parameter=f.get('parameter'), value=f.get('value')) + + for sched in schedulers: + configuration.scheduler_info.filename = sched + + # Check the config before trying to run it. + configuration.check_all() + + # Init a model from the configuration. + model = Model(configuration) + + # Execute the simulation. + try: + model.run_model() + except RuntimeError as e: + if e.args[0] != "Bin packing failed!": + raise e + missed_deadlines[sched] += 1 + except AssertionError: + missed_deadlines[sched] += 1 + except ValueError: + with open('failed_configuration.pkl', 'wb') as out: + pickle.dump(configuration, out, pickle.HIGHEST_PROTOCOL) + + if model.results is not None and model.results.total_exceeded_count > 0: + missed_deadlines[sched] += 1 + + for sched in schedulers: + print(f"{sched}: {missed_deadlines[sched]}") + + + # # # Print logs. + # # for log in model.logs: + # # print(log) + + +main(sys.argv) \ No newline at end of file diff --git a/script.py b/misc/script.py similarity index 58% rename from script.py rename to misc/script.py index bbf1879..275d47e 100755 --- a/script.py +++ b/misc/script.py @@ -5,42 +5,23 @@ Example of a script that uses SimSo. """ import sys +from context import simso from simso.core import Model from simso.core.Fault import ProcessorFailure, VoltageDrop, PriorityInversion, TimingError from simso.configuration import Configuration +from simso.configuration.parser import AmaltheaModelParser +import pickle def main(argv): - if len(argv) == 2: - # Configuration load from a file. - configuration = Configuration(argv[1]) - else: - # Manual configuration: - configuration = Configuration() + # Manual configuration: + configuration = Configuration("./misc/mobstr.amxmi") - configuration.cycles_per_ms = 1 + configuration.cycles_per_ms = 1000 - configuration.duration = 100 * configuration.cycles_per_ms + configuration.duration = 100 * configuration.cycles_per_ms - # Add tasks: - 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="T3", identifier=3, period=15, - activation_date=0, wcet=5, deadline=14) - 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) - 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.EDF" + configuration.scheduler_info.clas = "simso.schedulers.RM" # Check the config before trying to run it. configuration.check_all() @@ -57,3 +38,9 @@ def main(argv): main(sys.argv) + + + # configuration = None + + # with open('failed_configuration.pkl','rb') as input: + # configuration = pickle.load(input) diff --git a/misc/script_proba.py b/misc/script_proba.py index 469e65c..daf6577 100644 --- a/misc/script_proba.py +++ b/misc/script_proba.py @@ -5,6 +5,7 @@ Example of a script that uses SimSo. """ import sys +from context import simso from simso.core import Model from simso.configuration import Configuration @@ -17,7 +18,7 @@ def main(argv): # Manual configuration: configuration = Configuration() - configuration.cycles_per_ms = 1 + configuration.cycles_per_ms = 1000000 configuration.etm = "pwcet" @@ -38,8 +39,7 @@ def main(argv): # 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" + configuration.scheduler_info.filename = "./simso/schedulers/RM.py" # Check the config before trying to run it. configuration.check_all() diff --git a/script_proba.py b/script_proba.py deleted file mode 100644 index 3e27609..0000000 --- a/script_proba.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/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" - - # 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/setup.py b/setup.py index 1d8e34e..398ee64 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( 'Topic :: Scientific/Engineering', 'Development Status :: 5 - Production/Stable' ], - python_requires='>=3.6', + python_requires='>=3.10', packages=find_packages(), install_requires=[ 'SimPy>=4.0.1', diff --git a/simso/configuration/Configuration.py b/simso/configuration/Configuration.py index c9628d1..8b367a1 100644 --- a/simso/configuration/Configuration.py +++ b/simso/configuration/Configuration.py @@ -14,7 +14,7 @@ from simso.generator.task_generator import gen_probabilistic_arrivals from simso.generator.task_generator import cdf from .GenerateConfiguration import generate -from .parser import Parser +from .parser import Parser, AmaltheaModelParser # Hack for Python2 @@ -48,7 +48,24 @@ class Configuration(object): Args: - `filename` A file can be used to initialize the configuration. """ - if filename: + if filename and any(x in filename for x in [".amxmi", ".amxml"]): + parser = AmaltheaModelParser(filename) + self.etm = "wcet" + self.duration = 100 + self.cycles_per_ms = 1 + self._caches_list = [] + self.memory_access_time = 0 + self._task_info_list = parser.task_info_list + self.task_data_fields = [] + self._proc_info_list = parser.proc_info_list + self.proc_data_fields = [] + self._scheduler_info = parser.scheduler_info + self.penalty_preemption = 0 + self.penalty_migration = 0 + self.fault_info_list = [] + self.fault_data_fields=[] + self.mapping_dict = parser.task_mappings + elif filename: parser = Parser(filename) self.etm = parser.etm self.duration = parser.duration @@ -64,6 +81,7 @@ class Configuration(object): self.penalty_migration = parser.penalty_migration self.fault_info_list = parser.fault_info_list self.fault_data_fields = parser.fault_data_fields + self.mapping_dict = parser.mapping_dict # TODO: else: self.etm = "wcet" self.duration = 100000000 @@ -79,6 +97,7 @@ class Configuration(object): self.fault_data_fields = {} self.memory_access_time = 100 self._scheduler_info = SchedulerInfo() + self.mapping_dict = None self.calc_penalty_cache() self._set_filename(filename) @@ -288,7 +307,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=1, acet=1, pwcet=(1, 1.0), pmit=(0, 1.0), + n_instr=0, mix=0.5, stack_file="", wcet=1, acet=1, pwcet=[(1, 1.0)], pmit=[(10, 1.0)], et_stddev=0, deadline=10, base_cpi=1.0, followed_by=None, list_activation_dates=[], preemption_cost=0, data=None): """ @@ -299,7 +318,7 @@ class Configuration(object): if task_type == "Probabilistic": list_activation_dates = gen_probabilistic_arrivals( - pmit, activation_date, self.duration, False) + pmit, activation_date, self.duration_ms, False) pwcet = cdf(pwcet) task = TaskInfo(name, identifier, task_type, abort_on_miss, period, @@ -322,11 +341,17 @@ class Configuration(object): self.proc_info_list.append(proc) return proc - def add_fault(self, fault_type: str, start_time, end_time, target, value=None, parameter=None): + def add_fault(self, fault_type: str, start_time, end_time, fault_target, value=None, parameter=None): """ Helper method to create a FaultInfo and add it to the list of faults. """ fault_info = FaultInfo(fault_type, start_time, - end_time, target, value, parameter) + end_time, fault_target, value, parameter) self.fault_info_list.append(fault_info) return fault_info + + def add_mappings(self, mapping_dict): + """ + Add mappings in the form of Dict[task_info, List[processor_info]] + """ + self.mapping_dict= mapping_dict \ No newline at end of file diff --git a/simso/configuration/parser.py b/simso/configuration/parser.py index f2b79cd..6fd11b3 100644 --- a/simso/configuration/parser.py +++ b/simso/configuration/parser.py @@ -1,5 +1,6 @@ # coding=utf-8 +from xml.etree import ElementTree from xml.dom.minidom import parse import os.path from simso.core.Task import TaskInfo, task_types @@ -40,6 +41,7 @@ class Parser(object): def _parse_faults(self): # TODO: implement parser for faults self.fault_info_list = [] + self.fault_data_fields = {} def _parse_caches(self): self.caches_list = [] @@ -254,3 +256,171 @@ class Parser(object): if filename and filename[0] != '/': filename = self.cur_dir + '/' + filename self.scheduler_info.filename = filename + + +class AmaltheaModelParser: + def __init__(self, filename): + self.filename = filename + self.cur_dir = os.path.split(filename)[0] + if not self.cur_dir: + self.cur_dir = '.' + + tree = ElementTree.parse(filename) + self.root = tree.getroot() + + self._parse_processors() + self._parse_scheduler() + # Tasks have to be parsed after processors due to allocation strategies + self._parse_tasks() + + def _parse_tasks(self): + self.task_info_list = [] + self.task_mappings = {} + identifier = 0 + for tag in self.root.findall('swModel/tasks'): + name = tag.attrib['name'] + process = f"{tag.attrib['name']}?type=Task" + activations = [] + stimuli = self.root.find(f"stimuliModel/stimuli/[@name='{tag.attrib['stimuli'].split('?')[0]}']") + if stimuli.attrib['{http://www.w3.org/2001/XMLSchema-instance}type'] == 'am:PeriodicStimulus': + task_type = "Periodic" + period = stimuli.find(f"[@name='{tag.attrib['stimuli'].split('?')[0]}']/recurrence") + if period is not None: + unit = period.attrib['unit'] + period = int(period.attrib['value']) + if unit == 's': + period *= 1000 + elif unit == 'us': + period /=1000 + elif unit == 'ns': + period /= 1000000 + elif unit == 'ps': + period /= 1000000000 + elif stimuli.attrib['{http://www.w3.org/2001/XMLSchema-instance}type'] == 'am:ArrivalCurveStimulus': + task_type = 'Sporadic' + activations= stimuli.find(f"[@name='{tag.attrib['stimuli'].split('?')[0]}']/entries") + # TODO: find example + else: + continue + + deadline = self.root.find(f"constraintsModel/requirements/[@process='{process}']/limit/limitValue") + if deadline is not None: + deadline = int(deadline.attrib['value']) + else: + deadline = period + + priority = self.root.find(f"mappingModel/taskAllocation/[@task='{process}']/schedulingParameters") + if priority is not None: + priority = int(priority.attrib['priority']) + + affinities = next(iter(self.root.findall(f"mappingModel/taskAllocation/[@task='{process}']"))).attrib['affinity'] + affinities = affinities.split(' ') + affinity_list = [] + for i in affinities: + proc = next((p for p in self.proc_info_list if i.split('?')[0] in p.name), None) + affinity_list.append(proc) + + wcet = 0 + acet = 0 + for r in tag.findall(f"./activityGraph/items/items/[@{{http://www.w3.org/2001/XMLSchema-instance}}type='am:RunnableCall']"): + frequency_domain_list = set([p.name.split('_')[1] for p in affinity_list]) + + processor = affinity_list[0] + if len(frequency_domain_list) > 1: + processor = min(affinity_list, key=lambda x: x.speed) + + pname = processor.name.split('_')[1]+ '?type=ProcessingUnitDefinition' + + runnable= r.attrib['runnable'].split('?')[0] + runnable = self.root.find(f"swModel/runnables/[@name='{runnable}']/activityGraph/items/[@{{http://www.w3.org/2001/XMLSchema-instance}}type='am:Ticks']/extended/[@key='{pname}']/value") + + if runnable is not None: + if runnable.attrib['{http://www.w3.org/2001/XMLSchema-instance}type']== 'am:DiscreteValueConstant': + wcet += float(runnable.attrib['value']) + acet += float(runnable.attrib['value']) + elif runnable.attrib['{http://www.w3.org/2001/XMLSchema-instance}type']== 'am:DiscreteValueStatistics': + wcet += float(runnable.attrib['upperBound']) + acet += float(runnable.attrib['average']) + + + # wcet[ms] = Ticks (=wcet before) / frequency[Hz] * 1000 [s -> ms] + wcet = wcet/processor.data['speed'] * 1000 + acet = acet/processor.data['speed'] * 1000 + + task = TaskInfo(name=name, + identifier=identifier, + task_type=task_type, + abort_on_miss=True, + period=period, + activation_date=0, + n_instr = 0, + mix =0, + stack_file=("", self.cur_dir), + wcet = wcet, + acet= acet, + pwcet=None, + et_stddev = 0, + deadline=deadline, + base_cpi=0, + followed_by=None, + list_activation_dates=activations, + preemption_cost=0, + data = {'priority': priority}) + + identifier += 1 + self.task_info_list.append(task) + + self.task_mappings[task] = affinity_list + + + def _parse_scheduler(self): + scheduler = self.root.find("osModel/operatingSystems/taskSchedulers/schedulingAlgorithm") + clas = "simso.schedulers.RM" + if scheduler is not None: + scheduler = scheduler.attrib['{http://www.w3.org/2001/XMLSchema-instance}type'] + + match scheduler: + case "am:FixedPriorityPreemptive": + clas = "simso.schedulers.C_FP" + case "am:EarliestDeadlineFirst": + clas = "simso.schedulers.EDF" + case "am:PfairPD2": + clas = "simso.schedulers.PD2" + case "am:EarlyReleaseFairPD2": + clas = "simso.schedulers.ER_PD2" + case _: + pass + + self.scheduler_info = SchedulerInfo( + clas=clas, overhead=0, overhead_activate=0, + overhead_terminate=0, fields=None) + + + def _parse_processors(self): + self.proc_info_list = [] + num_proc = 0 + for tag in self.root.findall("hwModel/definitions/[@puType='CPU']"): + for core in self.root.findall(f"hwModel/structures/structures/modules/[@definition='{tag.attrib['name']}?type=ProcessingUnitDefinition']"): + f_domain = self.root.find(f"hwModel/domains/[@name='{core.attrib['frequencyDomain'].split('?')[0]}']/defaultValue") + unit = f_domain.attrib['unit'] + speed = float(f_domain.attrib['value']) + + if unit == 'GHz': + speed *= 1000000000 + elif unit == 'MHz': + speed *= 1000000 + elif unit == 'KHz': + speed *= 1000 + + proc = ProcInfo(name= core.attrib['name']+ "_" + tag.attrib['name'], + identifier=num_proc, + speed=speed, data={'speed':speed}) + + self.proc_info_list.append(proc) + + num_proc+=1 + + # adjust speed to range in 0 - 1 + max_speed = max(p.speed for p in self.proc_info_list) + for p in self.proc_info_list: + p.speed = p.speed / max_speed \ No newline at end of file diff --git a/simso/core/Fault.py b/simso/core/Fault.py index ad14697..e981cc9 100644 --- a/simso/core/Fault.py +++ b/simso/core/Fault.py @@ -3,6 +3,7 @@ from typing import Callable from simso.core.Processor import ProcInfo from simso.core.Monitor import Monitor from simso.core.FaultEvent import ProcessorFailureEvent, VoltageChangeEvent, TimingChangeEvent +from simso.core.JobEvent import JobEvent class FaultFunction: @@ -50,11 +51,11 @@ class Fault: @property def duration_ms(self): - return self._fault_info.start_time - self._fault_info.end_time + return self._fault_info.end_time - self._fault_info.start_time @property def duration_cycles(self): - return (self._fault_info.start_time - self._fault_info.end_time) * self._sim.cycles_per_ms + return self.duration_ms * self._sim.cycles_per_ms def release(self): yield self._sim.timeout(self._fault_info.start_time * self._sim.cycles_per_ms) @@ -62,7 +63,7 @@ class Fault: self._start_function(self._sim) yield self._sim.timeout((self._fault_info.end_time - - self._fault_info.start_time) * self._sim.y) + self._fault_info.start_time) * self._sim.cycles_per_ms) self._end_function(self._sim) @@ -195,9 +196,8 @@ fault_types = { "ProcessorFailure": ProcessorFailure } -fault_types_names = ["TimingError", "TimingChange", "Redundancy", - "VoltageChange", "VoltageDrop", "ProcessorFailure"] - +fault_types_names = ["TimingError", "VoltageDrop", "ProcessorFailure"] +# "TimingChange", "Redundancy", "VoltageChange", def FaultFactory(sim, fault_info): """ diff --git a/simso/core/Job.py b/simso/core/Job.py index 52c163c..e96b546 100644 --- a/simso/core/Job.py +++ b/simso/core/Job.py @@ -296,7 +296,12 @@ class Job: def activate_job(self): self._start_date = self._sim.now # Notify the OS. - self._task.cpu.activate(self) + if self._task.cpu.has_failure(): + proc = next(p for p in self._sim.processors if not p.has_failure()) + proc.activate(self) + proc.resched() + else: + self._task.cpu.activate(self) # While the job's execution is not finished. while self._end_date is None: diff --git a/simso/core/Model.py b/simso/core/Model.py index 3f28e30..8d3a4b0 100644 --- a/simso/core/Model.py +++ b/simso/core/Model.py @@ -61,6 +61,7 @@ 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(FaultFactory(self, fault_info)) @@ -78,6 +79,7 @@ class Model(Environment): self._callback = callback self.scheduler.task_list = self._task_list self.scheduler.processors = self._processors + self.scheduler.configuration = configuration self.results = None def now_ms(self): diff --git a/simso/core/Processor.py b/simso/core/Processor.py index e4d379a..8a77ba6 100644 --- a/simso/core/Processor.py +++ b/simso/core/Processor.py @@ -98,8 +98,17 @@ class Processor: self._running = job def fail(self, fault=None): - self._evts.append((FAIL, fault)) - self._fault = fault # TODO: check if I need this + self._fault = fault + if self._fault is not None: + self.sched.processors.remove(self) + self._evts.append((FAIL, fault)) + elif not self in self.sched.processors: + self.sched.processors.append(self) + + # self.resched() # TODO: changed from preempt, check if still works + + def has_failure(self): + return self._fault is not None def timer(self, timer): self._evts.append((TIMER, timer)) @@ -208,14 +217,8 @@ class Processor: elif evt[0] == SPEED: self._speed = evt[1] 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) - - self.resched() # TODO: changed from preempt, check if still works + self.monitor.observe(ProcFailedEvent(self._fault)) + yield self._model.timeout(self._fault.duration_cycles) elif evt[0] == RESCHED: self.monitor.observe(ProcOverheadEvent("Scheduling")) self.sched.monitor_begin_schedule(self) diff --git a/simso/core/Task.py b/simso/core/Task.py index 948fcda..1d5564e 100644 --- a/simso/core/Task.py +++ b/simso/core/Task.py @@ -1,7 +1,5 @@ # coding=utf-8 -import simpy -from simpy import Process from collections import deque # from SimPy.Simulation import Process, Monitor, hold, passivate from simso.core.Job import Job @@ -66,7 +64,7 @@ class TaskInfo(object): self.list_activation_dates = list_activation_dates self.data = data self.preemption_cost = preemption_cost - self._pwcet = pwcet + self.pwcet = pwcet @property def csdp(self): @@ -263,15 +261,18 @@ class GenericTask: self.followed_by.create_job(job) if len(self._activations_fifo) > 0: - self._activations_fifo.popleft() + # self._activations_fifo.popleft() + self._activations_fifo.remove(job) if len(self._activations_fifo) > 0: self.job = self._activations_fifo[0] self.job.process = self._sim.process(self.job.activate_job()) def _job_killer(self, job): - if job.end_date is None and job.computation_time < job.wcet: + if job.end_date is None and job.actual_computation_time < job.wcet: if self._task_info.abort_on_miss: # self.cancel(job) TODO: How to cancel Job? + # if job.process and job.process.is_alive: + # job.process.interrupt(self) job.abort() def create_job(self, pred=None): @@ -354,10 +355,9 @@ class ProbabilisticTask(GenericTask): Probabilistic Task process. Inherits from :class:`GenericTask`. The jobs are created using a list of probabilistic activation times. """ - fields = ['list_activation_dates', 'deadline', 'pwcet', 'pmit'] + fields = ['period', 'activation_date', 'deadline', 'wcet'] def execute(self): - self._init() for ndate in self.list_activation_dates: yield self._sim.timeout(int(ndate * self._sim.cycles_per_ms) @@ -370,7 +370,7 @@ class ProbabilisticTask(GenericTask): @property def pwcet(self): - return self._task_info._pwcet + return self._task_info.pwcet task_types = { diff --git a/simso/core/etm/PWCET.py b/simso/core/etm/PWCET.py index b4d6ae6..e87d9e9 100644 --- a/simso/core/etm/PWCET.py +++ b/simso/core/etm/PWCET.py @@ -22,7 +22,7 @@ class PWCET(AbstractExecutionTimeModel): def on_activate(self, job): self.executed[job] = 0 job.wcet = next(x[0] for x in job.task.pwcet if random.uniform( - 0, 1) <= x[1]) * self.sim.cycles_per_ms + 0, 1) <= x[1]) # * self.sim.cycles_per_ms def on_execute(self, job): self.on_execute_date[job] = self.sim.now diff --git a/simso/core/etm/WCET.py b/simso/core/etm/WCET.py index 7213bd0..84ba38e 100644 --- a/simso/core/etm/WCET.py +++ b/simso/core/etm/WCET.py @@ -1,6 +1,7 @@ from simso.core.etm.AbstractExecutionTimeModel \ import AbstractExecutionTimeModel +from math import ceil class WCET(AbstractExecutionTimeModel): def __init__(self, sim, _): @@ -42,8 +43,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)) + return ceil(wcet_cycles - self.get_executed(job)) def update(self): for job in list(self.on_execute_date.keys()): diff --git a/simso/generator/task_generator.py b/simso/generator/task_generator.py index 1104613..5b5ee95 100755 --- a/simso/generator/task_generator.py +++ b/simso/generator/task_generator.py @@ -6,6 +6,8 @@ import numpy as np import random import math from functools import reduce +from simso.core.Fault import TimingError, ProcessorFailure, VoltageDrop +from simso.core.Task import task_types def UUniFastDiscard(n, u, nsets): @@ -341,3 +343,50 @@ def gen_tasksets(utilizations, periods): return [[(trunc(ui * pi, 6), trunc(pi, 6)) for ui, pi in zip(us, ps)] for us, ps in zip(utilizations, periods)] + + +def gen_faultset(arrivals, durations, fault_types): + faults = [] + for a in arrivals: + f = random.choice(fault_types) + d = random.choice(durations) + + faults.append((f, a, a + d)) + + return faults + + + +def gen_faults(n_faults, task_info_list, processor_info_list, sim_duration): + arrivals = [] + for i in range(n_faults): + arrivals.append(random.uniform(0, sim_duration)) + durations = gen_periods_uniform(len(arrivals), 1, 2, 5)[0] + + + fault_set = [] + for i in range(len(arrivals)): + a = arrivals[i] + d = durations[i] + f = random.choice(["TimingError", "VoltageDrop"]) #"ProcessorFailure", + + if f == 'ProcessorFailure': + p = random.choice(processor_info_list) + fault_set.append({'fault_type': f, 'start_time':a, 'end_time':a+d, 'fault_target': p}) + elif f == 'TimingError': + t = random.choice(task_info_list) + p = random.choice(task_types[t.task_type].fields) + + v = getattr(t, p) + + # TODO: gaussian distribution parameter with v + ve = random.uniform(v-0.15*v, v+0.15*v) + + fault_set.append({'fault_type': f, 'start_time':a, 'end_time':a+d, 'fault_target': t, 'parameter':p, 'value':v}) + elif f == 'VoltageDrop': + p = random.choice(processor_info_list) + v = random.uniform(0.5, 0.9) + fault_set.append({'fault_type': f, 'start_time':a, 'end_time':a+d, 'fault_target': p, 'value':v}) + + + return fault_set \ No newline at end of file diff --git a/simso/schedulers/C_FP.py b/simso/schedulers/C_FP.py new file mode 100644 index 0000000..d646c6f --- /dev/null +++ b/simso/schedulers/C_FP.py @@ -0,0 +1,51 @@ + +from simso.core import Scheduler +from simso.schedulers import scheduler + +@scheduler("simso.schedulers.C_FP", required_task_fields = [ + {'name': 'priority', 'type' : 'int', 'default' : '0' } + ] +) +class C_FP(Scheduler): + """ Clustered Fixed-Priority """ + def init(self): + self.ready_list = [] + self.mappings_dict = {} + for k,v in self.configuration.mapping_dict.items(): + task = next(t for t in self.task_list if t._task_info == k) + cpu_list = [p for p in self.processors if p.identifier in [pi.identifier for pi in v]] + self.mappings_dict[task] = cpu_list + task.cpu = self.mappings_dict[task][0] + + + def on_activate(self, job): + self.ready_list.append(job) + job.cpu.resched() + + def on_terminated(self, job): + if job in self.ready_list: + self.ready_list.remove(job) + else: + job.cpu.resched() + + def schedule(self, cpu): + if self.ready_list: + # Get the job with the highest priority. + job = max(self.ready_list, key=lambda x: x.data['priority']) + + # Get a free processor or a processor running a low priority job. + key = lambda x: ( + 1 if x.running else 0, + x.running.data['priority'] if x.running else 0, + 0 if x is cpu else 1 + ) + cpu_min = min(self.mappings_dict[job.task], key=key) + + if (cpu_min.running is None or + cpu_min.running.data['priority'] < job.data['priority']): + self.ready_list.remove(job) + if cpu_min.running: + self.ready_list.append(cpu_min.running) + return (job, cpu_min) + + return None \ No newline at end of file diff --git a/simso/schedulers/C_RM.py b/simso/schedulers/C_RM.py new file mode 100644 index 0000000..c04b6e1 --- /dev/null +++ b/simso/schedulers/C_RM.py @@ -0,0 +1,48 @@ +from simso.core import Scheduler +from simso.schedulers import scheduler + +@scheduler("simso.schedulers.C_RM" +) +class C_RM(Scheduler): + """ Clustered Rate-Monotonic """ + def init(self): + self.ready_list = [] + self.mappings_dict = {} + for k,v in self.configuration.mapping_dict.items(): + task = next(t for t in self.task_list if t._task_info == k) + cpu_list = [p for p in self.processors if p.identifier in [pi.identifier for pi in v]] + self.mappings_dict[task] = cpu_list + + + def on_activate(self, job): + self.ready_list.append(job) + job.cpu.resched() + + def on_terminated(self, job): + if job in self.ready_list: + self.ready_list.remove(job) + else: + job.cpu.resched() + + def schedule(self, cpu): + decision = None + if self.ready_list: + # Job with highest priority. + job = min(self.ready_list, key=lambda x: x.period) + + # Get a free processor or a processor running a low priority job. + key = lambda x: ( + 0 if x.running is None else 1, + -x.running.period if x.running else 0, + 0 if x is cpu else 1 + ) + cpu_min = min(self.mappings_dict[job.task], key=key) + + if (cpu_min.running is None or + cpu_min.running.period > job.period): + self.ready_list.remove(job) + if cpu_min.running: + self.ready_list.append(cpu_min.running) + decision = (job, cpu_min) + + return decision \ No newline at end of file diff --git a/simso/schedulers/P_EDF2.py b/simso/schedulers/P_EDF2.py index b10587d..6c0ed71 100644 --- a/simso/schedulers/P_EDF2.py +++ b/simso/schedulers/P_EDF2.py @@ -3,6 +3,7 @@ Partitionned EDF without the helping class. Use EDF_mono. """ +from simpy import Resource from simso.core import Scheduler from simso.core.Scheduler import SchedulerInfo from simso.schedulers.EDF_mono import EDF_mono @@ -16,6 +17,9 @@ class P_EDF2(Scheduler): # Mapping task to scheduler. self.map_task_sched = {} + self._lock = Resource(self.sim) + self.request = None + cpus = [] for cpu in self.processors: # Append the processor to a list with an initial utilization of 0. @@ -36,7 +40,8 @@ class P_EDF2(Scheduler): while cpus[j][1] + float(task.wcet) / task.period > 1.0: j += 1 if j >= len(self.processors): - print("oops bin packing failed.") + # print("oops bin packing failed.") + raise RuntimeError("Bin packing failed!") return # Get the scheduler for this processor. @@ -55,7 +60,9 @@ class P_EDF2(Scheduler): def get_lock(self): # No lock mechanism is needed. - return True + self._lock.release(self.request) + self.request = self._lock.request() + return self.request def schedule(self, cpu): return self.map_cpu_sched[cpu.identifier].schedule(cpu) diff --git a/simso/utils/PartitionedScheduler.py b/simso/utils/PartitionedScheduler.py index c1d390f..8c44982 100644 --- a/simso/utils/PartitionedScheduler.py +++ b/simso/utils/PartitionedScheduler.py @@ -1,3 +1,4 @@ +from simpy import Resource from simso.core import Scheduler @@ -17,7 +18,7 @@ def best_fit(scheduler, task_list=None): while cpus[j][1] * task.period + float(task.wcet) > task.period: j += 1 if j >= len(scheduler.processors): - print("oops bin packing failed.") + # print("oops bin packing failed.") return False # Affect it to the task. @@ -48,7 +49,7 @@ def worst_fit(scheduler, task_list=None): while cpus[j][1] * task.period + float(task.wcet) > task.period: j += 1 if j >= len(scheduler.processors): - print("oops bin packing failed.") + # print("oops bin packing failed.") return False # Affect it to the task. @@ -80,7 +81,7 @@ def next_fit(scheduler, task_list=None): j = (j + 1) % len(scheduler.processors) k += 1 if k >= len(scheduler.processors): - print("oops bin packing failed.") + # print("oops bin packing failed.") return False # Affect it to the task. @@ -109,7 +110,7 @@ def first_fit(scheduler, task_list=None): while cpus[j][1] * task.period + float(task.wcet) > task.period: j += 1 if j >= len(scheduler.processors): - print("oops bin packing failed.") + # print("oops bin packing failed.") return False # Affect it to the task. @@ -177,6 +178,8 @@ class PartitionedScheduler(Scheduler): "PartitionedScheduler requires a monoprocessor scheduler to " \ "instantiate." + self._lock = Resource(self.sim) + self.request = None # Mapping processor to scheduler. self.map_cpu_sched = {} # Mapping task to scheduler. @@ -211,7 +214,9 @@ class PartitionedScheduler(Scheduler): def get_lock(self): # No lock mechanism is needed. - return True + self._lock.release(self.request) + self.request = self._lock.request() + return self.request def schedule(self, cpu): return self.map_cpu_sched[cpu.identifier].schedule(cpu)