#!/usr/bin/env python3 """ Generates real time tasksets according to the method described in 'A Comparison of Global and Partitioned EDF Schedulability Tests for Multiprocessors'. Therefore tasks with random periods within the range 1 to 100 are generated. Depending on the mode, the utilization of these tasks is either determined by * uniform distribution between 1/period and 1 * bimodal distribution heavy tasks with uniform distribution between 0.5 / 1 and light tasks uniform between 0.1 and 0.5 where the probability of being heavy is 1/3 * exponential distribution with mean 0.25 * exponential distribution with mean 0.50 """ import datetime import shutil import sys import os import random import argparse import json import math import copy from enum import Enum def query_yes_no(question, default=None): """ Queries the user for a decision. """ if default is None: prompt = ' [y/n]' elif default.lower() == 'yes': prompt = ' [Y/n]' elif default.lower() == 'no': prompt = ' [y/N]' else: raise ValueError('Invalid default answer {}'.format(default)) while True: print(question, prompt, end='') choice = input().lower() if 'yes'.find(choice) == 0: return True elif 'no'.find(choice) == 0: return False def gcd(a, b): if b == 0: return a return gcd(b, a % b) def lcm(a, b): return (a * b) / gcd(a, b) def hyperperiod(tasks): try: periods = [task.period for task in tasks] hyperperiod = periods[0] for period in periods[1:]: hyperperiod = lcm(hyperperiod, period) return hyperperiod except IndexError: return 0 class Timebase(Enum): seconds = 1 milliseconds = 2 microseconds = 3 nanoseconds = 4 class Distribution(Enum): uniform = 1 bimodal = 2 exp_quart = 3 exp_half = 4 class Task: """ Representation of a generated task. """ def __init__(self, wcet, period, deadline): self.wcet = int(wcet) self.period = int(period) self.deadline = int(deadline) def calc_utilization(tasklist): sum = 0 for task in tasklist: sum += task.wcet / task.period return sum def get_timebase_string(base): if base is Timebase.seconds: return 'std::chrono::seconds', 'embb::base::DurationSeconds' elif base is Timebase.milliseconds: return 'std::chrono::milliseconds', 'embb::base::DurationMilliseconds' elif base is Timebase.microseconds: return 'std::chrono::microseconds', 'embb::base::DurationMicroseconds' elif base is Timebase.nanoseconds: return 'std::chrono::nanoseconds', 'embb::base::DurationNanoseconds' def create_task(distribution): period = random.uniform(10, 100) if distribution is Distribution.uniform: util = random.uniform(1.0 / period, 1.0) elif distribution is Distribution.bimodal: check = random.uniform(0.0, 1.0) if check < 1 / 3: util = random.uniform(0.5, 1.0) else: util = random.uniform(1.0 / period, 1.0) elif distribution is Distribution.exp_quart: util = random.expovariant(1.0 / 0.25) elif distribution is Distribution.exp_half: util = random.expovariant(1.0 / 0.5) util = min(1.0, max(0.0, util)) wcet = period * util deadline = random.uniform(wcet, period) return Task(wcet, period, deadline) def main(): parser = argparse.ArgumentParser(description='Generate tasksets.') parser.add_argument('cores', type=int, help='Number of cores the taskset' 'should be generated for.') parser.add_argument('tasksetcount', type=int, help='Number of tasksets that' 'should be generated.') parser.add_argument('target', type=str, help='Output directory.', nargs='?', default='.') parser.add_argument('experiment_target', type=str, help='Output directory for experiments.', nargs='?', default='.') parser.add_argument('--utilization', type=float, help='The utility for which tasksets should be generated. Is a range such as 2.5 3.5', nargs=2) parser.add_argument('--baseclock', type=str, default='system_clock', nargs='?', help='The clock which is to be used for the execution.') timebase_grp = parser.add_mutually_exclusive_group(required=True) timebase_grp.add_argument('--seconds', action='store_const', const=Timebase.seconds) timebase_grp.add_argument('--milliseconds', action='store_const', const=Timebase.milliseconds) timebase_grp.add_argument('--microseconds', action='store_const', const=Timebase.microseconds) timebase_grp.add_argument('--nanoseconds', action='store_const', const=Timebase.nanoseconds) distr_grp = parser.add_mutually_exclusive_group() distr_grp.add_argument('--uniform', action='store_const', const=Distribution.uniform) distr_grp.add_argument('--bimodal', action='store_const', const=Distribution.bimodal) distr_grp.add_argument('--exp_quart', action='store_const', const=Distribution.exp_quart) distr_grp.add_argument('--exp_half', action='store_const', const=Distribution.exp_half) args = parser.parse_args() if args.seconds is not None: timebase = args.seconds elif args.milliseconds is not None: timebase = args.milliseconds elif args.microseconds is not None: timebase = args.microseconds elif args.nanoseconds is not None: timebase = args.nanoseconds if args.uniform is not None: distribution = args.uniform elif args.bimodal is not None: distribution = args.bimodal elif args.exp_quart is not None: distribution = args.exp_quart elif args.exp_half is not None: distribution = args.exp_half else: distribution = Distribution.uniform utility = None try: utility = args.utilization except AttributeError: pass print('Generating tasks…', file=sys.stderr) tasksets = [] while len(tasksets) < args.tasksetcount: taskset = [] if utility is None: while len(taskset) < args.cores + 1 or \ calc_utilization(taskset) <= args.cores and \ hyperperiod(taskset) < 300000: # Limit hyperperiod to 5 minutes taskset.append(create_task(distribution)) if len(taskset) >= args.cores + 1: tasksets.append(taskset) else: taskset_util = 0 while taskset_util < utility[1] and hyperperiod(taskset) < 300000: taskset.append(create_task(distribution)) taskset_util = calc_utilization(taskset) if utility[0] <= taskset_util <= utility[1]: if len(taskset) >= args.cores + 1: tasksets.append(taskset) taskset = copy.deepcopy(taskset) now = datetime.datetime.now() cpp_base, embb_base = get_timebase_string(timebase) try: os.makedirs(args.target) except FileExistsError: if not query_yes_no('Folder exists, remove?', default='yes'): sys.exit(1) shutil.rmtree(args.target) os.makedirs(args.target) print('Writing tasks…', file=sys.stderr) taken_names={} for taskset in tasksets: task_out = [] for task in taskset: out = {} out['wcet'] = task.wcet out['period'] = task.period out['deadline'] = task.deadline task_out.append(out) utilization = str(round(calc_utilization(taskset), 2)) utilization.replace('.', '_') name = 'taskset_{}_{}_{}'.format(now.strftime('%Y_%m_%d_%H_%M_%S'), len(taskset), utilization) try: taken_names[name] += 1 except KeyError: taken_names[name] = 1 name += '_{}'.format(taken_names[name]) out = {} out['name'] = name out['template'] = 'templates/normal/' out['includes'] = [ { 'name' : '' } ] out['cpp_time_base'] = cpp_base out['embb_time_base'] = embb_base out['base_clock'] = 'std::chrono::system_clock' out['data_description'] = [ { 'name' : 'task', 'fields' : [ { 'type' : 'int', 'name' : 'wcet' }, { 'type' : 'int', 'name' : 'period' }, { 'type' : 'int', 'name' : 'deadline' }, { 'type' : 'int', 'name' : 'count' } ] } ] out['data'] = [ { 'type' : 'task', 'name' : 'taskset', 'elem' : task_out } ] try: out['output'] = args.experiment_target except KeyError: pass outname = os.path.join(args.target, name + '.json') with open(outname, 'w') as outfile: outfile.write(json.dumps(out)) if __name__ == '__main__': main()