1# SPDX-License-Identifier: Apache-2.0 2# 3# Copyright (C) 2015, ARM Limited and contributors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may 6# not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import fileinput 19import json 20import os 21import re 22 23from collections import namedtuple 24from wlgen import Workload 25from devlib.utils.misc import ranges_to_list 26 27import logging 28 29_Phase = namedtuple('Phase', 'duration_s, period_ms, duty_cycle_pct') 30class Phase(_Phase): 31 """ 32 Descriptor for an RT-App load phase 33 34 :param duration_s: the phase duration in [s]. 35 :type duration_s: int 36 37 :param period_ms: the phase period in [ms]. 38 :type period_ms: int 39 40 :param duty_cycle_pct: the generated load in [%]. 41 :type duty_cycle_pct: int 42 """ 43 pass 44 45class RTA(Workload): 46 """ 47 Class for creating RT-App workloads 48 """ 49 50 def __init__(self, 51 target, 52 name, 53 calibration=None): 54 """ 55 :param target: Devlib target to run workload on. 56 :param name: Human-readable name for the workload. 57 :param calibration: CPU calibration specification. Can be obtained from 58 :meth:`calibrate`. 59 """ 60 61 # Setup logging 62 self._log = logging.getLogger('RTApp') 63 64 # rt-app calibration 65 self.pload = calibration 66 67 # TODO: Assume rt-app is pre-installed on target 68 # self.target.setup('rt-app') 69 70 super(RTA, self).__init__(target, name) 71 72 # rt-app executor 73 self.wtype = 'rtapp' 74 self.executor = 'rt-app' 75 76 # Default initialization 77 self.json = None 78 self.rta_profile = None 79 self.loadref = None 80 self.rta_cmd = None 81 self.rta_conf = None 82 self.test_label = None 83 84 # Setup RTA callbacks 85 self.setCallback('postrun', self.__postrun) 86 87 @staticmethod 88 def calibrate(target): 89 """ 90 Calibrate RT-App on each CPU in the system 91 92 :param target: Devlib target to run calibration on. 93 :returns: Dict mapping CPU numbers to RT-App calibration values. 94 """ 95 pload_regexp = re.compile(r'pLoad = ([0-9]+)ns') 96 pload = {} 97 98 # Setup logging 99 log = logging.getLogger('RTApp') 100 101 # Save previous governors 102 old_governors = {} 103 for domain in target.cpufreq.iter_domains(): 104 cpu = domain[0] 105 governor = target.cpufreq.get_governor(cpu) 106 tunables = target.cpufreq.get_governor_tunables(cpu) 107 old_governors[cpu] = governor, tunables 108 109 target.cpufreq.set_all_governors('performance') 110 111 for cpu in target.list_online_cpus(): 112 113 log.info('CPU%d calibration...', cpu) 114 115 max_rtprio = int(target.execute('ulimit -Hr').split('\r')[0]) 116 log.debug('Max RT prio: %d', max_rtprio) 117 if max_rtprio > 10: 118 max_rtprio = 10 119 120 rta = RTA(target, 'rta_calib') 121 rta.conf(kind='profile', 122 params = { 123 'task1': Periodic( 124 period_ms=100, 125 duty_cycle_pct=50, 126 duration_s=1, 127 sched={ 128 'policy': 'FIFO', 129 'prio' : max_rtprio 130 } 131 ).get() 132 }, 133 cpus=[cpu]) 134 rta.run(as_root=True) 135 136 for line in rta.getOutput().split('\n'): 137 pload_match = re.search(pload_regexp, line) 138 if pload_match is None: 139 continue 140 pload[cpu] = int(pload_match.group(1)) 141 log.debug('>>> cpu%d: %d', cpu, pload[cpu]) 142 143 # Restore previous governors 144 # Setting a governor & tunables for a cpu will set them for all cpus 145 # in the same clock domain, so only restoring them for one cpu 146 # per domain is enough to restore them all. 147 for cpu, (governor, tunables) in old_governors.iteritems(): 148 target.cpufreq.set_governor(cpu, governor) 149 target.cpufreq.set_governor_tunables(cpu, **tunables) 150 151 log.info('Target RT-App calibration:') 152 log.info("{" + ", ".join('"%r": %r' % (key, pload[key]) 153 for key in pload) + "}") 154 155 # Sanity check calibration values for big.LITTLE systems 156 if 'bl' in target.modules: 157 bcpu = target.bl.bigs_online[0] 158 lcpu = target.bl.littles_online[0] 159 if pload[bcpu] > pload[lcpu]: 160 log.warning('Calibration values reports big cores less ' 161 'capable than LITTLE cores') 162 raise RuntimeError('Calibration failed: try again or file a bug') 163 bigs_speedup = ((float(pload[lcpu]) / pload[bcpu]) - 1) * 100 164 log.info('big cores are ~%.0f%% more capable than LITTLE cores', 165 bigs_speedup) 166 167 return pload 168 169 def __postrun(self, params): 170 destdir = params['destdir'] 171 if destdir is None: 172 return 173 self._log.debug('Pulling logfiles to [%s]...', destdir) 174 for task in self.tasks.keys(): 175 logfile = self.target.path.join(self.run_dir, 176 '*{}*.log'.format(task)) 177 self.target.pull(logfile, destdir) 178 self._log.debug('Pulling JSON to [%s]...', destdir) 179 self.target.pull(self.target.path.join(self.run_dir, self.json), 180 destdir) 181 logfile = self.target.path.join(destdir, 'output.log') 182 self._log.debug('Saving output on [%s]...', logfile) 183 with open(logfile, 'w') as ofile: 184 for line in self.output['executor'].split('\n'): 185 ofile.write(line+'\n') 186 187 def _getFirstBiggest(self, cpus): 188 # Non big.LITTLE system: 189 if 'bl' not in self.target.modules: 190 # return the first CPU of the last cluster 191 platform = self.target.platform 192 cluster_last = list(set(platform.core_clusters))[-1] 193 cluster_cpus = [cpu_id 194 for cpu_id, cluster_id in enumerate(platform.core_clusters) 195 if cluster_id == cluster_last] 196 # If CPUs have been specified': return the fist in the last cluster 197 if cpus: 198 for cpu_id in cpus: 199 if cpu_id in cluster_cpus: 200 return cpu_id 201 # Otherwise just return the first cpu of the last cluster 202 return cluster_cpus[0] 203 204 # big.LITTLE system: 205 for c in cpus: 206 if c not in self.target.bl.bigs: 207 continue 208 return c 209 # Only LITTLE CPUs, thus: 210 # return the first possible cpu 211 return cpus[0] 212 213 def _getFirstBig(self, cpus=None): 214 # Non big.LITTLE system: 215 if 'bl' not in self.target.modules: 216 return self._getFirstBiggest(cpus) 217 if cpus: 218 for c in cpus: 219 if c not in self.target.bl.bigs: 220 continue 221 return c 222 # Only LITTLE CPUs, thus: 223 # return the first big core of the system 224 if self.target.big_core: 225 # Big.LITTLE system 226 return self.target.bl.bigs[0] 227 return 0 228 229 def _getFirstLittle(self, cpus=None): 230 # Non big.LITTLE system: 231 if 'bl' not in self.target.modules: 232 # return the first CPU of the first cluster 233 platform = self.target.platform 234 cluster_first = list(set(platform.core_clusters))[0] 235 cluster_cpus = [cpu_id 236 for cpu_id, cluster_id in enumerate(platform.core_clusters) 237 if cluster_id == cluster_first] 238 # If CPUs have been specified': return the fist in the first cluster 239 if cpus: 240 for cpu_id in cpus: 241 if cpu_id in cluster_cpus: 242 return cpu_id 243 # Otherwise just return the first cpu of the first cluster 244 return cluster_cpus[0] 245 246 # Try to return one LITTLE CPUs among the specified ones 247 if cpus: 248 for c in cpus: 249 if c not in self.target.bl.littles: 250 continue 251 return c 252 # Only big CPUs, thus: 253 # return the first LITTLE core of the system 254 if self.target.little_core: 255 # Big.LITTLE system 256 return self.target.bl.littles[0] 257 return 0 258 259 def getTargetCpu(self, loadref): 260 # Select CPU for task calibration, which is the first little 261 # of big depending on the loadref tag 262 if self.pload is not None: 263 if loadref and loadref.upper() == 'LITTLE': 264 target_cpu = self._getFirstLittle() 265 self._log.debug('ref on LITTLE cpu: %d', target_cpu) 266 else: 267 target_cpu = self._getFirstBig() 268 self._log.debug('ref on big cpu: %d', target_cpu) 269 return target_cpu 270 271 # These options are selected only when RTApp has not been 272 # already calibrated 273 if self.cpus is None: 274 target_cpu = self._getFirstBig() 275 self._log.debug('ref on cpu: %d', target_cpu) 276 else: 277 target_cpu = self._getFirstBiggest(self.cpus) 278 self._log.debug('ref on (possible) biggest cpu: %d', target_cpu) 279 return target_cpu 280 281 def getCalibrationConf(self, target_cpu=0): 282 if self.pload is None: 283 return 'CPU{0:d}'.format(target_cpu) 284 return self.pload[target_cpu] 285 286 def _confCustom(self): 287 288 rtapp_conf = self.params['custom'] 289 290 # Sanity check params being a valid file path 291 if not isinstance(rtapp_conf, str) or \ 292 not os.path.isfile(rtapp_conf): 293 self._log.debug('Checking for %s', rtapp_conf) 294 raise ValueError('value specified for \'params\' is not ' 295 'a valid rt-app JSON configuration file') 296 297 if self.duration is None: 298 raise ValueError('Workload duration not specified') 299 300 target_cpu = self.getTargetCpu(self.loadref) 301 calibration = self.getCalibrationConf(target_cpu) 302 303 self._log.info('Loading custom configuration:') 304 self._log.info(' %s', rtapp_conf) 305 self.json = '{0:s}_{1:02d}.json'.format(self.name, self.exc_id) 306 ofile = open(self.json, 'w') 307 ifile = open(rtapp_conf, 'r') 308 309 # Calibration can either be a string like "CPU1" or an integer, if the 310 # former we need to quote it. 311 if type(calibration) != int: 312 calibration = '"{}"'.format(calibration) 313 314 replacements = { 315 '__DURATION__' : str(self.duration), 316 '__PVALUE__' : str(calibration), 317 '__LOGDIR__' : str(self.run_dir), 318 '__WORKDIR__' : '"'+self.target.working_directory+'"', 319 } 320 321 for line in ifile: 322 for src, target in replacements.iteritems(): 323 line = line.replace(src, target) 324 ofile.write(line) 325 ifile.close() 326 ofile.close() 327 328 with open(self.json) as f: 329 conf = json.load(f) 330 for tid in conf['tasks']: 331 self.tasks[tid] = {'pid': -1} 332 333 return self.json 334 335 def _confProfile(self): 336 337 # Sanity check for task names 338 for task in self.params['profile'].keys(): 339 if len(task) > 15: 340 # rt-app uses pthread_setname_np(3) which limits the task name 341 # to 16 characters including the terminal '\0'. 342 msg = ('Task name "{}" too long, please configure your tasks ' 343 'with names shorter than 16 characters').format(task) 344 raise ValueError(msg) 345 346 # Task configuration 347 target_cpu = self.getTargetCpu(self.loadref) 348 self.rta_profile = { 349 'tasks': {}, 350 'global': {} 351 } 352 353 # Initialize global configuration 354 global_conf = { 355 'default_policy': 'SCHED_OTHER', 356 'duration': -1, 357 'calibration': 'CPU'+str(target_cpu), 358 'logdir': self.run_dir, 359 } 360 361 # Setup calibration data 362 calibration = self.getCalibrationConf(target_cpu) 363 global_conf['calibration'] = calibration 364 if self.duration is not None: 365 global_conf['duration'] = self.duration 366 self._log.warn('Limiting workload duration to %d [s]', 367 global_conf['duration']) 368 else: 369 self._log.info('Workload duration defined by longest task') 370 371 # Setup default scheduling class 372 if 'policy' in self.sched: 373 policy = self.sched['policy'].upper() 374 if policy not in ['OTHER', 'FIFO', 'RR', 'DEADLINE']: 375 raise ValueError('scheduling class {} not supported'\ 376 .format(policy)) 377 global_conf['default_policy'] = 'SCHED_' + self.sched['policy'] 378 379 self._log.info('Default policy: %s', global_conf['default_policy']) 380 381 # Setup global configuration 382 self.rta_profile['global'] = global_conf 383 384 # Setup tasks parameters 385 for tid in sorted(self.params['profile'].keys()): 386 task = self.params['profile'][tid] 387 388 # Initialize task configuration 389 task_conf = {} 390 391 if 'sched' not in task: 392 policy = 'DEFAULT' 393 else: 394 policy = task['sched']['policy'].upper() 395 if policy == 'DEFAULT': 396 task_conf['policy'] = global_conf['default_policy'] 397 sched_descr = 'sched: using default policy' 398 elif policy not in ['OTHER', 'FIFO', 'RR', 'DEADLINE']: 399 raise ValueError('scheduling class {} not supported'\ 400 .format(task['sclass'])) 401 else: 402 task_conf.update(task['sched']) 403 task_conf['policy'] = 'SCHED_' + policy 404 sched_descr = 'sched: {0:s}'.format(task['sched']) 405 406 # Initialize task phases 407 task_conf['phases'] = {} 408 409 self._log.info('------------------------') 410 self._log.info('task [%s], %s', tid, sched_descr) 411 412 if 'delay' in task.keys(): 413 if task['delay'] > 0: 414 task_conf['phases']['p000000'] = {} 415 task_conf['phases']['p000000']['delay'] = int(task['delay'] * 1e6) 416 self._log.info(' | start delay: %.6f [s]', 417 task['delay']) 418 419 self._log.info(' | calibration CPU: %d', target_cpu) 420 421 if 'loops' not in task.keys(): 422 task['loops'] = 1 423 task_conf['loop'] = task['loops'] 424 self._log.info(' | loops count: %d', task['loops']) 425 426 # Setup task affinity 427 if 'cpus' in task and task['cpus']: 428 self._log.info(' | CPUs affinity: %s', task['cpus']) 429 if isinstance(task['cpus'], str): 430 task_conf['cpus'] = ranges_to_list(task['cpus']) 431 elif isinstance(task['cpus'], list): 432 task_conf['cpus'] = task['cpus'] 433 else: 434 raise ValueError('cpus must be a list or string') 435 436 437 # Setup task configuration 438 self.rta_profile['tasks'][tid] = task_conf 439 440 # Getting task phase descriptor 441 pid=1 442 for phase in task['phases']: 443 444 # Convert time parameters to integer [us] units 445 duration = int(phase.duration_s * 1e6) 446 period = int(phase.period_ms * 1e3) 447 448 # A duty-cycle of 0[%] translates on a 'sleep' phase 449 if phase.duty_cycle_pct == 0: 450 451 self._log.info(' + phase_%06d: sleep %.6f [s]', 452 pid, duration/1e6) 453 454 task_phase = { 455 'loop': 1, 456 'sleep': duration, 457 } 458 459 # A duty-cycle of 100[%] translates on a 'run-only' phase 460 elif phase.duty_cycle_pct == 100: 461 462 self._log.info(' + phase_%06d: batch %.6f [s]', 463 pid, duration/1e6) 464 465 task_phase = { 466 'loop': 1, 467 'run': duration, 468 } 469 470 # A certain number of loops is requires to generate the 471 # proper load 472 else: 473 474 cloops = -1 475 if duration >= 0: 476 cloops = int(duration / period) 477 478 sleep_time = period * (100 - phase.duty_cycle_pct) / 100 479 running_time = period - sleep_time 480 481 self._log.info('+ phase_%06d: duration %.6f [s] (%d loops)', 482 pid, duration/1e6, cloops) 483 self._log.info('| period %6d [us], duty_cycle %3d %%', 484 period, phase.duty_cycle_pct) 485 self._log.info('| run_time %6d [us], sleep_time %6d [us]', 486 running_time, sleep_time) 487 488 task_phase = { 489 'loop': cloops, 490 'run': running_time, 491 'timer': {'ref': tid, 'period': period}, 492 } 493 494 self.rta_profile['tasks'][tid]['phases']\ 495 ['p'+str(pid).zfill(6)] = task_phase 496 497 pid+=1 498 499 # Append task name to the list of this workload tasks 500 self.tasks[tid] = {'pid': -1} 501 502 # Generate JSON configuration on local file 503 self.json = '{0:s}_{1:02d}.json'.format(self.name, self.exc_id) 504 with open(self.json, 'w') as outfile: 505 json.dump(self.rta_profile, outfile, 506 sort_keys=True, indent=4, separators=(',', ': ')) 507 508 return self.json 509 510 def conf(self, 511 kind, 512 params, 513 duration=None, 514 cpus=None, 515 sched=None, 516 run_dir=None, 517 exc_id=0, 518 loadref='big'): 519 """ 520 Configure a workload of a specified kind. 521 522 The rt-app based workload allows to define different classes of 523 workloads. The classes supported so far are detailed hereafter. 524 525 Custom workloads 526 When 'kind' is 'custom' the tasks generated by this workload are the 527 ones defined in a provided rt-app JSON configuration file. 528 In this case the 'params' parameter must be used to specify the 529 complete path of the rt-app JSON configuration file to use. 530 531 Profile based workloads 532 When ``kind`` is "profile", ``params`` is a dictionary mapping task 533 names to task specifications. The easiest way to create these task 534 specifications using :meth:`RTATask.get`. 535 536 For example, the following configures an RTA workload with a single 537 task, named 't1', using the default parameters for a Periodic RTATask: 538 539 :: 540 541 wl = RTA(...) 542 wl.conf(kind='profile', params={'t1': Periodic().get()}) 543 544 :param kind: Either 'custom' or 'profile' - see above. 545 :param params: RT-App parameters - see above. 546 :param duration: Maximum duration of the workload in seconds. Any 547 remaining tasks are killed by rt-app when this time has 548 elapsed. 549 :param cpus: CPUs to restrict this workload to, using ``taskset``. 550 :type cpus: list(int) 551 552 :param sched: Global RT-App scheduler configuration. Dict with fields: 553 554 policy 555 The default scheduler policy. Choose from 'OTHER', 'FIFO', 'RR', 556 and 'DEADLINE'. 557 558 :param run_dir: Target dir to store output and config files in. 559 560 .. TODO: document or remove loadref 561 """ 562 563 if not sched: 564 sched = {'policy' : 'OTHER'} 565 566 super(RTA, self).conf(kind, params, duration, 567 cpus, sched, run_dir, exc_id) 568 569 self.loadref = loadref 570 571 # Setup class-specific configuration 572 if kind == 'custom': 573 self._confCustom() 574 elif kind == 'profile': 575 self._confProfile() 576 577 # Move configuration file to target 578 self.target.push(self.json, self.run_dir) 579 580 self.rta_cmd = self.target.executables_directory + '/rt-app' 581 self.rta_conf = self.run_dir + '/' + self.json 582 self.command = '{0:s} {1:s} 2>&1'.format(self.rta_cmd, self.rta_conf) 583 584 # Set and return the test label 585 self.test_label = '{0:s}_{1:02d}'.format(self.name, self.exc_id) 586 return self.test_label 587 588class RTATask(object): 589 """ 590 Base class for conveniently constructing params to :meth:`RTA.conf` 591 592 This class represents an RT-App task which may contain multiple phases. It 593 implements ``__add__`` so that using ``+`` on two tasks concatenates their 594 phases. For example ``Ramp() + Periodic()`` would yield an ``RTATask`` that 595 executes the default phases for ``Ramp`` followed by the default phases for 596 ``Periodic``. 597 """ 598 599 def __init__(self): 600 self._task = {} 601 602 def get(self): 603 """ 604 Return a dict that can be passed as an element of the ``params`` field 605 to :meth:`RTA.conf`. 606 """ 607 return self._task 608 609 def __add__(self, next_phases): 610 self._task['phases'].extend(next_phases._task['phases']) 611 return self 612 613 614class Ramp(RTATask): 615 """ 616 Configure a ramp load. 617 618 This class defines a task which load is a ramp with a configured number 619 of steps according to the input parameters. 620 621 :param start_pct: the initial load percentage. 622 :param end_pct: the final load percentage. 623 :param delta_pct: the load increase/decrease at each step, in percentage 624 points. 625 :param time_s: the duration in seconds of each load step. 626 :param period_ms: the period used to define the load in [ms]. 627 :param delay_s: the delay in seconds before ramp start. 628 :param loops: number of time to repeat the ramp, with the specified delay in 629 between. 630 631 :param sched: the scheduler configuration for this task. 632 :type sched: dict 633 634 :param cpus: the list of CPUs on which task can run. 635 :type cpus: list(int) 636 """ 637 638 def __init__(self, start_pct=0, end_pct=100, delta_pct=10, time_s=1, 639 period_ms=100, delay_s=0, loops=1, sched=None, cpus=None): 640 super(Ramp, self).__init__() 641 642 self._task['cpus'] = cpus 643 if not sched: 644 sched = {'policy' : 'DEFAULT'} 645 self._task['sched'] = sched 646 self._task['delay'] = delay_s 647 self._task['loops'] = loops 648 649 if start_pct not in range(0,101) or end_pct not in range(0,101): 650 raise ValueError('start_pct and end_pct must be in [0..100] range') 651 652 if start_pct >= end_pct: 653 if delta_pct > 0: 654 delta_pct = -delta_pct 655 delta_adj = -1 656 if start_pct <= end_pct: 657 if delta_pct < 0: 658 delta_pct = -delta_pct 659 delta_adj = +1 660 661 phases = [] 662 steps = range(start_pct, end_pct+delta_adj, delta_pct) 663 for load in steps: 664 if load == 0: 665 phase = Phase(time_s, 0, 0) 666 else: 667 phase = Phase(time_s, period_ms, load) 668 phases.append(phase) 669 670 self._task['phases'] = phases 671 672class Step(Ramp): 673 """ 674 Configure a step load. 675 676 This class defines a task which load is a step with a configured initial and 677 final load. Using the ``loops`` param, this can be used to create a workload 678 that alternates between two load values. 679 680 :param start_pct: the initial load percentage. 681 :param end_pct: the final load percentage. 682 :param time_s: the duration in seconds of each load step. 683 :param period_ms: the period used to define the load in [ms]. 684 :param delay_s: the delay in seconds before ramp start. 685 :param loops: number of time to repeat the step, with the specified delay in 686 between. 687 688 :param sched: the scheduler configuration for this task. 689 :type sched: dict 690 691 :param cpus: the list of CPUs on which task can run. 692 :type cpus: list(int) 693 """ 694 695 def __init__(self, start_pct=0, end_pct=100, time_s=1, period_ms=100, 696 delay_s=0, loops=1, sched=None, cpus=None): 697 delta_pct = abs(end_pct - start_pct) 698 super(Step, self).__init__(start_pct, end_pct, delta_pct, time_s, 699 period_ms, delay_s, loops, sched, cpus) 700 701class Pulse(RTATask): 702 """ 703 Configure a pulse load. 704 705 This class defines a task which load is a pulse with a configured 706 initial and final load. 707 708 The main difference with the 'step' class is that a pulse workload is 709 by definition a 'step down', i.e. the workload switch from an finial 710 load to a final one which is always lower than the initial one. 711 Moreover, a pulse load does not generate a sleep phase in case of 0[%] 712 load, i.e. the task ends as soon as the non null initial load has 713 completed. 714 715 :param start_pct: the initial load percentage. 716 :param end_pct: the final load percentage. Must be lower than ``start_pct`` 717 value. If end_pct is 0, the task end after the ``start_pct`` 718 period has completed. 719 :param time_s: the duration in seconds of each load step. 720 :param period_ms: the period used to define the load in [ms]. 721 :param delay_s: the delay in seconds before ramp start. 722 :param loops: number of time to repeat the pulse, with the specified delay 723 in between. 724 725 :param sched: the scheduler configuration for this task. 726 :type sched: dict 727 728 :param cpus: the list of CPUs on which task can run 729 :type cpus: list(int) 730 """ 731 732 def __init__(self, start_pct=100, end_pct=0, time_s=1, period_ms=100, 733 delay_s=0, loops=1, sched=None, cpus=None): 734 super(Pulse, self).__init__() 735 736 if end_pct >= start_pct: 737 raise ValueError('end_pct must be lower than start_pct') 738 739 self._task = {} 740 741 self._task['cpus'] = cpus 742 if not sched: 743 sched = {'policy' : 'DEFAULT'} 744 self._task['sched'] = sched 745 self._task['delay'] = delay_s 746 self._task['loops'] = loops 747 self._task['phases'] = {} 748 749 if end_pct not in range(0,101) or start_pct not in range(0,101): 750 raise ValueError('end_pct and start_pct must be in [0..100] range') 751 if end_pct >= start_pct: 752 raise ValueError('end_pct must be lower than start_pct') 753 754 phases = [] 755 for load in [start_pct, end_pct]: 756 if load == 0: 757 continue 758 phase = Phase(time_s, period_ms, load) 759 phases.append(phase) 760 761 self._task['phases'] = phases 762 763 764class Periodic(Pulse): 765 """ 766 Configure a periodic load. This is the simplest type of RTA task. 767 768 This class defines a task which load is periodic with a configured 769 period and duty-cycle. 770 771 :param duty_cycle_pct: the load percentage. 772 :param duration_s: the total duration in seconds of the task. 773 :param period_ms: the period used to define the load in milliseconds. 774 :param delay_s: the delay in seconds before starting the periodic phase. 775 776 :param sched: the scheduler configuration for this task. 777 :type sched: dict 778 779 :param cpus: the list of CPUs on which task can run. 780 :type cpus: list(int) 781 """ 782 783 def __init__(self, duty_cycle_pct=50, duration_s=1, period_ms=100, 784 delay_s=0, sched=None, cpus=None): 785 super(Periodic, self).__init__(duty_cycle_pct, 0, duration_s, 786 period_ms, delay_s, 1, sched, cpus) 787