• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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