LISA介紹及其使用方法


LISA是ARM公司開發的一款開源工具。在內核開發過程中,苦於無法針對修改內容進行一些量化或者可視化結果的測量,而無感。LISA對於模型調優,回歸測試都有較強的支持。

什么是LISA?

LISA是Linux Interactive System Analysis的縮寫,從字面意思可以看出是一個分析工具,具有交互性特點,這有賴於ipython腳本。

LISA是一個Linux環境下用於回歸測試和對於各種workload進行交互測試的工具集。目前LISA主要專注於scheduler、power management和thermal框架的分析。但不僅於此,LISA提供一個框架且可以輕松擴展到其他用途。

LISA提供一些列API用於測試條例編寫和回歸測試條例開發。一系列針對內核核心功能的回歸測試條例已經提供。另外,LISA使用卓越的IPython Notebook框架和一些示例用於進行實驗。

LISA用來干什么?

  1. 有助於學習已有的功能
  2. 有助於開發新的代碼
  3. 有助於發現問題,並且找到原因
  4. 有助於分享可復制的測試:
    1. 足夠的彈性保證在不同待測設備上重復同樣的實驗
    2. 簡化預定義workload的生成和執行
    3. 定義一系列方法來評估內核行為
    4. 更簡單的獲取數據文件來生成統計信息和報表

LISA框架結構

LISA Overall View

待續:devlib/workload/trappy/bart/target什么功能?擴展?

TRAPpy

TRAPpy,即Trace Analysis and Plotting in Python,是一個用於分析數據的可視化工具。它分析類ftrace日志文件,然后基於分析數據創建圖表和數據分析。

TRAPpy需要一些其他工具的支持才能正常工作,比如trace-cmd、kernelshark。trace-cmd用於將trace.dat轉換成trace.txt文件。

TRAPpy安裝

Install additional tools required for some tests and functionalities

sudo apt install trace-cmd kernelshark 

安裝pip等工具:

sudo apt install python-pip python-dev

安裝依賴庫文件:

sudo apt install libfreetype6-dev libpng12-dev python-nose

 

sudo pip install numpy matplotlib pandas ipython[all]

安裝TRAPpy:

sudo pip install --upgrade trappy

TRAPpy使用

啟動一個ipython notebook服務:

ipython notebook

會彈出一個瀏覽器,可以在里面創建,修改,執行腳本。

API文檔:https://pythonhosted.org/TRAPpy/

BART

BART,即Behavioural Analysis and Regression Toolkit,基於TRAPpy,分析kernel輸出的ftrace來診斷當前的行為是否符合預期。

安裝BART

Install additional tools required for some tests and functionalities

$ sudo apt install trace-cmd kernelshark 

Install the Python package manager

$ sudo apt install python-pip python-dev 

Install required python packages

$ sudo apt install libfreetype6-dev libpng12-dev python-nose $ sudo pip install numpy matplotlib pandas ipython[all] $ sudo pip install --upgrade trappy 

ipython[all] will install IPython Notebook, a web based interactive python programming interface. It is required if you plan to use interactive plotting in BART.

Install BART

$ sudo pip install --upgrade bart-py

BART的用途

BART具有廣泛的用途,主要用於幫助開發者進行一些難以測試功能的自動化測試。

內核開發者:確保代碼的正確性

性能優化工程師:圖形化/診斷不同內核版本之間的性能表現。

質量管理、版本工程師:驗證不同模塊/patch集成特性。

API文檔:https://pythonhosted.org/bart-py

devlib

代碼路徑:https://github.com/ARM-software/devlib

devlib提供一個基於Linux操作系統設備,用於交互和獲取測量結果的接口。

wlgen/workload

用於生成各種負荷,目前主要支持rt-app。

LISA使用

准備工作

下載LISA:git clone https://github.com/ARM-software/lisa.git

運行LISA:source init_env,如下:

image

更新LISA依賴模塊

lisa-update

使用LISA進行測試

lisa-test tests/eas/acceptance.py

