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