1 // Copyright 2021 The libgav1 Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/utils/cpu.h"
16
17 #if defined(__linux__)
18 #include <unistd.h>
19
20 #include <cerrno>
21 #include <climits>
22 #include <cstdio>
23 #include <cstdlib>
24 #include <cstring>
25 #endif // defined(__linux__)
26
27 #include "gtest/gtest.h"
28 #include "src/utils/logging.h"
29
30 namespace libgav1 {
31 namespace {
32
33 #if defined(__linux__)
34
35 // Sample code for getting the number of performance CPU cores. The following
36 // sources were consulted:
37 // * https://www.kernel.org/doc/html/latest/admin-guide/cputopology.html
38 // * cpu-hotplug.txt: CPU hotplug Support in Linux(tm) Kernel
39 // https://lwn.net/Articles/537570/
40 // * https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu
41 // * Android bionic source code of get_nprocs():
42 // libc/bionic/sysinfo.cpp
43 // * glibc 2.30 source code of get_nprocs():
44 // sysdeps/unix/sysv/linux/getsysstats.c
45 //
46 // Tested on:
47 // * Asus Nexus 7 2013: Qualcomm Snapdragon 600, 32-bit Android 6.0.1
48 // (Marshmallow). Brings cores online and offline dynamically. (The tablet
49 // has 4 cores. "0", "0-1", "0-2", and "0-3" have all been observed in the
50 // /sys/devices/system/cpu/online file.) This causes the number of cores
51 // currently online to potentially be lower than the number of cores that can
52 // be brought online quickly.
53 // * General Mobile 4G: Qualcomm Snapdragon 410, 32-bit Android 7.1.1 (Nougat).
54 // * Motorola Moto G5 Plus: Qualcomm Snapdragon 625, 32-bit Android 8.1.0
55 // (Oreo).
56 // * Motorola Moto G7 Play: Qualcomm Snapdragon 632, 32-bit Android 9 (Pie).
57 // All 8 cores have the same cpuinfo_max_freq (1804800), but there are two
58 // values of cpuinfo_min_freq: cores 0-3 have 614400 and cores 4-7 have
59 // 633600. We would need to check cpuinfo_min_freq to differentiate the two
60 // kinds of cores (Qualcomm Kryo 250 Gold and Qualcomm Kryo 250 Silver).
61 // * Pixel 2 XL: Qualcomm Snapdragon 835, 64-bit Android 9 (Pie).
62 // * Pixel 3: Qualcomm Snapdragon 845, 64-bit Android 9 (Pie).
63 // * Pixel 3a: Qualcomm Snapdragon 670, 64-bit Android 9 (Pie).
64 // * Samsung Galaxy S6: Samsung Exynos 7 Octa (7420), 64-bit Android 7.0
65 // (Nougat).
66 // * Samsung Galaxy S8+ (SM-G955FD): Samsung Exynos 8895, 64-bit Android 8.0.0.
67 //
68 // Note: The sample code needs to use the 'long' type because it is the return
69 // type of the Standard C Library function strtol(). The ClangTidy warnings are
70 // suppressed with NOLINT(google-runtime-int) comments.
71
72 // Returns the number of online processor cores.
GetNumberOfProcessorsOnline()73 int GetNumberOfProcessorsOnline() {
74 // See https://developer.android.com/ndk/guides/cpu-features.
75 long num_cpus = sysconf(_SC_NPROCESSORS_ONLN); // NOLINT(google-runtime-int)
76 if (num_cpus < 0) {
77 LIBGAV1_DLOG(ERROR, "sysconf(_SC_NPROCESSORS_ONLN) failed: %s.",
78 strerror(errno));
79 return 0;
80 }
81 // It is safe to cast num_cpus to int. sysconf(_SC_NPROCESSORS_ONLN) returns
82 // the return value of get_nprocs(), which is an int.
83 return static_cast<int>(num_cpus);
84 }
85
86 // These CPUs support heterogeneous multiprocessing.
87 #if defined(__arm__) || defined(__aarch64__)
88
89 // A helper function used by GetNumberOfPerformanceCoresOnline().
90 //
91 // Returns the cpuinfo_max_freq value (in kHz) of the given CPU. Returns 0 on
92 // failure.
GetCpuinfoMaxFreq(int cpu_index)93 long GetCpuinfoMaxFreq(int cpu_index) { // NOLINT(google-runtime-int)
94 char buffer[128];
95 const int rv = snprintf(
96 buffer, sizeof(buffer),
97 "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", cpu_index);
98 if (rv < 0 || rv >= sizeof(buffer)) {
99 LIBGAV1_DLOG(ERROR, "snprintf failed, or |buffer| is too small.");
100 return 0;
101 }
102 FILE* file = fopen(buffer, "r");
103 if (file == nullptr) {
104 LIBGAV1_DLOG(ERROR, "fopen(\"%s\", \"r\") failed: %s.", buffer,
105 strerror(errno));
106 return 0;
107 }
108 char* const str = fgets(buffer, sizeof(buffer), file);
109 fclose(file);
110 if (str == nullptr) {
111 LIBGAV1_DLOG(ERROR, "fgets failed.");
112 return 0;
113 }
114 const long freq = strtol(str, nullptr, 10); // NOLINT(google-runtime-int)
115 if (freq <= 0 || freq == LONG_MAX) {
116 LIBGAV1_DLOG(ERROR,
117 "No conversion can be performed, or the converted value is "
118 "invalid: %ld.",
119 freq);
120 return 0;
121 }
122 return freq;
123 }
124
125 // Returns the number of performance CPU cores that are online. The number of
126 // efficiency CPU cores is subtracted from the total number of CPU cores. Uses
127 // cpuinfo_max_freq to determine whether a CPU is a performance core or an
128 // efficiency core.
129 //
130 // This function is not perfect. For example, the Snapdragon 632 SoC used in
131 // Motorola Moto G7 has performance and efficiency cores with the same
132 // cpuinfo_max_freq but different cpuinfo_min_freq. This function fails to
133 // differentiate the two kinds of cores and reports all the cores as
134 // performance cores.
GetNumberOfPerformanceCoresOnline()135 int GetNumberOfPerformanceCoresOnline() {
136 // Get the online CPU list. Some examples of the online CPU list are:
137 // "0-7"
138 // "0"
139 // "0-1,2,3,4-7"
140 char online[512];
141 FILE* file = fopen("/sys/devices/system/cpu/online", "r");
142 if (file == nullptr) {
143 LIBGAV1_DLOG(ERROR,
144 "fopen(\"/sys/devices/system/cpu/online\", \"r\") failed: %s.",
145 strerror(errno));
146 return 0;
147 }
148 char* const str = fgets(online, sizeof(online), file);
149 fclose(file);
150 file = nullptr;
151 if (str == nullptr) {
152 LIBGAV1_DLOG(ERROR, "fgets failed.");
153 return 0;
154 }
155 LIBGAV1_DLOG(INFO, "The online CPU list is %s", online);
156
157 // Count the number of the slowest CPUs. Some SoCs such as Snapdragon 855
158 // have performance cores with different max frequencies, so only the slowest
159 // CPUs are efficiency cores. If we count the number of the fastest CPUs, we
160 // will fail to count the second fastest performance cores.
161 long slowest_cpu_freq = LONG_MAX; // NOLINT(google-runtime-int)
162 int num_slowest_cpus = 0;
163 int num_cpus = 0;
164 const char* cp = online;
165 int range_begin = -1;
166 while (true) {
167 char* str_end;
168 const int cpu = static_cast<int>(strtol(cp, &str_end, 10));
169 if (str_end == cp) {
170 break;
171 }
172 cp = str_end;
173 if (*cp == '-') {
174 range_begin = cpu;
175 } else {
176 if (range_begin == -1) {
177 range_begin = cpu;
178 }
179
180 num_cpus += cpu - range_begin + 1;
181 for (int i = range_begin; i <= cpu; ++i) {
182 const long freq = GetCpuinfoMaxFreq(i); // NOLINT(google-runtime-int)
183 if (freq <= 0) {
184 return 0;
185 }
186 LIBGAV1_DLOG(INFO, "cpu%d max frequency is %ld kHz.", i, freq);
187 if (freq < slowest_cpu_freq) {
188 slowest_cpu_freq = freq;
189 num_slowest_cpus = 0;
190 }
191 if (freq == slowest_cpu_freq) {
192 ++num_slowest_cpus;
193 }
194 }
195
196 range_begin = -1;
197 }
198 if (*cp == '\0') {
199 break;
200 }
201 ++cp;
202 }
203
204 LIBGAV1_DLOG(INFO, "There are %d CPU cores.", num_cpus);
205 LIBGAV1_DLOG(INFO,
206 "%d CPU cores are the slowest, with max frequency %ld kHz.",
207 num_slowest_cpus, slowest_cpu_freq);
208 // If there are faster CPU cores than the slowest CPU cores, exclude the
209 // slowest CPU cores.
210 if (num_slowest_cpus < num_cpus) {
211 num_cpus -= num_slowest_cpus;
212 }
213 return num_cpus;
214 }
215
216 #else
217
218 // Assume symmetric multiprocessing.
GetNumberOfPerformanceCoresOnline()219 int GetNumberOfPerformanceCoresOnline() {
220 return GetNumberOfProcessorsOnline();
221 }
222
223 #endif
224
225 #endif // defined(__linux__)
226
227 /*
228 Run this test with logging enabled on an Android device:
229 64-bit Android:
230 tests/run_android_test.sh --test cpu --enable_asserts
231 32-bit Android:
232 tests/run_android_test.sh --test cpu --arch arm \
233 --enable_asserts
234 */
TEST(CpuTest,GetNumberOfPerformanceCoresOnline)235 TEST(CpuTest, GetNumberOfPerformanceCoresOnline) {
236 #if defined(__linux__)
237 const int num_cpus = GetNumberOfProcessorsOnline();
238 ASSERT_NE(num_cpus, 0);
239 LIBGAV1_DLOG(INFO, "There are %d cores online.", num_cpus);
240 const int num_performance_cpus = GetNumberOfPerformanceCoresOnline();
241 ASSERT_NE(num_performance_cpus, 0);
242 LIBGAV1_DLOG(INFO, "There are %d performance cores online.",
243 num_performance_cpus);
244 #endif // defined(__linux__)
245 }
246
247 } // namespace
248 } // namespace libgav1
249