使用LISA分析

執行lisa-ipython后,會打開瀏覽器。在瀏覽器中可以通過創建IPython腳本進行LISA相關測試。

image

LISA代碼分析

下面是LISA根目錄的二級樹,LISA具有明顯的模塊化區分,基於已有的框架可以輕松編寫測試用例tests,編寫自己想要的測試結果ipynb。

可以看出libs/utils提供LISA基礎框架,
libs/devlib支持和待測設備之間交互連接,
測試用例在tests中,
libs/wlgen產生特定workload,
測試結果在results中,
使用ipynb下各種基本進行分析,
ipynb需要的python庫在libs中。

├── assets
│   └── mp3-short.json
├── init_env source的環境腳本
├── ipynb  用來分析的ipython腳本
│   ├── chromeos
│   ├── energy
│   ├── examples
│   ├── profiling
│   ├── releases
│   ├── sched_dvfs
│   ├── sched_tune
│   ├── scratchpad
│   ├── thermal
│   └── tutorial
├── libs  各類基礎功能和第三方應用
│   ├── bart
│   ├── devlib
│   ├── __init__.py
│   ├── trappy
│   ├── utils
│   └── wlgen
├── LICENSE.txt
├── LisaShell.txt
├── logging.conf
├── README.md
├── results  執行結果
│   └── LisaInANutshell_Backup
├── src  shell配置文件
│   └── shell
├── target.config  待測設備配置文件
├── tests  測試用例編寫腳本,host用到的x86和target用到的arm
│   ├── eas
│   ├── sfreq
│   └── stune
├── tools  測試用到的工作
│   ├── arm64
│   ├── armeabi
│   ├── LICENSE.perf
│   ├── LICENSE.rt-app
│   ├── LICENSE.sysbench
│   ├── LICENSE.taskset
│   ├── LICENSE.trace-cmd
│   ├── plots.py
│   ├── report.py
│   ├── scripts
│   └── x86_64
└── Vagrantfile

下面重點分析libs/utils、libs/wlgen、libs/devlib、libs/trappy、libs/bart,然后如何寫自己的測試用例,並作分析。

libs/utils

class LisaTest是LISA測試用例的基類,調用class TestEnv配置測試環境,調用class Executor生成Executor。

@classmethod
def _runExperiments(cls):
    """
    Default experiments execution engine
    """

    cls.logger.info('Setup tests execution engine...')
    test_env = TestEnv(test_conf=cls._getTestConf())  基於test_conf文件生成TestEnv

    experiments_conf = cls._getExperimentsConf(test_env)
    cls.executor = Executor(test_env, experiments_conf)  基於experiments_conf生成Executor

    # Alias executor objects to make less verbose tests code
    cls.te = cls.executor.te
    cls.target = cls.executor.target

    # Execute pre-experiments code defined by the test
    cls._experimentsInit()  測試前初始化

    cls.logger.info('Experiments execution...')
    cls.executor.run()  測試執行實體

    # Execute post-experiments code defined by the test
    cls._experimentsFinalize()  測試后收尾工作

class TestEnv用於配置LISA執行環境,基於target_conf配置待測設備,基於test_conf配置需要針對測試進行設置,還進行工作目錄、測試工具等設置。

