Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 1 | Overview |
| 2 | ======== |
| 3 | |
| 4 | A :class:`Target` instance serves as the main interface to the target device. |
| 5 | There currently three target interfaces: |
| 6 | |
| 7 | - :class:`LinuxTarget` for interacting with Linux devices over SSH. |
| 8 | - :class:`AndroidTraget` for interacting with Android devices over adb. |
| 9 | - :class:`LocalLinuxTarget`: for interacting with the local Linux host. |
| 10 | |
| 11 | They all work in more-or-less the same way, with the major difference being in |
| 12 | how connection settings are specified; though there may also be a few APIs |
| 13 | specific to a particular target type (e.g. :class:`AndroidTarget` exposes |
| 14 | methods for working with logcat). |
| 15 | |
| 16 | |
| 17 | Acquiring a Target |
| 18 | ------------------ |
| 19 | |
| 20 | To create an interface to your device, you just need to instantiate one of the |
| 21 | :class:`Target` derivatives listed above, and pass it the right |
| 22 | ``connection_settings``. Code snippet below gives a typical example of |
| 23 | instantiating each of the three target types. |
| 24 | |
| 25 | .. code:: python |
| 26 | |
| 27 | from devlib import LocalLinuxTarget, LinuxTarget, AndroidTarget |
| 28 | |
| 29 | # Local machine requires no special connection settings. |
| 30 | t1 = LocalLinuxTarget() |
| 31 | |
| 32 | # For a Linux device, you will need to provide the normal SSH credentials. |
| 33 | # Both password-based, and key-based authentication is supported (password |
| 34 | # authentication requires sshpass to be installed on your host machine).' |
Morten Rasmussen | 192fb52 | 2016-03-21 15:15:26 +0000 | [diff] [blame] | 35 | t2 = LinuxTarget(connection_settings={'host': '192.168.0.5', |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 36 | 'username': 'root', |
| 37 | 'password': 'sekrit', |
| 38 | # or |
| 39 | 'keyfile': '/home/me/.ssh/id_rsa'}) |
| 40 | |
| 41 | # For an Android target, you will need to pass the device name as reported |
| 42 | # by "adb devices". If there is only one device visible to adb, you can omit |
| 43 | # this setting and instantiate similar to a local target. |
| 44 | t3 = AndroidTarget(connection_settings={'device': '0123456789abcde'}) |
| 45 | |
| 46 | Instantiating a target may take a second or two as the remote device will be |
| 47 | queried to initialize :class:`Target`'s internal state. If you would like to |
| 48 | create a :class:`Target` instance but not immediately connect to the remote |
| 49 | device, you can pass ``connect=False`` parameter. If you do that, you would have |
| 50 | to then explicitly call ``t.connect()`` before you can interact with the device. |
| 51 | |
| 52 | There are a few additional parameters you can pass in instantiation besides |
| 53 | ``connection_settings``, but they are usually unnecessary. Please see |
| 54 | :class:`Target` API documentation for more details. |
| 55 | |
| 56 | Target Interface |
| 57 | ---------------- |
| 58 | |
| 59 | This is a quick overview of the basic interface to the device. See |
Morten Rasmussen | 192fb52 | 2016-03-21 15:15:26 +0000 | [diff] [blame] | 60 | :class:`Target` API documentation for the full list of supported methods and |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 61 | more detailed documentation. |
| 62 | |
| 63 | One-time Setup |
| 64 | ~~~~~~~~~~~~~~ |
| 65 | |
| 66 | .. code:: python |
| 67 | |
| 68 | from devlib import LocalLinuxTarget |
| 69 | t = LocalLinuxTarget() |
| 70 | |
| 71 | t.setup() |
| 72 | |
| 73 | This sets up the target for ``devlib`` interaction. This includes creating |
| 74 | working directories, deploying busybox, etc. It's usually enough to do this once |
| 75 | for a new device, as the changes this makes will persist across reboots. |
| 76 | However, there is no issue with calling this multiple times, so, to be on the |
Marc Bonnici | 1fd5636 | 2017-01-10 15:35:21 +0000 | [diff] [blame] | 77 | safe side, it's a good idea to call this once at the beginning of your scripts. |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 78 | |
| 79 | Command Execution |
| 80 | ~~~~~~~~~~~~~~~~~ |
| 81 | |
| 82 | There are several ways to execute a command on the target. In each case, a |
Marc Bonnici | 1fd5636 | 2017-01-10 15:35:21 +0000 | [diff] [blame] | 83 | :class:`TargetError` will be raised if something goes wrong. In each case, it is |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 84 | also possible to specify ``as_root=True`` if the specified command should be |
| 85 | executed as root. |
| 86 | |
| 87 | .. code:: python |
| 88 | |
| 89 | from devlib import LocalLinuxTarget |
| 90 | t = LocalLinuxTarget() |
| 91 | |
| 92 | # Execute a command |
| 93 | output = t.execute('echo $PWD') |
| 94 | |
| 95 | # Execute command via a subprocess and return the corresponding Popen object. |
| 96 | # This will block current connection to the device until the command |
| 97 | # completes. |
| 98 | p = t.background('echo $PWD') |
| 99 | output, error = p.communicate() |
| 100 | |
| 101 | # Run the command in the background on the device and return immediately. |
| 102 | # This will not block the connection, allowing to immediately execute another |
| 103 | # command. |
| 104 | t.kick_off('echo $PWD') |
| 105 | |
| 106 | # This is used to invoke an executable binary on the device. This allows some |
| 107 | # finer-grained control over the invocation, such as specifying the directory |
| 108 | # in which the executable will run; however you're limited to a single binary |
| 109 | # and cannot construct complex commands (e.g. this does not allow chaining or |
| 110 | # piping several commands together). |
| 111 | output = t.invoke('echo', args=['$PWD'], in_directory='/') |
| 112 | |
| 113 | File Transfer |
| 114 | ~~~~~~~~~~~~~ |
| 115 | |
| 116 | .. code:: python |
| 117 | |
| 118 | from devlib import LocalLinuxTarget |
| 119 | t = LocalLinuxTarget() |
| 120 | |
| 121 | # "push" a file from the local machine onto the target device. |
| 122 | t.push('/path/to/local/file.txt', '/path/to/target/file.txt') |
| 123 | |
| 124 | # "pull" a file from the target device into a location on the local machine |
| 125 | t.pull('/path/to/target/file.txt', '/path/to/local/file.txt') |
| 126 | |
| 127 | # Install the specified binary on the target. This will deploy the file and |
| 128 | # ensure it's executable. This will *not* guarantee that the binary will be |
| 129 | # in PATH. Instead the path to the binary will be returned; this should be |
| 130 | # used to call the binary henceforth. |
| 131 | target_bin = t.install('/path/to/local/bin.exe') |
| 132 | # Example invocation: |
| 133 | output = t.execute('{} --some-option'.format(target_bin)) |
| 134 | |
| 135 | The usual access permission constraints on the user account (both on the target |
| 136 | and the host) apply. |
| 137 | |
| 138 | Process Control |
| 139 | ~~~~~~~~~~~~~~~ |
| 140 | |
| 141 | .. code:: python |
| 142 | |
| 143 | import signal |
| 144 | from devlib import LocalLinuxTarget |
| 145 | t = LocalLinuxTarget() |
| 146 | |
| 147 | # return PIDs of all running instances of a process |
| 148 | pids = t.get_pids_of('sshd') |
| 149 | |
| 150 | # kill a running process. This works the same ways as the kill command, so |
| 151 | # SIGTERM will be used by default. |
| 152 | t.kill(666, signal=signal.SIGKILL) |
| 153 | |
| 154 | # kill all running instances of a process. |
| 155 | t.killall('badexe', signal=signal.SIGKILL) |
| 156 | |
Marc Bonnici | 1fd5636 | 2017-01-10 15:35:21 +0000 | [diff] [blame] | 157 | # List processes running on the target. This returns a list of parsed |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 158 | # PsEntry records. |
| 159 | entries = t.ps() |
| 160 | # e.g. print virtual memory sizes of all running sshd processes: |
| 161 | print ', '.join(str(e.vsize) for e in entries if e.name == 'sshd') |
| 162 | |
| 163 | |
| 164 | More... |
| 165 | ~~~~~~~ |
| 166 | |
| 167 | As mentioned previously, the above is not intended to be exhaustive |
| 168 | documentation of the :class:`Target` interface. Please refer to the API |
| 169 | documentation for the full list of attributes and methods and their parameters. |
| 170 | |
| 171 | Super User Privileges |
| 172 | --------------------- |
| 173 | |
| 174 | It is not necessary for the account logged in on the target to have super user |
| 175 | privileges, however the functionality will obviously be diminished, if that is |
| 176 | not the case. ``devilib`` will determine if the logged in user has root |
| 177 | privileges and the correct way to invoke it. You should avoid including "sudo" |
| 178 | directly in your commands, instead, specify ``as_root=True`` where needed. This |
| 179 | will make your scripts portable across multiple devices and OS's. |
| 180 | |
| 181 | |
| 182 | On-Target Locations |
| 183 | ------------------- |
| 184 | |
| 185 | File system layouts vary wildly between devices and operating systems. |
| 186 | Hard-coding absolute paths in your scripts will mean there is a good chance they |
| 187 | will break if run on a different device. To help with this, ``devlib`` defines |
| 188 | a couple of "standard" locations and a means of working with them. |
| 189 | |
| 190 | working_directory |
| 191 | This is a directory on the target readable and writable by the account |
| 192 | used to log in. This should generally be used for all output generated |
| 193 | by your script on the device and as the destination for all |
| 194 | host-to-target file transfers. It may or may not permit execution so |
| 195 | executables should not be run directly from here. |
| 196 | |
| 197 | executables_directory |
| 198 | This directory allows execution. This will be used by ``install()``. |
| 199 | |
| 200 | .. code:: python |
| 201 | |
| 202 | from devlib import LocalLinuxTarget |
| 203 | t = LocalLinuxTarget() |
| 204 | |
| 205 | # t.path is equivalent to Python standard library's os.path, and should be |
| 206 | # used in the same way. This insures that your scripts are portable across |
| 207 | # both target and host OS variations. e.g. |
| 208 | on_target_path = t.path.join(t.working_directory, 'assets.tar.gz') |
| 209 | t.push('/local/path/to/assets.tar.gz', on_target_path) |
| 210 | |
| 211 | # Since working_directory is a common base path for on-target locations, |
| 212 | # there a short-hand for the above: |
| 213 | t.push('/local/path/to/assets.tar.gz', t.get_workpath('assets.tar.gz')) |
| 214 | |
| 215 | |
| 216 | Modules |
| 217 | ------- |
| 218 | |
| 219 | Additional functionality is exposed via modules. Modules are initialized as |
| 220 | attributes of a target instance. By default, ``hotplug``, ``cpufreq``, |
| 221 | ``cpuidle``, ``cgroups`` and ``hwmon`` will attempt to load on target; additional |
| 222 | modules may be specified when creating a :class:`Target` instance. |
| 223 | |
| 224 | A module will probe the target for support before attempting to load. So if the |
| 225 | underlying platform does not support particular functionality (e.g. the kernel |
| 226 | on target device was built without hotplug support). To check whether a module |
| 227 | has been successfully installed on a target, you can use ``has()`` method, e.g. |
| 228 | |
| 229 | .. code:: python |
| 230 | |
| 231 | from devlib import LocalLinuxTarget |
| 232 | t = LocalLinuxTarget() |
| 233 | |
| 234 | cpu0_freqs = [] |
| 235 | if t.has('cpufreq'): |
| 236 | cpu0_freqs = t.cpufreq.list_frequencies(0) |
| 237 | |
| 238 | |
| 239 | Please see the modules documentation for more detail. |
| 240 | |
| 241 | |
| 242 | Measurement and Trace |
| 243 | --------------------- |
| 244 | |
| 245 | You can collected traces (currently, just ftrace) using |
| 246 | :class:`TraceCollector`\ s. For example |
| 247 | |
| 248 | .. code:: python |
| 249 | |
| 250 | from devlib import AndroidTarget, FtraceCollector |
| 251 | t = LocalLinuxTarget() |
| 252 | |
| 253 | # Initialize a collector specifying the events you want to collect and |
| 254 | # the buffer size to be used. |
| 255 | trace = FtraceCollector(t, events=['power*'], buffer_size=40000) |
| 256 | |
| 257 | # clear ftrace buffer |
| 258 | trace.reset() |
| 259 | |
| 260 | # start trace collection |
| 261 | trace.start() |
| 262 | |
| 263 | # Perform the operations you want to trace here... |
| 264 | import time; time.sleep(5) |
| 265 | |
| 266 | # stop trace collection |
| 267 | trace.stop() |
| 268 | |
| 269 | # extract the trace file from the target into a local file |
| 270 | trace.get_trace('/tmp/trace.bin') |
| 271 | |
| 272 | # View trace file using Kernelshark (must be installed on the host). |
| 273 | trace.view('/tmp/trace.bin') |
| 274 | |
| 275 | # Convert binary trace into text format. This would normally be done |
| 276 | # automatically during get_trace(), unless autoreport is set to False during |
| 277 | # instantiation of the trace collector. |
| 278 | trace.report('/tmp/trace.bin', '/tmp/trace.txt') |
| 279 | |
| 280 | In a similar way, :class:`Instrument` instances may be used to collect |
| 281 | measurements (such as power) from targets that support it. Please see |
| 282 | instruments documentation for more details. |