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 datetime 19import json 20import logging 21import os 22import re 23import shutil 24import sys 25import time 26import unittest 27 28import devlib 29from devlib.utils.misc import memoized 30from devlib import Platform, TargetError 31from trappy.stats.Topology import Topology 32 33from wlgen import RTA 34from energy import EnergyMeter 35from energy_model import EnergyModel 36from conf import JsonConf 37from platforms.juno_energy import juno_energy 38from platforms.hikey_energy import hikey_energy 39from platforms.pixel_energy import pixel_energy 40 41USERNAME_DEFAULT = 'root' 42PASSWORD_DEFAULT = '' 43WORKING_DIR_DEFAULT = '/data/local/schedtest' 44FTRACE_EVENTS_DEFAULT = ['sched:*'] 45FTRACE_BUFSIZE_DEFAULT = 10240 46OUT_PREFIX = 'results' 47LATEST_LINK = 'results_latest' 48 49basepath = os.path.dirname(os.path.realpath(__file__)) 50basepath = basepath.replace('/libs/utils', '') 51 52def os_which(file): 53 for path in os.environ["PATH"].split(os.pathsep): 54 if os.path.exists(os.path.join(path, file)): 55 return os.path.join(path, file) 56 57 return None 58 59class ShareState(object): 60 __shared_state = {} 61 62 def __init__(self): 63 self.__dict__ = self.__shared_state 64 65class TestEnv(ShareState): 66 """ 67 Represents the environment configuring LISA, the target, and the test setup 68 69 The test environment is defined by: 70 71 - a target configuration (target_conf) defining which HW platform we 72 want to use to run the experiments 73 - a test configuration (test_conf) defining which SW setups we need on 74 that HW target 75 - a folder to collect the experiments results, which can be specified 76 using the test_conf::results_dir option and is by default wiped from 77 all the previous contents (if wipe=True) 78 79 :param target_conf: 80 Configuration defining the target to run experiments on. May be 81 82 - A dict defining the values directly 83 - A path to a JSON file containing the configuration 84 - ``None``, in which case $LISA_HOME/target.config is used. 85 86 You need to provide the information needed to connect to the 87 target. For SSH targets that means "host", "username" and 88 either "password" or "keyfile". All other fields are optional if 89 the relevant features aren't needed. Has the following keys: 90 91 **host** 92 Target IP or MAC address for SSH access 93 **username** 94 For SSH access 95 **keyfile** 96 Path to SSH key (alternative to password) 97 **password** 98 SSH password (alternative to keyfile) 99 **device** 100 Target Android device ID if using ADB 101 **port** 102 Port for Android connection default port is 5555 103 **ANDROID_HOME** 104 Path to Android SDK. Defaults to ``$ANDROID_HOME`` from the 105 environment. 106 **rtapp-calib** 107 Calibration values for RT-App. If unspecified, LISA will 108 calibrate RT-App on the target. A message will be logged with 109 a value that can be copied here to avoid having to re-run 110 calibration on subsequent tests. 111 **tftp** 112 Directory path containing kernels and DTB images for the 113 target. LISA does *not* manage this TFTP server, it must be 114 provided externally. Optional. 115 116 :param test_conf: Configuration of software for target experiments. Takes 117 the same form as target_conf. Fields are: 118 119 **modules** 120 Devlib modules to be enabled. Default is [] 121 **exclude_modules** 122 Devlib modules to be disabled. Default is []. 123 **tools** 124 List of tools (available under ./tools/$ARCH/) to install on 125 the target. Names, not paths (e.g. ['ftrace']). Default is []. 126 **ping_time**, **reboot_time** 127 Override parameters to :meth:`reboot` method 128 **__features__** 129 List of test environment features to enable. Options are: 130 131 "no-kernel" 132 do not deploy kernel/dtb images 133 "no-reboot" 134 do not force reboot the target at each configuration change 135 "debug" 136 enable debugging messages 137 138 **ftrace** 139 Configuration for ftrace. Dictionary with keys: 140 141 events 142 events to enable. 143 functions 144 functions to enable in the function tracer. Optional. 145 buffsize 146 Size of buffer. Default is 10240. 147 148 **systrace** 149 Configuration for systrace. Dictionary with keys: 150 categories: 151 overide the list of categories enabled 152 extra_categories: 153 append to the default list of categories 154 extra_events: 155 additional ftrace events to manually enable during systrac'ing 156 buffsize: 157 Size of ftrace buffer that systrace uses 158 159 **results_dir** 160 location of results of the experiments 161 162 :param wipe: set true to cleanup all previous content from the output 163 folder 164 :type wipe: bool 165 166 :param force_new: Create a new TestEnv object even if there is one available 167 for this session. By default, TestEnv only creates one 168 object per session, use this to override this behaviour. 169 :type force_new: bool 170 """ 171 172 _initialized = False 173 174 def __init__(self, target_conf=None, test_conf=None, wipe=True, 175 force_new=False): 176 super(TestEnv, self).__init__() 177 178 if self._initialized and not force_new: 179 return 180 181 self.conf = {} 182 self.test_conf = {} 183 self.target = None 184 self.ftrace = None 185 self.workdir = WORKING_DIR_DEFAULT 186 self.__installed_tools = set() 187 self.__modules = [] 188 self.__connection_settings = None 189 self._calib = None 190 191 # Keep track of target IP and MAC address 192 self.ip = None 193 self.mac = None 194 195 # Keep track of last installed kernel 196 self.kernel = None 197 self.dtb = None 198 199 # Energy meter configuration 200 self.emeter = None 201 202 # The platform descriptor to be saved into the results folder 203 self.platform = {} 204 205 # Keep track of android support 206 self.LISA_HOME = os.environ.get('LISA_HOME', '/vagrant') 207 self.ANDROID_HOME = os.environ.get('ANDROID_HOME', None) 208 self.CATAPULT_HOME = os.environ.get('CATAPULT_HOME', 209 os.path.join(self.LISA_HOME, 'tools', 'catapult')) 210 211 # Setup logging 212 self._log = logging.getLogger('TestEnv') 213 214 # Compute base installation path 215 self._log.info('Using base path: %s', basepath) 216 217 # Setup target configuration 218 if isinstance(target_conf, dict): 219 self._log.info('Loading custom (inline) target configuration') 220 self.conf = target_conf 221 elif isinstance(target_conf, str): 222 self._log.info('Loading custom (file) target configuration') 223 self.conf = self.loadTargetConfig(target_conf) 224 elif target_conf is None: 225 self._log.info('Loading default (file) target configuration') 226 self.conf = self.loadTargetConfig() 227 self._log.debug('Target configuration %s', self.conf) 228 229 # Setup test configuration 230 if test_conf: 231 if isinstance(test_conf, dict): 232 self._log.info('Loading custom (inline) test configuration') 233 self.test_conf = test_conf 234 elif isinstance(test_conf, str): 235 self._log.info('Loading custom (file) test configuration') 236 self.test_conf = self.loadTargetConfig(test_conf) 237 else: 238 raise ValueError('test_conf must be either a dictionary or a filepath') 239 self._log.debug('Test configuration %s', self.conf) 240 241 # Setup target working directory 242 if 'workdir' in self.conf: 243 self.workdir = self.conf['workdir'] 244 245 # Initialize binary tools to deploy 246 test_conf_tools = self.test_conf.get('tools', []) 247 target_conf_tools = self.conf.get('tools', []) 248 self.__tools = list(set(test_conf_tools + target_conf_tools)) 249 250 # Initialize ftrace events 251 # test configuration override target one 252 if 'ftrace' in self.test_conf: 253 self.conf['ftrace'] = self.test_conf['ftrace'] 254 if self.conf.get('ftrace'): 255 self.__tools.append('trace-cmd') 256 257 # Initialize features 258 if '__features__' not in self.conf: 259 self.conf['__features__'] = [] 260 261 self._init() 262 263 # Initialize FTrace events collection 264 self._init_ftrace(True) 265 266 # Initialize RT-App calibration values 267 self.calibration() 268 269 # Initialize local results folder 270 # test configuration overrides target one 271 self.res_dir = (self.test_conf.get('results_dir') or 272 self.conf.get('results_dir')) 273 274 if self.res_dir and not os.path.isabs(self.res_dir): 275 self.res_dir = os.path.join(basepath, 'results', self.res_dir) 276 else: 277 self.res_dir = os.path.join(basepath, OUT_PREFIX) 278 self.res_dir = datetime.datetime.now()\ 279 .strftime(self.res_dir + '/%Y%m%d_%H%M%S') 280 281 if wipe and os.path.exists(self.res_dir): 282 self._log.warning('Wipe previous contents of the results folder:') 283 self._log.warning(' %s', self.res_dir) 284 shutil.rmtree(self.res_dir, ignore_errors=True) 285 if not os.path.exists(self.res_dir): 286 os.makedirs(self.res_dir) 287 288 res_lnk = os.path.join(basepath, LATEST_LINK) 289 if os.path.islink(res_lnk): 290 os.remove(res_lnk) 291 os.symlink(self.res_dir, res_lnk) 292 293 # Initialize energy probe instrument 294 self._init_energy(True) 295 296 self._log.info('Set results folder to:') 297 self._log.info(' %s', self.res_dir) 298 self._log.info('Experiment results available also in:') 299 self._log.info(' %s', res_lnk) 300 301 self._initialized = True 302 303 def loadTargetConfig(self, filepath='target.config'): 304 """ 305 Load the target configuration from the specified file. 306 307 :param filepath: Path of the target configuration file. Relative to the 308 root folder of the test suite. 309 :type filepath: str 310 311 """ 312 313 # Loading default target configuration 314 conf_file = os.path.join(basepath, filepath) 315 316 self._log.info('Loading target configuration [%s]...', conf_file) 317 conf = JsonConf(conf_file) 318 conf.load() 319 return conf.json 320 321 def _init(self, force = False): 322 323 # Initialize target 324 self._init_target(force) 325 326 # Initialize target Topology for behavior analysis 327 CLUSTERS = [] 328 329 # Build topology for a big.LITTLE systems 330 if self.target.big_core and \ 331 (self.target.abi == 'arm64' or self.target.abi == 'armeabi'): 332 # Populate cluster for a big.LITTLE platform 333 if self.target.big_core: 334 # Load cluster of LITTLE cores 335 CLUSTERS.append( 336 [i for i,t in enumerate(self.target.core_names) 337 if t == self.target.little_core]) 338 # Load cluster of big cores 339 CLUSTERS.append( 340 [i for i,t in enumerate(self.target.core_names) 341 if t == self.target.big_core]) 342 # Build topology for an SMP systems 343 elif not self.target.big_core or \ 344 self.target.abi == 'x86_64': 345 for c in set(self.target.core_clusters): 346 CLUSTERS.append( 347 [i for i,v in enumerate(self.target.core_clusters) 348 if v == c]) 349 self.topology = Topology(clusters=CLUSTERS) 350 self._log.info('Topology:') 351 self._log.info(' %s', CLUSTERS) 352 353 # Initialize the platform descriptor 354 self._init_platform() 355 356 357 def _init_target(self, force = False): 358 359 if not force and self.target is not None: 360 return self.target 361 362 self.__connection_settings = {} 363 364 # Configure username 365 if 'username' in self.conf: 366 self.__connection_settings['username'] = self.conf['username'] 367 else: 368 self.__connection_settings['username'] = USERNAME_DEFAULT 369 370 # Configure password or SSH keyfile 371 if 'keyfile' in self.conf: 372 self.__connection_settings['keyfile'] = self.conf['keyfile'] 373 elif 'password' in self.conf: 374 self.__connection_settings['password'] = self.conf['password'] 375 else: 376 self.__connection_settings['password'] = PASSWORD_DEFAULT 377 378 # Configure port 379 if 'port' in self.conf: 380 self.__connection_settings['port'] = self.conf['port'] 381 382 # Configure the host IP/MAC address 383 if 'host' in self.conf: 384 try: 385 if ':' in self.conf['host']: 386 (self.mac, self.ip) = self.resolv_host(self.conf['host']) 387 else: 388 self.ip = self.conf['host'] 389 self.__connection_settings['host'] = self.ip 390 except KeyError: 391 raise ValueError('Config error: missing [host] parameter') 392 393 try: 394 platform_type = self.conf['platform'] 395 except KeyError: 396 raise ValueError('Config error: missing [platform] parameter') 397 398 if platform_type.lower() == 'android': 399 self.ANDROID_HOME = self.conf.get('ANDROID_HOME', 400 self.ANDROID_HOME) 401 if self.ANDROID_HOME: 402 self._adb = os.path.join(self.ANDROID_HOME, 403 'platform-tools', 'adb') 404 self._fastboot = os.path.join(self.ANDROID_HOME, 405 'platform-tools', 'fastboot') 406 os.environ['ANDROID_HOME'] = self.ANDROID_HOME 407 os.environ['CATAPULT_HOME'] = self.CATAPULT_HOME 408 else: 409 self._log.info('Android SDK not found as ANDROID_HOME not defined, using PATH for platform tools') 410 self._adb = os_which('adb') 411 self._fastboot = os_which('fastboot') 412 if self._adb: 413 self._log.info('Using adb from ' + self._adb) 414 if self._fastboot: 415 self._log.info('Using fastboot from ' + self._fastboot) 416 417 self._log.info('External tools using:') 418 self._log.info(' ANDROID_HOME: %s', self.ANDROID_HOME) 419 self._log.info(' CATAPULT_HOME: %s', self.CATAPULT_HOME) 420 421 if not os.path.exists(self._adb): 422 raise RuntimeError('\nADB binary not found\n\t{}\ndoes not exists!\n\n' 423 'Please configure ANDROID_HOME to point to ' 424 'a valid Android SDK installation folder.'\ 425 .format(self._adb)) 426 427 ######################################################################## 428 # Board configuration 429 ######################################################################## 430 431 # Setup board default if not specified by configuration 432 self.nrg_model = None 433 platform = None 434 self.__modules = [] 435 if 'board' not in self.conf: 436 self.conf['board'] = 'UNKNOWN' 437 438 # Initialize TC2 board 439 if self.conf['board'].upper() == 'TC2': 440 platform = devlib.platform.arm.TC2() 441 self.__modules = ['bl', 'hwmon', 'cpufreq'] 442 443 # Initialize JUNO board 444 elif self.conf['board'].upper() in ('JUNO', 'JUNO2'): 445 platform = devlib.platform.arm.Juno() 446 self.nrg_model = juno_energy 447 self.__modules = ['bl', 'hwmon', 'cpufreq'] 448 449 # Initialize OAK board 450 elif self.conf['board'].upper() == 'OAK': 451 platform = Platform(model='MT8173') 452 self.__modules = ['bl', 'cpufreq'] 453 454 # Initialized HiKey board 455 elif self.conf['board'].upper() == 'HIKEY': 456 self.nrg_model = hikey_energy 457 self.__modules = [ "cpufreq", "cpuidle" ] 458 platform = Platform(model='hikey') 459 460 # Initialize Pixel phone 461 elif self.conf['board'].upper() == 'PIXEL': 462 self.nrg_model = pixel_energy 463 self.__modules = ['bl', 'cpufreq'] 464 platform = Platform(model='pixel') 465 466 elif self.conf['board'] != 'UNKNOWN': 467 # Initilize from platform descriptor (if available) 468 board = self._load_board(self.conf['board']) 469 if board: 470 core_names=board['cores'] 471 platform = Platform( 472 model=self.conf['board'], 473 core_names=core_names, 474 core_clusters = self._get_clusters(core_names), 475 big_core=board.get('big_core', None) 476 ) 477 self.__modules=board.get('modules', []) 478 479 ######################################################################## 480 # Modules configuration 481 ######################################################################## 482 483 modules = set(self.__modules) 484 485 # Refine modules list based on target.conf 486 modules.update(self.conf.get('modules', [])) 487 # Merge tests specific modules 488 modules.update(self.test_conf.get('modules', [])) 489 490 remove_modules = set(self.conf.get('exclude_modules', []) + 491 self.test_conf.get('exclude_modules', [])) 492 modules.difference_update(remove_modules) 493 494 self.__modules = list(modules) 495 self._log.info('Devlib modules to load: %s', self.__modules) 496 497 ######################################################################## 498 # Devlib target setup (based on target.config::platform) 499 ######################################################################## 500 501 # If the target is Android, we need just (eventually) the device 502 if platform_type.lower() == 'android': 503 self.__connection_settings = None 504 device = 'DEFAULT' 505 if 'device' in self.conf: 506 device = self.conf['device'] 507 self.__connection_settings = {'device' : device} 508 elif 'host' in self.conf: 509 host = self.conf['host'] 510 port = '5555' 511 if 'port' in self.conf: 512 port = str(self.conf['port']) 513 device = '{}:{}'.format(host, port) 514 self.__connection_settings = {'device' : device} 515 self._log.info('Connecting Android target [%s]', device) 516 else: 517 self._log.info('Connecting %s target:', platform_type) 518 for key in self.__connection_settings: 519 self._log.info('%10s : %s', key, 520 self.__connection_settings[key]) 521 522 self._log.info('Connection settings:') 523 self._log.info(' %s', self.__connection_settings) 524 525 if platform_type.lower() == 'linux': 526 self._log.debug('Setup LINUX target...') 527 if "host" not in self.__connection_settings: 528 raise ValueError('Missing "host" param in Linux target conf') 529 530 self.target = devlib.LinuxTarget( 531 platform = platform, 532 connection_settings = self.__connection_settings, 533 load_default_modules = False, 534 modules = self.__modules) 535 elif platform_type.lower() == 'android': 536 self._log.debug('Setup ANDROID target...') 537 self.target = devlib.AndroidTarget( 538 platform = platform, 539 connection_settings = self.__connection_settings, 540 load_default_modules = False, 541 modules = self.__modules) 542 elif platform_type.lower() == 'host': 543 self._log.debug('Setup HOST target...') 544 self.target = devlib.LocalLinuxTarget( 545 platform = platform, 546 load_default_modules = False, 547 modules = self.__modules) 548 else: 549 raise ValueError('Config error: not supported [platform] type {}'\ 550 .format(platform_type)) 551 552 self._log.debug('Checking target connection...') 553 self._log.debug('Target info:') 554 self._log.debug(' ABI: %s', self.target.abi) 555 self._log.debug(' CPUs: %s', self.target.cpuinfo) 556 self._log.debug(' Clusters: %s', self.target.core_clusters) 557 558 self._log.info('Initializing target workdir:') 559 self._log.info(' %s', self.target.working_directory) 560 561 self.target.setup() 562 self.install_tools(self.__tools) 563 564 # Verify that all the required modules have been initialized 565 for module in self.__modules: 566 self._log.debug('Check for module [%s]...', module) 567 if not hasattr(self.target, module): 568 self._log.warning('Unable to initialize [%s] module', module) 569 self._log.error('Fix your target kernel configuration or ' 570 'disable module from configuration') 571 raise RuntimeError('Failed to initialized [{}] module, ' 572 'update your kernel or test configurations'.format(module)) 573 574 if not self.nrg_model: 575 try: 576 self._log.info('Attempting to read energy model from target') 577 self.nrg_model = EnergyModel.from_target(self.target) 578 except (TargetError, RuntimeError, ValueError) as e: 579 self._log.error("Couldn't read target energy model: %s", e) 580 581 def install_tools(self, tools): 582 """ 583 Install tools additional to those specified in the test config 'tools' 584 field 585 586 :param tools: The list of names of tools to install 587 :type tools: list(str) 588 """ 589 tools = set(tools) 590 591 # Add tools dependencies 592 if 'rt-app' in tools: 593 tools.update(['taskset', 'trace-cmd', 'perf', 'cgroup_run_into.sh']) 594 595 # Remove duplicates and already-instaled tools 596 tools.difference_update(self.__installed_tools) 597 598 tools_to_install = [] 599 for tool in tools: 600 binary = '{}/tools/scripts/{}'.format(basepath, tool) 601 if not os.path.isfile(binary): 602 binary = '{}/tools/{}/{}'\ 603 .format(basepath, self.target.abi, tool) 604 tools_to_install.append(binary) 605 606 for tool_to_install in tools_to_install: 607 self.target.install(tool_to_install) 608 609 self.__installed_tools.update(tools) 610 611 def ftrace_conf(self, conf): 612 self._init_ftrace(True, conf) 613 614 def _init_ftrace(self, force=False, conf=None): 615 616 if not force and self.ftrace is not None: 617 return self.ftrace 618 619 if conf is None and 'ftrace' not in self.conf: 620 return None 621 622 if conf is not None: 623 ftrace = conf 624 else: 625 ftrace = self.conf['ftrace'] 626 627 events = FTRACE_EVENTS_DEFAULT 628 if 'events' in ftrace: 629 events = ftrace['events'] 630 631 functions = None 632 if 'functions' in ftrace: 633 functions = ftrace['functions'] 634 635 buffsize = FTRACE_BUFSIZE_DEFAULT 636 if 'buffsize' in ftrace: 637 buffsize = ftrace['buffsize'] 638 639 self.ftrace = devlib.FtraceCollector( 640 self.target, 641 events = events, 642 functions = functions, 643 buffer_size = buffsize, 644 autoreport = False, 645 autoview = False 646 ) 647 648 if events: 649 self._log.info('Enabled tracepoints:') 650 for event in events: 651 self._log.info(' %s', event) 652 if functions: 653 self._log.info('Kernel functions profiled:') 654 for function in functions: 655 self._log.info(' %s', function) 656 657 return self.ftrace 658 659 def _init_energy(self, force): 660 661 # Initialize energy probe to board default 662 self.emeter = EnergyMeter.getInstance(self.target, self.conf, force, 663 self.res_dir) 664 665 def _init_platform_bl(self): 666 self.platform = { 667 'clusters' : { 668 'little' : self.target.bl.littles, 669 'big' : self.target.bl.bigs 670 }, 671 'freqs' : { 672 'little' : self.target.bl.list_littles_frequencies(), 673 'big' : self.target.bl.list_bigs_frequencies() 674 } 675 } 676 self.platform['cpus_count'] = \ 677 len(self.platform['clusters']['little']) + \ 678 len(self.platform['clusters']['big']) 679 680 def _init_platform_smp(self): 681 self.platform = { 682 'clusters' : {}, 683 'freqs' : {} 684 } 685 for cpu_id,node_id in enumerate(self.target.core_clusters): 686 if node_id not in self.platform['clusters']: 687 self.platform['clusters'][node_id] = [] 688 self.platform['clusters'][node_id].append(cpu_id) 689 690 if 'cpufreq' in self.target.modules: 691 # Try loading frequencies using the cpufreq module 692 for cluster_id in self.platform['clusters']: 693 core_id = self.platform['clusters'][cluster_id][0] 694 self.platform['freqs'][cluster_id] = \ 695 self.target.cpufreq.list_frequencies(core_id) 696 else: 697 self._log.warning('Unable to identify cluster frequencies') 698 699 # TODO: get the performance boundaries in case of intel_pstate driver 700 701 self.platform['cpus_count'] = len(self.target.core_clusters) 702 703 def _load_em(self, board): 704 em_path = os.path.join(basepath, 705 'libs/utils/platforms', board.lower() + '.json') 706 self._log.debug('Trying to load default EM from %s', em_path) 707 if not os.path.exists(em_path): 708 return None 709 self._log.info('Loading default EM:') 710 self._log.info(' %s', em_path) 711 board = JsonConf(em_path) 712 board.load() 713 if 'nrg_model' not in board.json: 714 return None 715 return board.json['nrg_model'] 716 717 def _load_board(self, board): 718 board_path = os.path.join(basepath, 719 'libs/utils/platforms', board.lower() + '.json') 720 self._log.debug('Trying to load board descriptor from %s', board_path) 721 if not os.path.exists(board_path): 722 return None 723 self._log.info('Loading board:') 724 self._log.info(' %s', board_path) 725 board = JsonConf(board_path) 726 board.load() 727 if 'board' not in board.json: 728 return None 729 return board.json['board'] 730 731 def _get_clusters(self, core_names): 732 idx = 0 733 clusters = [] 734 ids_map = { core_names[0] : 0 } 735 for name in core_names: 736 idx = ids_map.get(name, idx+1) 737 ids_map[name] = idx 738 clusters.append(idx) 739 return clusters 740 741 def _init_platform(self): 742 if 'bl' in self.target.modules: 743 self._init_platform_bl() 744 else: 745 self._init_platform_smp() 746 747 # Adding energy model information 748 if 'nrg_model' in self.conf: 749 self.platform['nrg_model'] = self.conf['nrg_model'] 750 # Try to load the default energy model (if available) 751 else: 752 self.platform['nrg_model'] = self._load_em(self.conf['board']) 753 754 # Adding topology information 755 self.platform['topology'] = self.topology.get_level("cluster") 756 757 # Adding kernel build information 758 kver = self.target.kernel_version 759 self.platform['kernel'] = {t: getattr(kver, t, None) 760 for t in [ 761 'release', 'version', 762 'version_number', 'major', 'minor', 763 'rc', 'sha1', 'parts' 764 ] 765 } 766 self.platform['abi'] = self.target.abi 767 self.platform['os'] = self.target.os 768 769 self._log.debug('Platform descriptor initialized\n%s', self.platform) 770 # self.platform_dump('./') 771 772 def platform_dump(self, dest_dir, dest_file='platform.json'): 773 plt_file = os.path.join(dest_dir, dest_file) 774 self._log.debug('Dump platform descriptor in [%s]', plt_file) 775 with open(plt_file, 'w') as ofile: 776 json.dump(self.platform, ofile, sort_keys=True, indent=4) 777 return (self.platform, plt_file) 778 779 def calibration(self, force=False): 780 """ 781 Get rt-app calibration. Run calibration on target if necessary. 782 783 :param force: Always run calibration on target, even if we have not 784 installed rt-app or have already run calibration. 785 :returns: A dict with calibration results, which can be passed as the 786 ``calibration`` parameter to :class:`RTA`, or ``None`` if 787 force=False and we have not installed rt-app. 788 """ 789 790 if not force and self._calib: 791 return self._calib 792 793 required = force or 'rt-app' in self.__installed_tools 794 795 if not required: 796 self._log.debug('No RT-App workloads, skipping calibration') 797 return 798 799 if not force and 'rtapp-calib' in self.conf: 800 self._log.warning('Using configuration provided RTApp calibration') 801 self._calib = { 802 int(key): int(value) 803 for key, value in self.conf['rtapp-calib'].items() 804 } 805 else: 806 self._log.info('Calibrating RTApp...') 807 self._calib = RTA.calibrate(self.target) 808 809 self._log.info('Using RT-App calibration values:') 810 self._log.info(' %s', 811 "{" + ", ".join('"%r": %r' % (key, self._calib[key]) 812 for key in sorted(self._calib)) + "}") 813 return self._calib 814 815 def resolv_host(self, host=None): 816 """ 817 Resolve a host name or IP address to a MAC address 818 819 .. TODO Is my networking terminology correct here? 820 821 :param host: IP address or host name to resolve. If None, use 'host' 822 value from target_config. 823 :type host: str 824 """ 825 if host is None: 826 host = self.conf['host'] 827 828 # Refresh ARP for local network IPs 829 self._log.debug('Collecting all Bcast address') 830 output = os.popen(r'ifconfig').read().split('\n') 831 for line in output: 832 match = IFCFG_BCAST_RE.search(line) 833 if not match: 834 continue 835 baddr = match.group(1) 836 try: 837 cmd = r'nmap -T4 -sP {}/24 &>/dev/null'.format(baddr.strip()) 838 self._log.debug(cmd) 839 os.popen(cmd) 840 except RuntimeError: 841 self._log.warning('Nmap not available, try IP lookup using broadcast ping') 842 cmd = r'ping -b -c1 {} &>/dev/null'.format(baddr) 843 self._log.debug(cmd) 844 os.popen(cmd) 845 846 return self.parse_arp_cache(host) 847 848 def parse_arp_cache(self, host): 849 output = os.popen(r'arp -n') 850 if ':' in host: 851 # Assuming this is a MAC address 852 # TODO add a suitable check on MAC address format 853 # Query ARP for the specified HW address 854 ARP_RE = re.compile( 855 r'([^ ]*).*({}|{})'.format(host.lower(), host.upper()) 856 ) 857 macaddr = host 858 ipaddr = None 859 for line in output: 860 match = ARP_RE.search(line) 861 if not match: 862 continue 863 ipaddr = match.group(1) 864 break 865 else: 866 # Assuming this is an IP address 867 # TODO add a suitable check on IP address format 868 # Query ARP for the specified IP address 869 ARP_RE = re.compile( 870 r'{}.*ether *([0-9a-fA-F:]*)'.format(host) 871 ) 872 macaddr = None 873 ipaddr = host 874 for line in output: 875 match = ARP_RE.search(line) 876 if not match: 877 continue 878 macaddr = match.group(1) 879 break 880 else: 881 # When target is accessed via WiFi, there is not MAC address 882 # reported by arp. In these cases we can know only the IP 883 # of the remote target. 884 macaddr = 'UNKNOWN' 885 886 if not ipaddr or not macaddr: 887 raise ValueError('Unable to lookup for target IP/MAC address') 888 self._log.info('Target (%s) at IP address: %s', macaddr, ipaddr) 889 return (macaddr, ipaddr) 890 891 def reboot(self, reboot_time=120, ping_time=15): 892 """ 893 Reboot target. 894 895 :param boot_time: Time to wait for the target to become available after 896 reboot before declaring failure. 897 :param ping_time: Period between attempts to ping the target while 898 waiting for reboot. 899 """ 900 # Send remote target a reboot command 901 if self._feature('no-reboot'): 902 self._log.warning('Reboot disabled by conf features') 903 else: 904 if 'reboot_time' in self.conf: 905 reboot_time = int(self.conf['reboot_time']) 906 907 if 'ping_time' in self.conf: 908 ping_time = int(self.conf['ping_time']) 909 910 # Before rebooting make sure to have IP and MAC addresses 911 # of the target 912 (self.mac, self.ip) = self.parse_arp_cache(self.ip) 913 914 self.target.execute('sleep 2 && reboot -f &', as_root=True) 915 916 # Wait for the target to complete the reboot 917 self._log.info('Waiting up to %s[s] for target [%s] to reboot...', 918 reboot_time, self.ip) 919 920 ping_cmd = "ping -c 1 {} >/dev/null".format(self.ip) 921 elapsed = 0 922 start = time.time() 923 while elapsed <= reboot_time: 924 time.sleep(ping_time) 925 self._log.debug('Trying to connect to [%s] target...', self.ip) 926 if os.system(ping_cmd) == 0: 927 break 928 elapsed = time.time() - start 929 if elapsed > reboot_time: 930 if self.mac: 931 self._log.warning('target [%s] not responding to PINGs, ' 932 'trying to resolve MAC address...', 933 self.ip) 934 (self.mac, self.ip) = self.resolv_host(self.mac) 935 else: 936 self._log.warning('target [%s] not responding to PINGs, ' 937 'trying to continue...', 938 self.ip) 939 940 # Force re-initialization of all the devlib modules 941 force = True 942 943 # Reset the connection to the target 944 self._init(force) 945 946 # Initialize FTrace events collection 947 self._init_ftrace(force) 948 949 # Initialize energy probe instrument 950 self._init_energy(force) 951 952 def install_kernel(self, tc, reboot=False): 953 """ 954 Deploy kernel and DTB via TFTP, optionally rebooting 955 956 :param tc: Dicionary containing optional keys 'kernel' and 'dtb'. Values 957 are paths to the binaries to deploy. 958 :type tc: dict 959 960 :param reboot: Reboot thet target after deployment 961 :type reboot: bool 962 """ 963 964 # Default initialize the kernel/dtb settings 965 tc.setdefault('kernel', None) 966 tc.setdefault('dtb', None) 967 968 if self.kernel == tc['kernel'] and self.dtb == tc['dtb']: 969 return 970 971 self._log.info('Install kernel [%s] on target...', tc['kernel']) 972 973 # Install kernel/dtb via FTFP 974 if self._feature('no-kernel'): 975 self._log.warning('Kernel deploy disabled by conf features') 976 977 elif 'tftp' in self.conf: 978 self._log.info('Deploy kernel via TFTP...') 979 980 # Deploy kernel in TFTP folder (mandatory) 981 if 'kernel' not in tc or not tc['kernel']: 982 raise ValueError('Missing "kernel" parameter in conf: %s', 983 'KernelSetup', tc) 984 self.tftp_deploy(tc['kernel']) 985 986 # Deploy DTB in TFTP folder (if provided) 987 if 'dtb' not in tc or not tc['dtb']: 988 self._log.debug('DTB not provided, using existing one') 989 self._log.debug('Current conf:\n%s', tc) 990 self._log.warning('Using pre-installed DTB') 991 else: 992 self.tftp_deploy(tc['dtb']) 993 994 else: 995 raise ValueError('Kernel installation method not supported') 996 997 # Keep track of last installed kernel 998 self.kernel = tc['kernel'] 999 if 'dtb' in tc: 1000 self.dtb = tc['dtb'] 1001 1002 if not reboot: 1003 return 1004 1005 # Reboot target 1006 self._log.info('Rebooting taget...') 1007 self.reboot() 1008 1009 1010 def tftp_deploy(self, src): 1011 """ 1012 .. TODO 1013 """ 1014 1015 tftp = self.conf['tftp'] 1016 1017 dst = tftp['folder'] 1018 if 'kernel' in src: 1019 dst = os.path.join(dst, tftp['kernel']) 1020 elif 'dtb' in src: 1021 dst = os.path.join(dst, tftp['dtb']) 1022 else: 1023 dst = os.path.join(dst, os.path.basename(src)) 1024 1025 cmd = 'cp {} {} && sync'.format(src, dst) 1026 self._log.info('Deploy %s into %s', src, dst) 1027 result = os.system(cmd) 1028 if result != 0: 1029 self._log.error('Failed to deploy image: %s', src) 1030 raise ValueError('copy error') 1031 1032 def _feature(self, feature): 1033 return feature in self.conf['__features__'] 1034 1035IFCFG_BCAST_RE = re.compile( 1036 r'Bcast:(.*) ' 1037) 1038 1039# vim :set tabstop=4 shiftwidth=4 expandtab 1040