def __init__(self, target_conf=None, test_conf=None, wipe=True,
             force_new=False):

    super(TestEnv, self).__init__()

    # Keep track of android support
    self.LISA_HOME = os.environ.get('LISA_HOME', '/vagrant')
    self.ANDROID_HOME = os.environ.get('ANDROID_HOME', None)
    self.CATAPULT_HOME = os.environ.get('CATAPULT_HOME',
            os.path.join(self.LISA_HOME, 'tools', 'catapult'))

    # Setup logging
    self._log = logging.getLogger('TestEnv')

    # Compute base installation path
    self._log.info('Using base path: %s', basepath)

    # Setup target configuration
    if isinstance(target_conf, dict):
        self._log.info('Loading custom (inline) target configuration')
        self.conf = target_conf
    elif isinstance(target_conf, str):
        self._log.info('Loading custom (file) target configuration')
        self.conf = self.loadTargetConfig(target_conf)
    elif target_conf is None:
        self._log.info('Loading default (file) target configuration')
        self.conf = self.loadTargetConfig()
    self._log.debug('Target configuration %s', self.conf)

    # Setup test configuration
    if test_conf:
        if isinstance(test_conf, dict):
            self._log.info('Loading custom (inline) test configuration')
            self.test_conf = test_conf
        elif isinstance(test_conf, str):
            self._log.info('Loading custom (file) test configuration')
            self.test_conf = self.loadTargetConfig(test_conf)
        else:
            raise ValueError('test_conf must be either a dictionary or a filepath')
        self._log.debug('Test configuration %s', self.conf)

    # Setup target working directory
    if 'workdir' in self.conf:
        self.workdir = self.conf['workdir']

    # Initialize binary tools to deploy
    if 'tools' in self.conf:
        self.__tools = self.conf['tools']
    # Merge tests specific tools
    if self.test_conf and 'tools' in self.test_conf and \
       self.test_conf['tools']:
        if 'tools' not in self.conf:
            self.conf['tools'] = []
        self.__tools = list(set(
            self.conf['tools'] + self.test_conf['tools']
        ))

    # Initialize ftrace events
    # test configuration override target one
    if self.test_conf and 'ftrace' in self.test_conf:
        self.conf['ftrace'] = self.test_conf['ftrace']
    if 'ftrace' in self.conf and self.conf['ftrace']:
        self.__tools.append('trace-cmd')

    # Initialize features
    if '__features__' not in self.conf:
        self.conf['__features__'] = []

    self._init()

    # Initialize FTrace events collection
    self._init_ftrace(True)

    # Initialize RT-App calibration values
    self.calibration()

    # Initialize local results folder
    # test configuration override target one

    res_lnk = os.path.join(basepath, LATEST_LINK)
    if os.path.islink(res_lnk):
        os.remove(res_lnk)
    os.symlink(self.res_dir, res_lnk)

    # Initialize energy probe instrument
    self._init_energy(True)

class Executor是實際生成workload的部分,worload的具體情況在experiments_conf中配置。

def __init__(self, test_env, experiments_conf):
    """
    Tests Executor

    A tests executor is a module which support the execution of a
    configured set of experiments. Each experiment is composed by:
    - a target configuration
    - a worload to execute

    The executor module can be configured to run a set of workloads (wloads)
    in each different target configuration of a specified set (confs). These
    wloads and confs can be specified by the "experiments_conf" input
    dictionary. Each (workload, conf, iteration) tuple is called an
    "experiment".

    All the results generated by each experiment will be collected a result
    folder which is named according to this template:
        results/<test_id>/<wltype>:<conf>:<wload>/<run_id>
    where:
    - <test_id> : the "tid" defined by the experiments_conf, or a timestamp
                  based folder in case "tid" is not specified
    - <wltype>  : the class of workload executed, e.g. rtapp or sched_perf
    - <conf>    : the identifier of one of the specified configurations
    - <wload>   : the identified of one of the specified workload
    - <run_id>  : the progressive execution number from 1 up to the
                  specified iterations

    After the workloads have been run, the Executor object's `experiments`
    attribute is a list of Experiment objects. The `out_dir` attribute of
    these objects can be used to find the results of the experiment.
    """

    # Initialize globals
    self._default_cgroup = None
    self._cgroup = None

    # Setup logging
    self._log = logging.getLogger('Executor')

    # Setup test configuration  解析experiments_conf,這些參數都會傳遞給wlgen執行。

    self._print_section('Experiments configuration')  打印此實驗配置

Executor.run根據Executor.__init__解析的配置,以workload為單位開始執行。

