diff --git a/.vscode/.ropeproject/config.py b/.vscode/.ropeproject/config.py new file mode 100644 index 0000000..dee2d1a --- /dev/null +++ b/.vscode/.ropeproject/config.py @@ -0,0 +1,114 @@ +# The default ``config.py`` +# flake8: noqa + + +def set_prefs(prefs): + """This function is called before opening the project""" + + # Specify which files and folders to ignore in the project. + # Changes to ignored resources are not added to the history and + # VCSs. Also they are not returned in `Project.get_files()`. + # Note that ``?`` and ``*`` match all characters but slashes. + # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' + # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' + # '.svn': matches 'pkg/.svn' and all of its children + # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' + # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' + prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', + '.hg', '.svn', '_svn', '.git', '.tox'] + + # Specifies which files should be considered python files. It is + # useful when you have scripts inside your project. Only files + # ending with ``.py`` are considered to be python files by + # default. + # prefs['python_files'] = ['*.py'] + + # Custom source folders: By default rope searches the project + # for finding source folders (folders that should be searched + # for finding modules). You can add paths to that list. Note + # that rope guesses project source folders correctly most of the + # time; use this if you have any problems. + # The folders should be relative to project root and use '/' for + # separating folders regardless of the platform rope is running on. + # 'src/my_source_folder' for instance. + # prefs.add('source_folders', 'src') + + # You can extend python path for looking up modules + # prefs.add('python_path', '~/python/') + + # Should rope save object information or not. + prefs['save_objectdb'] = True + prefs['compress_objectdb'] = False + + # If `True`, rope analyzes each module when it is being saved. + prefs['automatic_soa'] = True + # The depth of calls to follow in static object analysis + prefs['soa_followed_calls'] = 0 + + # If `False` when running modules or unit tests "dynamic object + # analysis" is turned off. This makes them much faster. + prefs['perform_doa'] = True + + # Rope can check the validity of its object DB when running. + prefs['validate_objectdb'] = True + + # How many undos to hold? + prefs['max_history_items'] = 32 + + # Shows whether to save history across sessions. + prefs['save_history'] = True + prefs['compress_history'] = False + + # Set the number spaces used for indenting. According to + # :PEP:`8`, it is best to use 4 spaces. Since most of rope's + # unit-tests use 4 spaces it is more reliable, too. + prefs['indent_size'] = 4 + + # Builtin and c-extension modules that are allowed to be imported + # and inspected by rope. + prefs['extension_modules'] = [] + + # Add all standard c-extensions to extension_modules list. + prefs['import_dynload_stdmods'] = True + + # If `True` modules with syntax errors are considered to be empty. + # The default value is `False`; When `False` syntax errors raise + # `rope.base.exceptions.ModuleSyntaxError` exception. + prefs['ignore_syntax_errors'] = False + + # If `True`, rope ignores unresolvable imports. Otherwise, they + # appear in the importing namespace. + prefs['ignore_bad_imports'] = False + + # If `True`, rope will insert new module imports as + # `from import ` by default. + prefs['prefer_module_from_imports'] = False + + # If `True`, rope will transform a comma list of imports into + # multiple separate import statements when organizing + # imports. + prefs['split_imports'] = False + + # If `True`, rope will remove all top-level import statements and + # reinsert them at the top of the module when making changes. + prefs['pull_imports_to_top'] = True + + # If `True`, rope will sort imports alphabetically by module name instead + # of alphabetically by import statement, with from imports after normal + # imports. + prefs['sort_imports_alphabetically'] = False + + # Location of implementation of + # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general + # case, you don't have to change this value, unless you're an rope expert. + # Change this value to inject you own implementations of interfaces + # listed in module rope.base.oi.type_hinting.providers.interfaces + # For example, you can add you own providers for Django Models, or disable + # the search type-hinting in a class hierarchy, etc. + prefs['type_hinting_factory'] = ( + 'rope.base.oi.type_hinting.factory.default_type_hinting_factory') + + +def project_opened(project): + """This function is called after opening the project""" + # Do whatever you like here! diff --git a/misc/script.py b/script.py similarity index 93% rename from misc/script.py rename to script.py index 6152855..1a4001d 100755 --- a/misc/script.py +++ b/script.py @@ -17,15 +17,17 @@ def main(argv): # Manual configuration: configuration = Configuration() + configuration.cycles_per_ms = 1 + configuration.duration = 420 * 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="T2", identifier=2, period=12, - activation_date=0, wcet=3, deadline=12) + activation_date=0, wcet=3, deadline=8) configuration.add_task(name="T3", identifier=3, period=20, - activation_date=0, wcet=5, deadline=20) + activation_date=0, wcet=5, deadline=9) # Add a processor: configuration.add_processor(name="CPU 1", identifier=1) diff --git a/script_proba.py b/script_proba.py new file mode 100644 index 0000000..1251b99 --- /dev/null +++ b/script_proba.py @@ -0,0 +1,52 @@ +#!/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=6, 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=[(5,0.5), (7, 0.5)], deadline=7, task_type = "Probabilistic",abort_on_miss=True) + + # Add a processor: + configuration.add_processor(name="CPU 1", identifier=1) + + # Add a scheduler: + configuration.scheduler_info.filename = "./simso/schedulers/DM_mono.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 f98b0dd..1d8e34e 100644 --- a/setup.py +++ b/setup.py @@ -17,9 +17,10 @@ setup( 'Topic :: Scientific/Engineering', 'Development Status :: 5 - Production/Stable' ], + python_requires='>=3.6', packages=find_packages(), install_requires=[ - 'SimPy==2.3.1', + 'SimPy>=4.0.1', 'numpy>=1.6' ], long_description="""\ diff --git a/simso/configuration/Configuration.py b/simso/configuration/Configuration.py index 4c0f2d7..539911e 100644 --- a/simso/configuration/Configuration.py +++ b/simso/configuration/Configuration.py @@ -8,6 +8,8 @@ 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.generator.task_generator import gen_probabilistic_arrivals +from simso.generator.task_generator import cdf from .GenerateConfiguration import generate from .parser import Parser @@ -38,6 +40,7 @@ class Configuration(object): of this class will be passed to the constructor of the :class:`Model ` class. """ + def __init__(self, filename=None): """ Args: @@ -279,7 +282,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, + n_instr=0, mix=0.5, stack_file="", wcet=0, acet=0, pwcet=(0, 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): """ @@ -288,9 +291,14 @@ class Configuration(object): if data is None: data = dict((k, None) for k in self.task_data_fields) + if task_type == "Probabilistic": + list_activation_dates = gen_probabilistic_arrivals( + pmit, activation_date, self.duration, False) + pwcet = cdf(pwcet) + task = TaskInfo(name, identifier, task_type, abort_on_miss, period, activation_date, n_instr, mix, - (stack_file, self.cur_dir), wcet, acet, et_stddev, + (stack_file, self.cur_dir), wcet, acet, pwcet, et_stddev, deadline, base_cpi, followed_by, list_activation_dates, preemption_cost, data) self.task_info_list.append(task) diff --git a/simso/configuration/GenerateConfiguration.py b/simso/configuration/GenerateConfiguration.py index faba105..0af5d39 100644 --- a/simso/configuration/GenerateConfiguration.py +++ b/simso/configuration/GenerateConfiguration.py @@ -112,6 +112,7 @@ def generate_tasks(top, task_info_list, fields): 'mix': str(task.mix), 'WCET': str(task.wcet), 'ACET': str(task.acet), + 'PWCET': str(task.pwcet), 'preemption_cost': str(task.preemption_cost), 'et_stddev': str(task.et_stddev)}) if task.followed_by is not None: diff --git a/simso/core/Job.py b/simso/core/Job.py index 4708fd3..12afdbc 100644 --- a/simso/core/Job.py +++ b/simso/core/Job.py @@ -44,6 +44,7 @@ class Job(Process): self._monitor = monitor self._etm = etm self._was_running_on = task.cpu + self._wcet = task.wcet self._on_activate() @@ -106,7 +107,8 @@ class Job(Process): self._monitor.observe(JobEvent(self, JobEvent.ABORTED)) self._task.end_job(self) self._task.cpu.terminate(self) - self._sim.logger.log("Job " + str(self.name) + " aborted! ret:" + str(self.ret)) + self._sim.logger.log("Job " + str(self.name) + + " aborted! ret:" + str(self.ret)) def is_running(self): """ @@ -243,7 +245,11 @@ class Job(Process): Worst-Case Execution Time in milliseconds. Equivalent to ``self.task.wcet``. """ - return self._task.wcet + return self._wcet + + @wcet.setter + def wcet(self, value): + self._wcet = value @property def activation_date(self): @@ -314,3 +320,11 @@ class Job(Process): else: self.interruptReset() + + +class SequentialJob(Job): + pass + + +class ParallelJob(Job): + pass diff --git a/simso/core/Model.py b/simso/core/Model.py index de61b0e..3f58584 100644 --- a/simso/core/Model.py +++ b/simso/core/Model.py @@ -7,6 +7,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 +import SimPy as SPPc class Model(Simulation): @@ -26,6 +27,7 @@ class Model(Simulation): Methods: """ + print(SPPc.__version__) Simulation.__init__(self) self._logger = Logger(self) task_info_list = configuration.task_info_list diff --git a/simso/core/Task.py b/simso/core/Task.py index da5ad2d..a37cfdf 100644 --- a/simso/core/Task.py +++ b/simso/core/Task.py @@ -18,7 +18,7 @@ class TaskInfo(object): """ def __init__(self, name, identifier, task_type, abort_on_miss, period, - activation_date, n_instr, mix, stack_file, wcet, acet, + activation_date, n_instr, mix, stack_file, wcet, acet, pwcet, et_stddev, deadline, base_cpi, followed_by, list_activation_dates, preemption_cost, data): """ @@ -33,6 +33,7 @@ class TaskInfo(object): :type stack_file: str :type wcet: float :type acet: float + :type pwcet: list :type et_stddev: float :type deadline: float :type base_cpi: float @@ -62,6 +63,7 @@ class TaskInfo(object): self.list_activation_dates = list_activation_dates self.data = data self.preemption_cost = preemption_cost + self._pwcet = pwcet @property def csdp(self): @@ -316,7 +318,7 @@ class PTask(GenericTask): self._sim.cycles_per_ms) while True: - #print self.sim.now(), "activate", self.name + # print self.sim.now(), "activate", self.name self.create_job() yield hold, self, int(self.period * self._sim.cycles_per_ms) @@ -341,13 +343,38 @@ class SporadicTask(GenericTask): return self._task_info.list_activation_dates +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'] + + def execute(self): + + self._init() + for ndate in self.list_activation_dates: + yield hold, self, int(ndate * self._sim.cycles_per_ms) \ + - self._sim.now() + self.create_job() + + @property + def list_activation_dates(self): + return self._task_info.list_activation_dates + + @property + def pwcet(self): + return self._task_info._pwcet + + task_types = { "Periodic": PTask, "APeriodic": ATask, - "Sporadic": SporadicTask + "Sporadic": SporadicTask, + "Probabilistic": ProbabilisticTask } -task_types_names = ["Periodic", "APeriodic", "Sporadic"] +task_types_names = ["Periodic", "APeriodic", "Sporadic", "Probabilistic"] def Task(sim, task_info): @@ -355,5 +382,4 @@ def Task(sim, task_info): Task factory. Return and instantiate the correct class according to the task_info. """ - return task_types[task_info.task_type](sim, task_info) diff --git a/simso/core/etm/PWCET.py b/simso/core/etm/PWCET.py new file mode 100644 index 0000000..1d8099f --- /dev/null +++ b/simso/core/etm/PWCET.py @@ -0,0 +1,52 @@ +import random +from simso.core.etm.AbstractExecutionTimeModel \ + import AbstractExecutionTimeModel + + +class PWCET(AbstractExecutionTimeModel): + def __init__(self, sim, _): + self.sim = sim + self.executed = {} + self.on_execute_date = {} + + def init(self): + pass + + def update_executed(self, job): + if job in self.on_execute_date: + self.executed[job] += (self.sim.now() - self.on_execute_date[job] + ) * job.cpu.speed + + del self.on_execute_date[job] + + 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 + + def on_execute(self, job): + self.on_execute_date[job] = self.sim.now() + + def on_preempted(self, job): + self.update_executed(job) + + def on_terminated(self, job): + self.update_executed(job) + + def on_abort(self, job): + self.update_executed(job) + + def get_executed(self, job): + if job in self.on_execute_date: + c = (self.sim.now() - self.on_execute_date[job]) * job.cpu.speed + else: + c = 0 + return self.executed[job] + c + + def get_ret(self, job): + wcet_cycles = int(job.wcet * self.sim.cycles_per_ms) + return int(wcet_cycles - self.get_executed(job)) + + def update(self): + for job in list(self.on_execute_date.keys()): + self.update_executed(job) diff --git a/simso/core/etm/__init__.py b/simso/core/etm/__init__.py index 14329a4..32550bc 100644 --- a/simso/core/etm/__init__.py +++ b/simso/core/etm/__init__.py @@ -1,5 +1,6 @@ from .WCET import WCET from .ACET import ACET +from .PWCET import PWCET from .CacheModel import CacheModel from .FixedPenalty import FixedPenalty @@ -7,12 +8,14 @@ execution_time_models = { 'wcet': WCET, 'acet': ACET, 'cache': CacheModel, - 'fixedpenalty': FixedPenalty + 'fixedpenalty': FixedPenalty, + 'pwcet': PWCET } execution_time_model_names = { 'WCET': 'wcet', 'ACET': 'acet', 'Cache Model': 'cache', - 'Fixed Penalty': 'fixedpenalty' + 'Fixed Penalty': 'fixedpenalty', + 'Probabilistic WCET': 'pwcet' } diff --git a/simso/generator/task_generator.py b/simso/generator/task_generator.py index 1fad270..1104613 100755 --- a/simso/generator/task_generator.py +++ b/simso/generator/task_generator.py @@ -5,6 +5,7 @@ Tools for generating task sets. import numpy as np import random import math +from functools import reduce def UUniFastDiscard(n, u, nsets): @@ -68,7 +69,7 @@ def StaffordRandFixedSum(n, u, nsets): if n < u: return None - #deal with n=1 case + # deal with n=1 case if n == 1: return np.tile(np.array([u]), [nsets, 1]) @@ -113,8 +114,8 @@ def StaffordRandFixedSum(n, u, nsets): x[n - 1, ...] = sm + pr * s - #iterated in fixed dimension order but needs to be randomised - #permute x row order within each column + # iterated in fixed dimension order but needs to be randomised + # permute x row order within each column for i in range(0, nsets): x[..., i] = x[np.random.permutation(n), i] @@ -218,10 +219,11 @@ def next_arrival_poisson(period): return -math.log(1.0 - random.random()) * period -def gen_arrivals(period, min_, max_, round_to_int=False): - def trunc(x, p): - return int(x * 10 ** p) / float(10 ** p) +def trunc(x, p): + return int(x * 10 ** p) / float(10 ** p) + +def gen_arrivals(period, min_, max_, round_to_int=False): dates = [] n = min_ - period while True: @@ -236,6 +238,28 @@ def gen_arrivals(period, min_, max_, round_to_int=False): return dates +def cdf(tuple_array): + sorted_tuples = sorted(tuple_array, key=lambda x: x[0]) + return [reduce(lambda x, y: (y[0], x[1]+y[1]), sorted_tuples[:i+1]) for i in range(0, len(sorted_tuples))] + + +def gen_probabilistic_arrivals(pmit, min_, max_, round_to_int=False): + dates = [min_] + cdf_mit = cdf(pmit) + + n = 0 + while True: + n += next(x[0] for x in cdf_mit if random.random() < x[1]) + if round_to_int: + n = int(round(n)) + else: + n = trunc(n, 6) + if n > max_: + break + dates.append(n) + return dates + + def gen_periods_loguniform(n, nsets, min_, max_, round_to_int=False): """ Generate a list of `nsets` sets containing each `n` random periods using a diff --git a/simso/schedulers/DM_mono.py b/simso/schedulers/DM_mono.py new file mode 100644 index 0000000..7c0aafd --- /dev/null +++ b/simso/schedulers/DM_mono.py @@ -0,0 +1,27 @@ +""" +Deadline Monotonic algorithm for uniprocessor architectures. +""" +from simso.core import Scheduler +from simso.schedulers import scheduler + +@scheduler("simso.schedulers.DM_mono") +class DM_mono(Scheduler): + def init(self): + self.ready_list = [] + + def on_activate(self, job): + self.ready_list.append(job) + job.cpu.resched() + + def on_terminated(self, job): + self.ready_list.remove(job) + job.cpu.resched() + + def schedule(self, cpu): + if self.ready_list: + # job with the highest priority based on deadline + job = min(self.ready_list, key=lambda x: x.task.deadline) + else: + job = None + + return (job, cpu)