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 logging 19import os 20import re 21import webbrowser 22import time 23 24from gfxinfo import GfxInfo 25from surfaceflinger import SurfaceFlinger 26 27from . import System 28 29class Workload(object): 30 """ 31 Base class for Android related workloads 32 """ 33 34 _packages = None 35 _availables = {} 36 37 def __init__(self, test_env): 38 """ 39 Initialized workloads available on the specified test environment 40 41 test_env: target test environment 42 """ 43 self._te = test_env 44 self._target = test_env.target 45 self._log = logging.getLogger('Workload') 46 47 # Set of data reported in output of each run 48 self.trace_file = None 49 self.nrg_report = None 50 51 # Hooks to run at different points of workload execution 52 self.hooks = {} 53 54 def _adb(self, cmd): 55 return 'adb -s {} {}'.format(self._target.adb_name, cmd) 56 57 @classmethod 58 def _check_availables(cls, test_env): 59 """ 60 List the supported android workloads which are available on the target 61 """ 62 63 _log = logging.getLogger('Workload') 64 65 # Getting the list of installed packages 66 cls._packages = test_env.target.list_packages() 67 _log.debug('Packages:\n%s', cls._packages) 68 69 _log.debug('Building list of available workloads...') 70 for sc in Workload.__subclasses__(): 71 _log.debug('Checking workload [%s]...', sc.__name__) 72 73 if sc.package == 'optional': 74 cls._availables[sc.__name__.lower()] = sc 75 continue 76 77 required_packages = [sc.package] 78 if hasattr(sc, 'test_package'): 79 required_packages.append(sc.test_package) 80 81 if all(p in cls._packages for p in required_packages): 82 cls._availables[sc.__name__.lower()] = sc 83 84 _log.info('Supported workloads available on target:') 85 _log.info(' %s', ', '.join(cls._availables.keys())) 86 87 @classmethod 88 def getInstance(cls, test_env, name): 89 """ 90 Get a reference to the specified Android workload 91 """ 92 93 # Initialize list of available workloads 94 if cls._packages is None: 95 cls._check_availables(test_env) 96 97 if name.lower() not in cls._availables: 98 msg = 'Workload [{}] not available on target'.format(name) 99 raise ValueError(msg) 100 101 ret_cls = cls._availables[name.lower()](test_env) 102 103 # Add generic support for cgroup tracing (detect if cgroup module exists) 104 if ('modules' in test_env.conf) and ('cgroups' in test_env.conf['modules']): 105 # Enable dumping support (which happens after systrace starts) 106 ret_cls._log.info('Enabling CGroup support for dumping schedtune/cpuset events') 107 ret_cls.add_hook('post_collect_start', ret_cls.post_collect_start_cgroup) 108 # Also update the extra ftrace points needed 109 if not 'systrace' in test_env.conf: 110 test_env.conf['systrace'] = { 'extra_events': ['cgroup_attach_task', 'sched_process_fork'] } 111 else: 112 if not 'extra_events' in test_env.conf: 113 test_env.conf['systrace']['extra_events'] = ['cgroup_attach_task', 'sched_process_fork'] 114 else: 115 test_env.conf['systrace']['extra_events'].append(['cgroup_attach_task', 'sched_process_fork']) 116 117 return ret_cls 118 119 def trace_cgroup(self, controller, cgroup): 120 cgroup = self._te.target.cgroups.controllers[controller].cgroup('/' + cgroup) 121 cgroup.trace_cgroup_tasks() 122 123 def post_collect_start_cgroup(self): 124 # Since systrace starts asynchronously, wait for trace to start 125 while True: 126 if self._te.target.execute('cat /d/tracing/tracing_on')[0] == "0": 127 time.sleep(0.1) 128 continue 129 break 130 131 self.trace_cgroup('schedtune', '') # root 132 self.trace_cgroup('schedtune', 'top-app') 133 self.trace_cgroup('schedtune', 'foreground') 134 self.trace_cgroup('schedtune', 'background') 135 self.trace_cgroup('schedtune', 'rt') 136 137 self.trace_cgroup('cpuset', '') # root 138 self.trace_cgroup('cpuset', 'top-app') 139 self.trace_cgroup('cpuset', 'foreground') 140 self.trace_cgroup('cpuset', 'background') 141 self.trace_cgroup('cpuset', 'system-background') 142 143 def add_hook(self, hook, hook_fn): 144 allowed = ['post_collect_start'] 145 if hook not in allowed: 146 return 147 self.hooks[hook] = hook_fn 148 149 def run(self, out_dir, collect='', 150 **kwargs): 151 raise RuntimeError('Not implemented') 152 153 def tracingStart(self, screen_always_on=True): 154 # Keep the screen on during any data collection 155 if screen_always_on: 156 System.screen_always_on(self._target, enable=True) 157 # Reset the dumpsys data for the package 158 if 'gfxinfo' in self.collect: 159 System.gfxinfo_reset(self._target, self.package) 160 if 'surfaceflinger' in self.collect: 161 System.surfaceflinger_reset(self._target, self.package) 162 if 'logcat' in self.collect: 163 System.logcat_reset(self._target) 164 # Make sure ftrace and systrace are not both specified to be collected 165 if 'ftrace' in self.collect and 'systrace' in self.collect: 166 msg = 'ftrace and systrace cannot be used at the same time' 167 raise ValueError(msg) 168 # Start FTrace 169 if 'ftrace' in self.collect: 170 self.trace_file = os.path.join(self.out_dir, 'trace.dat') 171 self._log.info('FTrace START') 172 self._te.ftrace.start() 173 # Start Systrace (mutually exclusive with ftrace) 174 elif 'systrace' in self.collect: 175 self.trace_file = os.path.join(self.out_dir, 'trace.html') 176 # Get the systrace time 177 match = re.search(r'systrace_([0-9]+)', self.collect) 178 self._trace_time = match.group(1) if match else None 179 self._log.info('Systrace START') 180 self._target.execute('echo 0 > /d/tracing/tracing_on') 181 self._systrace_output = System.systrace_start( 182 self._te, self.trace_file, self._trace_time, conf=self._te.conf) 183 if 'energy' in self.collect: 184 # Wait for systrace to start before cutting off USB 185 while True: 186 if self._target.execute('cat /d/tracing/tracing_on')[0] == "0": 187 time.sleep(0.1) 188 continue 189 break 190 # Initialize energy meter results 191 if 'energy' in self.collect and self._te.emeter: 192 self._te.emeter.reset() 193 self._log.info('Energy meter STARTED') 194 # Run post collect hooks passed added by the user of wload object 195 if 'post_collect_start' in self.hooks: 196 hookfn = self.hooks['post_collect_start'] 197 self._log.info("Running post collect startup hook {}".format(hookfn.__name__)) 198 hookfn() 199 200 def tracingStop(self, screen_always_on=True): 201 # Collect energy meter results 202 if 'energy' in self.collect and self._te.emeter: 203 self.nrg_report = self._te.emeter.report(self.out_dir) 204 self._log.info('Energy meter STOPPED') 205 # Stop FTrace 206 if 'ftrace' in self.collect: 207 self._te.ftrace.stop() 208 self._log.info('FTrace STOP') 209 self._te.ftrace.get_trace(self.trace_file) 210 # Stop Systrace (mutually exclusive with ftrace) 211 elif 'systrace' in self.collect: 212 if not self._systrace_output: 213 self._log.warning('Systrace is not running!') 214 else: 215 self._log.info('Waiting systrace report [%s]...', 216 self.trace_file) 217 if self._trace_time is None: 218 # Systrace expects <enter> 219 self._systrace_output.sendline('') 220 self._systrace_output.wait() 221 # Parse the data gathered from dumpsys gfxinfo 222 if 'gfxinfo' in self.collect: 223 dump_file = os.path.join(self.out_dir, 'dumpsys_gfxinfo.txt') 224 System.gfxinfo_get(self._target, self.package, dump_file) 225 self.gfxinfo = GfxInfo(dump_file) 226 # Parse the data gathered from dumpsys SurfaceFlinger 227 if 'surfaceflinger' in self.collect: 228 dump_file = os.path.join(self.out_dir, 'dumpsys_surfaceflinger.txt') 229 System.surfaceflinger_get(self._target, self.package, dump_file) 230 self.surfaceflinger = SurfaceFlinger(dump_file) 231 if 'logcat' in self.collect: 232 dump_file = os.path.join(self.out_dir, 'logcat.txt') 233 System.logcat_get(self._target, dump_file) 234 # Dump a platform description 235 self._te.platform_dump(self.out_dir) 236 # Restore automatic screen off 237 if screen_always_on: 238 System.screen_always_on(self._target, enable=False) 239 240 def traceShow(self): 241 """ 242 Open the collected trace using the most appropriate native viewer. 243 244 The native viewer depends on the specified trace format: 245 - ftrace: open using kernelshark 246 - systrace: open using a browser 247 248 In both cases the native viewer is assumed to be available in the host 249 machine. 250 """ 251 252 if 'ftrace' in self.collect: 253 os.popen("kernelshark {}".format(self.trace_file)) 254 return 255 256 if 'systrace' in self.collect: 257 webbrowser.open(self.trace_file) 258 return 259 260 self._log.warning('No trace collected since last run') 261 262# vim :set tabstop=4 shiftwidth=4 expandtab 263