def run(self):
    self._print_section('Experiments execution')

    self.experiments = []

    # Run all the configured experiments
    exp_idx = 0
    for tc in self._experiments_conf['confs']:
        # TARGET: configuration
        if not self._target_configure(tc): 配置待測設備
            continue
        for wl_idx in self._experiments_conf['wloads']:
            # TEST: configuration
            wload, test_dir = self._wload_init(tc, wl_idx)  workload初始化
            for itr_idx in range(1, self._iterations + 1):
                exp = Experiment(
                    wload_name=wl_idx,
                    wload=wload,
                    conf=tc,
                    iteration=itr_idx,
                    out_dir=os.path.join(test_dir, str(itr_idx)))
                self.experiments.append(exp)

                # WORKLOAD: execution
                self._wload_run(exp_idx, exp)  worload執行
                exp_idx += 1
        self._target_cleanup(tc)

    self._print_section('Experiments execution completed')
    self._log.info('Results available in:')
    self._log.info('      %s', self.te.res_dir)

Executor._wload_run執行單個workload:

def _wload_run(self, exp_idx, experiment):
    tc = experiment.conf
    wload = experiment.wload
    tc_idx = tc['tag']

    self._print_title('Experiment {}/{}, [{}:{}] {}/{}'\
            .format(exp_idx, self._exp_count,
                    tc_idx, experiment.wload_name,
                    experiment.iteration, self._iterations))

    # Setup local results folder
    self._log.debug('out_dir set to [%s]', experiment.out_dir)
    os.system('mkdir -p ' + experiment.out_dir)

    # Freeze all userspace tasks that we don't need for running tests
    need_thaw = False
    if self._target_conf_flag(tc, 'freeze_userspace'):
        need_thaw = self._freeze_userspace()

    # FTRACE: start (if a configuration has been provided)
    if self.te.ftrace and self._target_conf_flag(tc, 'ftrace'):
        self._log.warning('FTrace events collection enabled')  准備抓取ftrace
        self.te.ftrace.start()

    # ENERGY: start sampling  抓取Power Meter數據
    if self.te.emeter:
        self.te.emeter.reset()

    # WORKLOAD: Run the configured workload
    wload.run(out_dir=experiment.out_dir, cgroup=self._cgroup)  執行workload

下面是收集Power Meter和ftrace數據。

    # ENERGY: collect measurements
    if self.te.emeter:
        self.te.emeter.report(experiment.out_dir)

    # FTRACE: stop and collect measurements
    if self.te.ftrace and self._target_conf_flag(tc, 'ftrace'):
        self.te.ftrace.stop()

        trace_file = experiment.out_dir + '/trace.dat'
        self.te.ftrace.get_trace(trace_file)
        self._log.info('Collected FTrace binary trace:')
        self._log.info('   %s',
                       trace_file.replace(self.te.res_dir, '<res_dir>'))

        stats_file = experiment.out_dir + '/trace_stat.json'
        self.te.ftrace.get_stats(stats_file)
        self._log.info('Collected FTrace function profiling:')
        self._log.info('   %s',
                       stats_file.replace(self.te.res_dir, '<res_dir>'))

    # Unfreeze the tasks we froze
    if need_thaw:
        self._thaw_userspace()

    self._print_footer()

為了盡量降低測試的干擾,引入了freeze_userspace這個flag,這是基於CGroup的freezer子系統實現的。將必須要保留的進程之外的進程,全部凍結。

critical_tasks = {
    'linux': ['init', 'systemd', 'sh', 'ssh'],
    'android': [
        'sh', 'adbd', 'init',
        'usb', 'transport',
        # We don't actually need this task but on Google Pixel it apparently
        # cannot be frozen, so the cgroup state gets stuck in FREEZING if we
        # try to freeze it.
        'thermal-engine'
    ]
}

在Executor._wload_conf根據wordload配置,調用wlgen生成workload。

