1#!/usr/bin/env python3
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may 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,
13# WITHOUT 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 os
19import statistics
20import subprocess
21import sys
22import tempfile
23import time
24
25transfer_size_mib = 100
26num_runs = 10
27
28# Make sure environment is setup, otherwise "adb" module is not available.
29if os.getenv("ANDROID_BUILD_TOP") is None:
30    print("Run source/lunch before running " + sys.argv[0])
31    sys.exit()
32
33import adb
34
35def lock_min(device):
36    device.shell_nocheck(["""
37        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
38            echo userspace > $x/scaling_governor
39            cat $x/scaling_min_freq > $x/scaling_setspeed
40        done
41    """])
42
43def lock_max(device):
44    device.shell_nocheck(["""
45        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
46            echo userspace > $x/scaling_governor
47            cat $x/scaling_max_freq > $x/scaling_setspeed
48        done
49    """])
50
51def unlock(device):
52    device.shell_nocheck(["""
53        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
54            echo ondemand > $x/scaling_governor
55            echo sched > $x/scaling_governor
56            echo schedutil > $x/scaling_governor
57        done
58    """])
59
60def harmonic_mean(xs):
61    return 1.0 / statistics.mean([1.0 / x for x in xs])
62
63def analyze(name, speeds):
64    median = statistics.median(speeds)
65    mean = harmonic_mean(speeds)
66    stddev = statistics.stdev(speeds)
67    msg = "%s: %d runs: median %.2f MiB/s, mean %.2f MiB/s, stddev: %.2f MiB/s"
68    print(msg % (name, len(speeds), median, mean, stddev))
69
70def benchmark_sink(device=None, size_mb=transfer_size_mib):
71    if device == None:
72        device = adb.get_device()
73
74    speeds = list()
75    cmd = device.adb_cmd + ["raw", "sink:%d" % (size_mb * 1024 * 1024)]
76
77    with tempfile.TemporaryFile() as tmpfile:
78        tmpfile.truncate(size_mb * 1024 * 1024)
79
80        for _ in range(0, num_runs):
81            tmpfile.seek(0)
82            begin = time.time()
83            subprocess.check_call(cmd, stdin=tmpfile)
84            end = time.time()
85            speeds.append(size_mb / float(end - begin))
86
87    analyze("sink   %dMiB (write RAM)  " % size_mb, speeds)
88
89def benchmark_source(device=None, size_mb=transfer_size_mib):
90    if device == None:
91        device = adb.get_device()
92
93    speeds = list()
94    cmd = device.adb_cmd + ["raw", "source:%d" % (size_mb * 1024 * 1024)]
95
96    with open(os.devnull, 'w') as devnull:
97        for _ in range(0, num_runs):
98            begin = time.time()
99            subprocess.check_call(cmd, stdout=devnull)
100            end = time.time()
101            speeds.append(size_mb / float(end - begin))
102
103    analyze("source %dMiB (read RAM)   " % size_mb, speeds)
104
105def benchmark_push(device=None, file_size_mb=transfer_size_mib):
106    if device == None:
107        device = adb.get_device()
108
109    remote_path = "/data/local/tmp/adb_benchmark_push_tmp"
110    local_path = "/tmp/adb_benchmark_temp"
111
112    with open(local_path, "wb") as f:
113        f.truncate(file_size_mb * 1024 * 1024)
114
115    speeds = list()
116    for _ in range(0, num_runs):
117        begin = time.time()
118        parameters = ['-Z'] # Disable compression since our file is full of 0s
119        device.push(local=local_path, remote=remote_path, parameters=parameters)
120        end = time.time()
121        speeds.append(file_size_mb / float(end - begin))
122
123    analyze("push   %dMiB (write flash)" % file_size_mb, speeds)
124
125def benchmark_pull(device=None, file_size_mb=transfer_size_mib):
126    if device == None:
127        device = adb.get_device()
128
129    remote_path = "/data/local/tmp/adb_benchmark_pull_temp"
130    local_path = "/tmp/adb_benchmark_temp"
131
132    device.shell(["dd", "if=/dev/zero", "of=" + remote_path, "bs=1m",
133                  "count=" + str(file_size_mb)])
134    speeds = list()
135    for _ in range(0, num_runs):
136        begin = time.time()
137        device.pull(remote=remote_path, local=local_path)
138        end = time.time()
139        speeds.append(file_size_mb / float(end - begin))
140
141    analyze("pull   %dMiB (read flash) " % file_size_mb, speeds)
142
143def benchmark_device_dd(device=None, file_size_mb=transfer_size_mib):
144    if device == None:
145        device = adb.get_device()
146
147    speeds = list()
148    for _ in range(0, num_runs):
149        begin = time.time()
150        device.shell(["dd", "if=/dev/zero", "bs=1m",
151                      "count=" + str(file_size_mb)])
152        end = time.time()
153        speeds.append(file_size_mb / float(end - begin))
154
155    analyze("dd     %dMiB (write flash)" % file_size_mb, speeds)
156
157def main():
158    device = adb.get_device()
159    unlock(device)
160
161    benchmark_sink(device)
162    benchmark_source(device)
163    benchmark_push(device)
164    benchmark_pull(device)
165    benchmark_device_dd(device)
166
167if __name__ == "__main__":
168    main()
169