1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #define LOG_TAG "HwbinderThroughputTest"
17
18 #include <unistd.h>
19 #include <sys/wait.h>
20
21 #include <cstring>
22 #include <iostream>
23 #include <string>
24 #include <tuple>
25 #include <vector>
26
27 #include <log/log.h>
28
29 #include <android/hardware/tests/libhwbinder/1.0/IBenchmark.h>
30 #include <hidl/HidlSupport.h>
31
32 using namespace std;
33 using namespace android;
34 using namespace android::hardware;
35
36 // Generated HIDL files
37 using android::hardware::tests::libhwbinder::V1_0::IBenchmark;
38
39 #define ASSERT_TRUE(cond) \
40 do { \
41 if (!(cond)) {\
42 cerr << __func__ << ":" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \
43 exit(EXIT_FAILURE); \
44 } \
45 } while (0)
46
47 class Pipe {
48 int m_readFd;
49 int m_writeFd;
Pipe(int readFd,int writeFd)50 Pipe(int readFd, int writeFd)
51 : m_readFd{readFd}, m_writeFd{writeFd} {
52 }
53 Pipe(const Pipe &) = delete;
54 Pipe& operator=(const Pipe &) = delete;
55 Pipe& operator=(const Pipe &&) = delete;
56 public:
Pipe(Pipe && rval)57 Pipe(Pipe&& rval) noexcept {
58 m_readFd = rval.m_readFd;
59 m_writeFd = rval.m_writeFd;
60 rval.m_readFd = 0;
61 rval.m_writeFd = 0;
62 }
~Pipe()63 ~Pipe() {
64 if (m_readFd)
65 close(m_readFd);
66 if (m_writeFd)
67 close(m_writeFd);
68 }
signal()69 void signal() {
70 bool val = true;
71 int error = write(m_writeFd, &val, sizeof(val));
72 ASSERT_TRUE(error >= 0);
73 }
wait()74 void wait() {
75 bool val = false;
76 int error = read(m_readFd, &val, sizeof(val));
77 ASSERT_TRUE(error >= 0);
78 }
send(const T & v)79 template<typename T> void send(const T& v) {
80 int error = write(m_writeFd, &v, sizeof(T));
81 ASSERT_TRUE(error >= 0);
82 }
recv(T & v)83 template<typename T> void recv(T& v) {
84 int error = read(m_readFd, &v, sizeof(T));
85 ASSERT_TRUE(error >= 0);
86 }
createPipePair()87 static tuple<Pipe, Pipe> createPipePair() {
88 int a[2];
89 int b[2];
90
91 int error1 = pipe(a);
92 int error2 = pipe(b);
93 ASSERT_TRUE(error1 >= 0);
94 ASSERT_TRUE(error2 >= 0);
95
96 return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1]));
97 }
98 };
99
100 static const uint32_t num_buckets = 128;
101 static const uint64_t max_time_bucket = 50ull * 1000000;
102 static const uint64_t time_per_bucket = max_time_bucket / num_buckets;
103 static constexpr float time_per_bucket_ms = time_per_bucket / 1.0E6;
104
105 struct ProcResults {
106 uint64_t m_best = max_time_bucket;
107 uint64_t m_worst = 0;
108 uint32_t m_buckets[num_buckets] = {0};
109 uint64_t m_transactions = 0;
110 uint64_t m_total_time = 0;
111
112 // Add a new latency data point and update the aggregation info
113 // e.g. best/worst/total_time.
add_timeProcResults114 void add_time(uint64_t time) {
115 m_buckets[min(time, max_time_bucket - 1) / time_per_bucket] += 1;
116 m_best = min(time, m_best);
117 m_worst = max(time, m_worst);
118 m_transactions += 1;
119 m_total_time += time;
120 }
121 // Combine two sets of latency data points and update the aggregation info.
combineProcResults122 static ProcResults combine(const ProcResults& a, const ProcResults& b) {
123 ProcResults ret;
124 for (uint32_t i = 0; i < num_buckets; i++) {
125 ret.m_buckets[i] = a.m_buckets[i] + b.m_buckets[i];
126 }
127 ret.m_worst = max(a.m_worst, b.m_worst);
128 ret.m_best = min(a.m_best, b.m_best);
129 ret.m_transactions = a.m_transactions + b.m_transactions;
130 ret.m_total_time = a.m_total_time + b.m_total_time;
131 return ret;
132 }
133 // Calculate and report the final aggregated results.
dumpProcResults134 void dump() {
135 double best = (double) m_best / 1.0E6;
136 double worst = (double) m_worst / 1.0E6;
137 double average = (double) m_total_time / m_transactions / 1.0E6;
138 cout << "average:"
139 << average
140 << "ms worst:"
141 << worst
142 << "ms best:"
143 << best
144 << "ms"
145 << endl;
146
147 uint64_t cur_total = 0;
148 for (uint32_t i = 0; i < num_buckets; i++) {
149 float cur_time = time_per_bucket_ms * i + 0.5f * time_per_bucket_ms;
150 if ((cur_total < 0.5f * m_transactions)
151 && (cur_total + m_buckets[i] >= 0.5f * m_transactions)) {
152 cout << "50%: " << cur_time << " ";
153 }
154 if ((cur_total < 0.9f * m_transactions)
155 && (cur_total + m_buckets[i] >= 0.9f * m_transactions)) {
156 cout << "90%: " << cur_time << " ";
157 }
158 if ((cur_total < 0.95f * m_transactions)
159 && (cur_total + m_buckets[i] >= 0.95f * m_transactions)) {
160 cout << "95%: " << cur_time << " ";
161 }
162 if ((cur_total < 0.99f * m_transactions)
163 && (cur_total + m_buckets[i] >= 0.99f * m_transactions)) {
164 cout << "99%: " << cur_time << " ";
165 }
166 cur_total += m_buckets[i];
167 }
168 cout << endl;
169
170 }
171 };
172
generateServiceName(int num)173 string generateServiceName(int num) {
174 string serviceName = "hwbinderService" + to_string(num);
175 return serviceName;
176 }
177
service_fx(const string & serviceName,Pipe p)178 void service_fx(const string &serviceName, Pipe p) {
179 // Start service.
180 sp<IBenchmark> server = IBenchmark::getService(serviceName, true);
181 ALOGD("Registering %s", serviceName.c_str());
182 status_t status = server->registerAsService(serviceName);
183 if (status != ::android::OK) {
184 ALOGE("Failed to register service %s", serviceName.c_str());
185 exit(EXIT_FAILURE);
186 }
187
188 ALOGD("Starting %s", serviceName.c_str());
189
190 // Signal service started to master and wait to exit.
191 p.signal();
192 p.wait();
193 exit(EXIT_SUCCESS);
194 }
195
worker_fx(int num,int iterations,int service_count,bool get_stub,Pipe p)196 void worker_fx(
197 int num,
198 int iterations,
199 int service_count,
200 bool get_stub,
201 Pipe p) {
202 srand(num);
203 p.signal();
204 p.wait();
205
206 // Get references to test services.
207 vector<sp<IBenchmark>> workers;
208
209 for (int i = 0; i < service_count; i++) {
210 sp<IBenchmark> service = IBenchmark::getService(
211 generateServiceName(i), get_stub);
212 ASSERT_TRUE(service != NULL);
213 if (get_stub) {
214 ASSERT_TRUE(!service->isRemote());
215 } else {
216 ASSERT_TRUE(service->isRemote());
217 }
218 workers.push_back(service);
219 }
220
221 ProcResults results;
222 chrono::time_point<chrono::high_resolution_clock> start, end;
223 // Prepare data to IPC
224 hidl_vec<uint8_t> data_vec;
225 data_vec.resize(16);
226 for (size_t i = 0; i < data_vec.size(); i++) {
227 data_vec[i] = i;
228 }
229 // Run the benchmark.
230 for (int i = 0; i < iterations; i++) {
231 // Randomly pick a service.
232 int target = rand() % service_count;
233
234 start = chrono::high_resolution_clock::now();
235 Return<void> ret = workers[target]->sendVec(data_vec, [&](const auto &) {});
236 if (!ret.isOk()) {
237 cout << "thread " << num << " failed status: "
238 << ret.description() << endl;
239 exit(EXIT_FAILURE);
240 }
241 end = chrono::high_resolution_clock::now();
242
243 uint64_t cur_time = uint64_t(
244 chrono::duration_cast<chrono::nanoseconds>(end - start).count());
245 results.add_time(cur_time);
246 }
247 // Signal completion to master and wait.
248 p.signal();
249 p.wait();
250
251 // Send results to master and wait for go to exit.
252 p.send(results);
253 p.wait();
254
255 exit (EXIT_SUCCESS);
256 }
257
make_service(string service_name)258 Pipe make_service(string service_name) {
259 auto pipe_pair = Pipe::createPipePair();
260 pid_t pid = fork();
261 if (pid) {
262 /* parent */
263 return move(get<0>(pipe_pair));
264 } else {
265 /* child */
266 service_fx(service_name, move(get<1>(pipe_pair)));
267 /* never get here */
268 return move(get<0>(pipe_pair));
269 }
270 }
271
make_worker(int num,int iterations,int service_count,bool get_stub)272 Pipe make_worker(int num, int iterations, int service_count, bool get_stub) {
273 auto pipe_pair = Pipe::createPipePair();
274 pid_t pid = fork();
275 if (pid) {
276 /* parent */
277 return move(get<0>(pipe_pair));
278 } else {
279 /* child */
280 worker_fx(num, iterations, service_count, get_stub,
281 move(get<1>(pipe_pair)));
282 /* never get here */
283 return move(get<0>(pipe_pair));
284 }
285 }
286
wait_all(vector<Pipe> & v)287 void wait_all(vector<Pipe>& v) {
288 for (size_t i = 0; i < v.size(); i++) {
289 v[i].wait();
290 }
291 }
292
signal_all(vector<Pipe> & v)293 void signal_all(vector<Pipe>& v) {
294 for (size_t i = 0; i < v.size(); i++) {
295 v[i].signal();
296 }
297 }
298
main(int argc,char * argv[])299 int main(int argc, char *argv[]) {
300 setenv("TREBLE_TESTING_OVERRIDE", "true", true);
301
302 enum HwBinderMode {
303 kBinderize = 0,
304 kPassthrough = 1,
305 };
306 HwBinderMode mode = HwBinderMode::kBinderize;
307
308 // Num of workers.
309 int workers = 2;
310 // Num of services.
311 int services = -1;
312 int iterations = 10000;
313
314 vector<Pipe> worker_pipes;
315 vector<Pipe> service_pipes;
316
317 // Parse arguments.
318 for (int i = 1; i < argc; i++) {
319 if (string(argv[i]) == "-m") {
320 if (!strcmp(argv[i + 1], "PASSTHROUGH")) {
321 mode = HwBinderMode::kPassthrough;
322 }
323 i++;
324 continue;
325 }
326 if (string(argv[i]) == "-w") {
327 workers = atoi(argv[i + 1]);
328 i++;
329 continue;
330 }
331 if (string(argv[i]) == "-i") {
332 iterations = atoi(argv[i + 1]);
333 i++;
334 continue;
335 }
336 if (string(argv[i]) == "-s") {
337 services = atoi(argv[i + 1]);
338 i++;
339 continue;
340 }
341 }
342 // If service number is not provided, set it the same as the worker number.
343 if (services == -1) {
344 services = workers;
345 }
346 if (mode == HwBinderMode::kBinderize) {
347 // Create services.
348 vector<pid_t> pIds;
349 for (int i = 0; i < services; i++) {
350 string serviceName = generateServiceName(i);
351 cout << "creating service: " << serviceName << endl;
352 service_pipes.push_back(make_service(serviceName));
353 }
354 // Wait until all services are up.
355 wait_all(service_pipes);
356 }
357
358 // Create workers (test clients).
359 bool get_stub = mode == HwBinderMode::kBinderize ? false : true;
360 for (int i = 0; i < workers; i++) {
361 worker_pipes.push_back(make_worker(i, iterations, services, get_stub));
362 }
363 // Wait untill all workers are ready.
364 wait_all(worker_pipes);
365
366 // Run the workers and wait for completion.
367 chrono::time_point<chrono::high_resolution_clock> start, end;
368 cout << "waiting for workers to complete" << endl;
369 start = chrono::high_resolution_clock::now();
370 signal_all(worker_pipes);
371 wait_all(worker_pipes);
372 end = chrono::high_resolution_clock::now();
373
374 // Calculate overall throughput.
375 double iterations_per_sec = double(iterations * workers)
376 / (chrono::duration_cast < chrono::nanoseconds
377 > (end - start).count() / 1.0E9);
378 cout << "iterations per sec: " << iterations_per_sec << endl;
379
380 // Collect all results from the workers.
381 cout << "collecting results" << endl;
382 signal_all(worker_pipes);
383 ProcResults tot_results;
384 for (int i = 0; i < workers; i++) {
385 ProcResults tmp_results;
386 worker_pipes[i].recv(tmp_results);
387 tot_results = ProcResults::combine(tot_results, tmp_results);
388 }
389 tot_results.dump();
390
391 if (mode == HwBinderMode::kBinderize) {
392 // Kill all the services.
393 cout << "killing services" << endl;
394 signal_all(service_pipes);
395 for (int i = 0; i < services; i++) {
396 int status;
397 wait(&status);
398 if (status != 0) {
399 cout << "nonzero child status" << status << endl;
400 }
401 }
402 }
403 // Kill all the workers.
404 cout << "killing workers" << endl;
405 signal_all(worker_pipes);
406 for (int i = 0; i < workers; i++) {
407 int status;
408 wait(&status);
409 if (status != 0) {
410 cout << "nonzero child status" << status << endl;
411 }
412 }
413 return 0;
414 }
415