def _wload_conf(self, wl_idx, wlspec):

    # CPUS: setup execution on CPUs if required by configuration
    cpus = self._wload_cpus(wl_idx, wlspec)

    # CGroup: setup CGroups if requried by configuration
    self._cgroup = self._default_cgroup
    if 'cgroup' in wlspec:
        if 'cgroups' not in self.target.modules:
            raise RuntimeError('Target not supporting CGroups or CGroups '
                               'not configured for the current test configuration')
        self._cgroup = wlspec['cgroup']

    if wlspec['type'] == 'rt-app':
        return self._wload_rtapp(wl_idx, wlspec, cpus)  rtapp類型的workload
    if wlspec['type'] == 'perf_bench':
        return self._wload_perf_bench(wl_idx, wlspec, cpus)  perf_bench類型的workload


    raise ValueError('unsupported "type" value for [{}] '
                     'workload specification'
                     .format(wl_idx))

platforms下存放的是不同類型主板的配置文件。

analysis目錄下存放的是針對不同關注點(比如,cpus、eas、frequency、idle)等解析trace.txt的腳本,經過這些腳本處理。ipython Notebook可以生成可視化圖表。

class CpusAnalysis

如何擴展?

需要編寫自己設備的配置文件target.conf:

{
    /* Platform */
    /* - linux   : accessed via SSH connection                    */
    /* - android : accessed via ADB connection                    */
    /* - host    : run on the local host                          */
    "platform" : "android", 不同類型的Target,對應不同類型的Connection

    /* Board */
    /* Currently supported boards are:                            */
    /*  juno  : target is a JUNO board                            */
    /*  tc2   : target is a TC2 board                             */
    /* Leave commented if your board is not listed above          */
    "board" : "hikey",  主板類型對應libs/utils/platforms/hikey.json文件。

    /* Target Android device ID */
    "device" : "0123456789abcdef",  adb設備的ID

    /* Login username (has to be sudo enabled) */
    "username" : "root",

    /* Devlib modules to enable/disbale for all the experiments */
    "modules"         : [],
    "exclude_modules" : [],

    /* List of test environment features to enable */
    /*  no-kernel : do not deploy kernel/dtb images               */
    /*  no-reboot : do not force reboot the target at each        */
    /*              configuration change                          */
    /*  debug     : enable debugging messages                     */
    "__features__" : "no-kernel no-reboot"
}

在utils/analysis下,基於class AnalysisModule擴展自己的分析腳本,生成圖表。

libs/wlgen

class Workload作為各種workload的基類,class RTA是class Workload子類。

class Workload的__init__最主要的是進行參數的初始化,使用target.config配置。

run是class Workload的核心,該方法是負荷的執行主體。如果需要抓取ftrace,也會在這里收集。

def run(self,
        ftrace=None,
        cgroup=None,
        cpus=None,
        background=False,
        out_dir='./',
        as_root=False,
        start_pause_s=None,
        end_pause_s=None):

    self.cgroup = cgroup

    # Compose the actual execution command starting from the base command
    # defined by the base class
    _command = self.command

    if not _command:
        self._log.error('Error: empty executor command')

    # Prepend eventually required taskset command
    if cpus or self.cpus:  如果需要設置CPU親和性,使用taskset進行設置。
        cpus_mask = self.getCpusMask(cpus if cpus else self.cpus)
        self.taskset_cmd = '{}/taskset 0x{:X}'\
                .format(self.target.executables_directory,
                        cpus_mask)
        _command = '{} {}'\
                .format(self.taskset_cmd, _command)

    if self.cgroup and hasattr(self.target, 'cgroups'):
        # Get a reference to the CGroup to use
        _command = self.target.cgroups.run_into_cmd(self.cgroup, _command)

    # Start FTrace (if required)
    if ftrace:  設置ftrace相關sysfs節點,啟動ftrace抓取
        ftrace.start()

    # Wait `start_pause` seconds before running the workload
    if start_pause_s:
        self._log.info('Waiting %f seconds before starting workload execution',
                       start_pause_s)
        sleep(start_pause_s)

    # Start task in background if required
    if background:  是否作為背景進程運行
        self._log.debug('WlGen [background]: %s', _command)
        self.target.background(_command, as_root=as_root)
        self.output['executor'] = ''

    # Start task in foreground
    else:
        self._log.info('Workload execution START:')
        self._log.info('   %s', _command)
        # Run command and wait for it to complete
        results = self.target.execute(_command, as_root=as_root)  在class TestEnv的_init_target創建了class Target的子類class AndroidTarget。此處execute都是通過adb shell執行。
        self.output['executor'] = results

    # Wait `end_pause` seconds before stopping ftrace
    if end_pause_s:
        self._log.info('Waiting %f seconds before stopping trace collection',
                       end_pause_s)
        sleep(end_pause_s)

    # Stop FTrace (if required)
    ftrace_dat = None
    if ftrace:  停止ftrace抓取,並導出ftrace內容。
        ftrace.stop()
        ftrace_dat = out_dir + '/' + self.test_label + '.dat'
        dirname = os.path.dirname(ftrace_dat)
        if not os.path.exists(dirname):
            self._log.debug('Create ftrace results folder [%s]',
                            dirname)
            os.makedirs(dirname)
        self._log.info('Pulling trace file into [%s]...', ftrace_dat)
        ftrace.get_trace(ftrace_dat)

    if not background:
        self.__callback('postrun', destdir=out_dir)
        self._log.debug('Workload execution COMPLETED')

    return ftrace_dat

