Home | History | Annotate | Download | only in lisa
      1 # SPDX-License-Identifier: Apache-2.0
      2 #
      3 # Copyright (C) 2017, 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 
     18 from collections import OrderedDict, namedtuple
     19 import json
     20 import os
     21 
     22 from perf_analysis import PerfAnalysis
     23 from wlgen import RTA, Periodic, Ramp, Step
     24 
     25 from test_wlgen import WlgenSelfBase
     26 
     27 class RTABase(WlgenSelfBase):
     28     """
     29     Common functionality for testing RTA
     30 
     31     Doesn't have "Test" in the name so that nosetests doesn't try to run it
     32     directly
     33     """
     34 
     35     tools = ['rt-app']
     36 
     37     def get_expected_command(self, rta_wload):
     38         """Return the rt-app command we should execute when `run` is called"""
     39         rta_path = os.path.join(self.target.executables_directory, 'rt-app')
     40         json_path = os.path.join(rta_wload.run_dir, rta_wload.json)
     41         return '{} {} 2>&1'.format(rta_path, json_path)
     42 
     43     def setUp(self):
     44         super(RTABase, self).setUp()
     45 
     46         # Can't calibrate rt-app because:
     47         # - Need to set performance governor
     48         # - Need to use SCHED_FIFO + high priority
     49         # We probably don't have permissions so use a dummy calibration.
     50         self.calibration = {c: 100
     51                            for c in range(len(self.target.cpuinfo.cpu_names))}
     52 
     53         os.makedirs(self.host_out_dir)
     54 
     55     def assert_output_file_exists(self, path):
     56         """Assert that a file was created in host_out_dir"""
     57         path = os.path.join(self.host_out_dir, path)
     58         self.assertTrue(os.path.isfile(path),
     59                         'No output file {} from rt-app'.format(path))
     60 
     61     def assert_can_read_logfile(self, exp_tasks):
     62         """Assert that the perf_analysis module understands the log output"""
     63         pa = PerfAnalysis(self.host_out_dir)
     64         self.assertSetEqual(set(exp_tasks), set(pa.tasks()))
     65 
     66 class TestRTAProfile(RTABase):
     67     def _do_test(self, task, exp_phases):
     68         rtapp = RTA(self.target, name='test', calibration=self.calibration)
     69 
     70         rtapp.conf(
     71             kind = 'profile',
     72             params = {'my_task': task.get()},
     73             run_dir=self.target_run_dir
     74         )
     75 
     76         with open(rtapp.json) as f:
     77             conf = json.load(f, object_pairs_hook=OrderedDict)
     78 
     79         # Check that the configuration looks like we expect it to
     80         phases = conf['tasks']['my_task']['phases'].values()
     81         self.assertEqual(len(phases), len(exp_phases), 'Wrong number of phases')
     82         for phase, exp_phase in zip(phases, exp_phases):
     83             self.assertDictEqual(phase, exp_phase)
     84 
     85         # Try running the workload and check that it produces the expected log
     86         # files
     87         rtapp.run(out_dir=self.host_out_dir)
     88 
     89         rtapp_cmds = [c for c in self.target.executed_commands if 'rt-app' in c]
     90         self.assertListEqual(rtapp_cmds, [self.get_expected_command(rtapp)])
     91 
     92         self.assert_output_file_exists('output.log')
     93         self.assert_output_file_exists('test_00.json')
     94         self.assert_output_file_exists('rt-app-my_task-0.log')
     95         self.assert_can_read_logfile(exp_tasks=['my_task'])
     96 
     97     def test_profile_periodic_smoke(self):
     98         """
     99         Smoketest Periodic rt-app workload
    100 
    101         Creates a workload using Periodic, tests that the JSON has the expected
    102         content, then tests that it can be run.
    103         """
    104 
    105         task = Periodic(period_ms=100, duty_cycle_pct=20, duration_s=1)
    106 
    107         exp_phases = [
    108             {
    109                 'loop': 10,
    110                 'run': 20000,
    111                 'timer': {
    112                     'period': 100000,
    113                     'ref': 'my_task'
    114                 }
    115             }
    116         ]
    117 
    118         self._do_test(task, exp_phases)
    119 
    120     def test_profile_step_smoke(self):
    121         """
    122         Smoketest Step rt-app workload
    123 
    124         Creates a workload using Step, tests that the JSON has the expected
    125         content, then tests that it can be run.
    126         """
    127 
    128         task = Step(start_pct=100, end_pct=0, time_s=1)
    129 
    130         exp_phases = [
    131             {
    132                 'run': 1000000,
    133                 'loop': 1
    134             },
    135             {
    136                 'sleep': 1000000,
    137                 'loop': 1
    138             },
    139         ]
    140 
    141         self._do_test(task, exp_phases)
    142 
    143     def test_composition(self):
    144         """
    145         Test RTA task composition with __add__
    146 
    147         Creates a composed workload by +-ing RTATask objects, tests that the
    148         JSON has the expected content, then tests running the workload
    149         """
    150         light  = Periodic(duty_cycle_pct=10, duration_s=1.0, period_ms=10)
    151 
    152         start_pct = 10
    153         end_pct = 90
    154         delta_pct = 20
    155         num_ramp_phases = ((end_pct - start_pct) / delta_pct) + 1
    156         ramp = Ramp(start_pct=start_pct, end_pct=end_pct, delta_pct=delta_pct,
    157                     time_s=1, period_ms=50)
    158 
    159         heavy = Periodic(duty_cycle_pct=90, duration_s=0.1, period_ms=100)
    160 
    161         task = light + ramp + heavy
    162 
    163         exp_phases = [
    164             # Light phase:
    165             {
    166                 "loop": 100,
    167                 "run": 1000,
    168                 "timer": {
    169                     "period": 10000,
    170                     "ref": "my_task"
    171                 }
    172             },
    173             # Ramp phases:
    174             {
    175                 "loop": 20,
    176                 "run": 5000,
    177                 "timer": {
    178                     "period": 50000,
    179                     "ref": "my_task"
    180                 }
    181             },
    182             {
    183                 "loop": 20,
    184                 "run": 15000,
    185                 "timer": {
    186                     "period": 50000,
    187                     "ref": "my_task"
    188                 }
    189             },
    190             {
    191                 "loop": 20,
    192                 "run": 25000,
    193                 "timer": {
    194                     "period": 50000,
    195                     "ref": "my_task"
    196                 }
    197             },
    198             {
    199                 "loop": 20,
    200                 "run": 35000,
    201                 "timer": {
    202                     "period": 50000,
    203                     "ref": "my_task"
    204                 }
    205             },
    206             {
    207                 "loop": 20,
    208                 "run": 45000,
    209                 "timer": {
    210                     "period": 50000,
    211                     "ref": "my_task"
    212                 }
    213             },
    214             # Heavy phase:
    215             {
    216                 "loop": 1,
    217                 "run": 90000,
    218                 "timer": {
    219                     "period": 100000,
    220                     "ref": "my_task"
    221                 }
    222             }]
    223 
    224 
    225         self._do_test(task, exp_phases)
    226 
    227     def test_invalid_composition(self):
    228         """Test that you can't compose tasks with a delay in the second task"""
    229         t1 = Periodic()
    230         t2 = Periodic(delay_s=1)
    231 
    232         # Should work fine if delayed task is the first one
    233         try:
    234             t3 = t2 + t1
    235         except Exception as e:
    236             raise AssertionError("Couldn't compose tasks: {}".format(e))
    237 
    238         # But not the other way around
    239         with self.assertRaises(ValueError):
    240             t3 = t1 + t2
    241 
    242 
    243 class TestRTACustom(RTABase):
    244     def _test_custom_smoke(self, calibration):
    245         """
    246         Test RTA custom workload
    247 
    248         Creates an rt-app workload using 'custom' and checks that the json
    249         roughly matches the file we provided. If we have root, attempts to run
    250         the workload.
    251         """
    252 
    253         json_path = os.path.join(os.getenv('LISA_HOME'),
    254                                  'assets', 'mp3-short.json')
    255         rtapp = RTA(self.target, name='test', calibration=calibration)
    256 
    257         # Configure this RTApp instance to:
    258         rtapp.conf(kind='custom', params=json_path, duration=5,
    259                    run_dir=self.target_run_dir)
    260 
    261         with open(rtapp.json) as f:
    262             conf = json.load(f)
    263 
    264         # Convert k to str because the json loader gives us unicode strings
    265         tasks = set([str(k) for k in conf['tasks'].keys()])
    266         self.assertSetEqual(
    267             tasks,
    268             set(['AudioTick', 'AudioOut', 'AudioTrack',
    269                  'mp3.decoder', 'OMXCall']))
    270 
    271         # Would like to try running the workload but mp3-short.json has nonzero
    272         # 'priority' fields, and we probably don't have permission for that
    273         # unless we're root.
    274         if self.target.is_rooted:
    275             rtapp.run(out_dir=self.host_out_dir)
    276 
    277             rtapp_cmds = [c for c in self.target.executed_commands
    278                           if 'rt-app' in c]
    279             self.assertListEqual(rtapp_cmds, [self.get_expected_command(rtapp)])
    280 
    281             self.assert_output_file_exists('output.log')
    282             self.assert_output_file_exists('test_00.json')
    283 
    284     def test_custom_smoke_calib(self):
    285         """Test RTA custom workload (providing calibration)"""
    286         self._test_custom_smoke(self.calibration)
    287 
    288     def test_custom_smoke_no_calib(self):
    289         """Test RTA custom workload (providing no calibration)"""
    290         self._test_custom_smoke(None)
    291 
    292 
    293 DummyBlModule = namedtuple('bl', ['bigs'])
    294 
    295 class TestRTACalibrationConf(RTABase):
    296     """Test setting the "calibration" field of rt-app config"""
    297     def _get_calib_conf(self, calibration):
    298         rtapp = RTA(self.target, name='test', calibration=calibration)
    299 
    300         rtapp.conf(
    301             kind = 'profile',
    302             params = {'t1': Periodic().get()},
    303             run_dir=self.target_run_dir
    304         )
    305 
    306         with open(rtapp.json) as f:
    307             return json.load(f)['global']['calibration']
    308 
    309     def test_calibration_conf_pload(self):
    310         """Test that the smallest pload value is used, if provided"""
    311         cpus = range(self.target.number_of_cpus)
    312         conf = self._get_calib_conf(dict(zip(cpus, [c + 100 for c in cpus])))
    313         self.assertEqual(conf, 100,
    314                          'Calibration not set to minimum pload value')
    315 
    316     def test_calibration_conf_bl(self):
    317         """Test that a big CPU is used if big.LITTLE data is available"""
    318         self.target.modules.append('bl')
    319         self.target.bl = DummyBlModule([1, 2])
    320         conf = self._get_calib_conf(None)
    321         self.assertIn(conf, ['CPU{}'.format(c) for c in self.target.bl.bigs],
    322                       'Calibration not set to use a big CPU')
    323 
    324     def test_calibration_conf_nodata(self):
    325         """Test that the last CPU is used if no data is available"""
    326         conf = self._get_calib_conf(None)
    327         cpu = self.target.number_of_cpus - 1
    328         self.assertEqual(conf, 'CPU{}'.format(cpu),
    329                          'Calibration not set to highest numbered CPU')
    330