在了解了基類Workload之后,稍微了解一下class RTA。

RTA根據需要增加了calibrate,用於在執行rtapp workload之前,校准cpu的性能。

另外擴展了四種任務類型,class Ramp、class Step、class Pulse和class Periodic。

如何擴展?

workload的擴展都是基於class Workload進行。如果需要創建自己的workload,就需要參照rta.py,寫一個自己的子類;class LocalLinuxTarget作為class LinuxTarget的子類,用於測試本地host設備。

 

libs/devlib

target.py中定義了基類class Target,以及兩種類型的子類class LinuxTarget和class AndroidTarget,針對ssh連接設備和adb連接設備。

class Workload的三個子類,分別對應三種不同類型的連接class AdbConnection、class SshConnection和class LocalConnection。

class FtraceController進行ftrace抓取前buffer大小、filter等的設置,導出ftrace,進行trace.dat到trace.txt的轉變,以及抓取結束后的清理工作。

 

instrument的__init__.py中定義了基類class Instrument,用於擴展不同類型的測量儀器,一般對應的是物理上存在的設備。

class DaqInstrument、class EnergyProbeInstrument和class HwmonInstrument分別對應DAQ、Energy Probe和hwmon三種設備。

 

class Module基類用於針對不同模塊進行配置,有的是配置某一模塊的內核sysfs節點,有的是使用命令執行操作。

比如class BigLittleModule,online/offline不同cluster的CPU,或者獲取CPU的各種信息。

class CpufreqModule顯示/設置CPU的governor、最高頻率、當前頻率等等信息。

 

bin存放devlib用到的可執行文件,比如busybox、trace-cmd等。

如何擴展?

所以綜合下來,在devlib中可能根據class Workload需要擴展不同類型的連接。

如果有新的測試儀器,需要擴展class Instrument。

有時候為了方便對摸快操作,可以基於class Module進行擴展。

libs/trappy

從TRAPpy的縮寫即可知道,一是解析trace,二是對解析結果進行可視化顯示。

在trappy/trappy下有很多python腳本,里面注冊了很多ftrace的解析器,register_ftrace_parser和register_dynamic_ftrace。

libs/bart

在進行了這些分析之后,可以看出test、experiment、workload之間的關系。

一個test可以對應一個或多個experiment;一個experiment可以對應一個或多個workload。

test對應tests目錄中的腳本,experiment對應Executor,workload對應wlgen。

編寫測試用例

編寫分析腳本

參考資料

  1. LISA Wiki:https://github.com/ARM-software/lisa/wiki
  2. LISA Git:https://github.com/ARM-software/lisa


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM