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 #include <android/hardware/tests/libhwbinder/1.0/IScheduleTest.h>
17 #include <cstdio>
18 #include <cstdlib>
19 #include <cstring>
20 #include <fstream>
21 #include <iomanip>
22 #include <iostream>
23 #include <string>
24 #include <tuple>
25 #include <vector>
26
27 #include <pthread.h>
28 #include <sys/wait.h>
29 #include <unistd.h>
30
31 using namespace std;
32 using namespace android;
33 using namespace android::hardware;
34
35 using android::hardware::tests::libhwbinder::V1_0::IScheduleTest;
36
37 #define ASSERT(cond) \
38 do { \
39 if (!(cond)) { \
40 cerr << __func__ << ":" << __LINE__ << " condition:" << #cond \
41 << " failed\n" \
42 << endl; \
43 exit(EXIT_FAILURE); \
44 } \
45 } while (0)
46
47 vector<sp<IScheduleTest> > services;
48
49 // the ratio that the service is synced on the same cpu beyond
50 // GOOD_SYNC_MIN is considered as good
51 #define GOOD_SYNC_MIN (0.6)
52
53 #define DUMP_PRICISION 2
54
55 string trace_path = "/sys/kernel/debug/tracing";
56
57 // the default value
58 int no_pair = 1;
59 int iterations = 100;
60 int no_inherent = 0;
61 int no_sync = 0;
62 int verbose = 0;
63 int trace;
64 bool pass_through = false;
65
traceIsOn()66 static bool traceIsOn() {
67 fstream file;
68 file.open(trace_path + "/tracing_on", ios::in);
69 char on;
70 file >> on;
71 file.close();
72 return on == '1';
73 }
74
traceStop()75 static void traceStop() {
76 ofstream file;
77 file.open(trace_path + "/tracing_on", ios::out | ios::trunc);
78 file << '0' << endl;
79 file.close();
80 }
81
82 // the deadline latency that we are interested in
83 uint64_t deadline_us = 2500;
84
threadPri()85 static int threadPri() {
86 struct sched_param param;
87 int policy;
88 ASSERT(!pthread_getschedparam(pthread_self(), &policy, ¶m));
89 return param.sched_priority;
90 }
91
threadDump(const char * prefix)92 static void threadDump(const char* prefix) {
93 struct sched_param param;
94 int policy;
95 if (!verbose) return;
96 cout << "--------------------------------------------------" << endl;
97 cout << setw(12) << left << prefix << " pid: " << getpid()
98 << " tid: " << gettid() << " cpu: " << sched_getcpu() << endl;
99 ASSERT(!pthread_getschedparam(pthread_self(), &policy, ¶m));
100 string s = (policy == SCHED_OTHER)
101 ? "SCHED_OTHER"
102 : (policy == SCHED_FIFO)
103 ? "SCHED_FIFO"
104 : (policy == SCHED_RR) ? "SCHED_RR" : "???";
105 cout << setw(12) << left << s << param.sched_priority << endl;
106 return;
107 }
108
109 // This IPC class is widely used in binder/hwbinder tests.
110 // The common usage is the main process to create the Pipe and forks.
111 // Both parent and child hold a object and each wait() on parent
112 // needs a signal() on the child to wake up and vice versa.
113 class Pipe {
114 int m_readFd;
115 int m_writeFd;
Pipe(int readFd,int writeFd)116 Pipe(int readFd, int writeFd) : m_readFd{readFd}, m_writeFd{writeFd} {}
117 Pipe(const Pipe&) = delete;
118 Pipe& operator=(const Pipe&) = delete;
119 Pipe& operator=(const Pipe&&) = delete;
120
121 public:
Pipe(Pipe && rval)122 Pipe(Pipe&& rval) noexcept {
123 m_readFd = rval.m_readFd;
124 m_writeFd = rval.m_writeFd;
125 rval.m_readFd = 0;
126 rval.m_writeFd = 0;
127 }
~Pipe()128 ~Pipe() {
129 if (m_readFd) close(m_readFd);
130 if (m_writeFd) close(m_writeFd);
131 }
signal()132 void signal() {
133 bool val = true;
134 int error = write(m_writeFd, &val, sizeof(val));
135 ASSERT(error >= 0);
136 };
wait()137 void wait() {
138 bool val = false;
139 int error = read(m_readFd, &val, sizeof(val));
140 ASSERT(error >= 0);
141 }
142 template <typename T>
send(const T & v)143 void send(const T& v) {
144 int error = write(m_writeFd, &v, sizeof(T));
145 ASSERT(error >= 0);
146 }
147 template <typename T>
recv(T & v)148 void recv(T& v) {
149 int error = read(m_readFd, &v, sizeof(T));
150 ASSERT(error >= 0);
151 }
createPipePair()152 static tuple<Pipe, Pipe> createPipePair() {
153 int a[2];
154 int b[2];
155
156 int error1 = pipe(a);
157 int error2 = pipe(b);
158 ASSERT(error1 >= 0);
159 ASSERT(error2 >= 0);
160
161 return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1]));
162 }
163 };
164
165 typedef chrono::time_point<chrono::high_resolution_clock> Tick;
166
tickNow()167 static inline Tick tickNow() { return chrono::high_resolution_clock::now(); }
168
tickNano(Tick & sta,Tick & end)169 static inline uint64_t tickNano(Tick& sta, Tick& end) {
170 return uint64_t(
171 chrono::duration_cast<chrono::nanoseconds>(end - sta).count());
172 }
173
174 struct Results {
175 uint64_t m_best = 0xffffffffffffffffULL;
176 uint64_t m_worst = 0;
177 uint64_t m_transactions = 0;
178 uint64_t m_total_time = 0;
179 uint64_t m_miss = 0;
180 bool tracing;
ResultsResults181 Results(bool _tracing) : tracing(_tracing) {}
miss_deadlineResults182 inline bool miss_deadline(uint64_t nano) { return nano > deadline_us * 1000; }
add_timeResults183 void add_time(uint64_t nano) {
184 m_best = min(nano, m_best);
185 m_worst = max(nano, m_worst);
186 m_transactions += 1;
187 m_total_time += nano;
188 if (miss_deadline(nano)) m_miss++;
189 if (miss_deadline(nano) && tracing) {
190 // There might be multiple process pair running the test concurrently
191 // each may execute following statements and only the first one actually
192 // stop the trace and any traceStop() afterthen has no effect.
193 traceStop();
194 cout << endl;
195 cout << "deadline triggered: halt & stop trace" << endl;
196 cout << "log:" + trace_path + "/trace" << endl;
197 cout << endl;
198 exit(1);
199 }
200 }
dumpResults201 void dump() {
202 double best = (double)m_best / 1.0E6;
203 double worst = (double)m_worst / 1.0E6;
204 double average = (double)m_total_time / m_transactions / 1.0E6;
205 int W = DUMP_PRICISION + 2;
206 cout << std::setprecision(DUMP_PRICISION) << "{ \"avg\":" << setw(W) << left
207 << average << ", \"wst\":" << setw(W) << left << worst
208 << ", \"bst\":" << setw(W) << left << best << ", \"miss\":" << left
209 << m_miss << ", \"meetR\":" << setprecision(DUMP_PRICISION + 3) << left
210 << (1.0 - (double)m_miss / m_transactions) << "}";
211 }
212 };
213
generateServiceName(int num)214 static string generateServiceName(int num) {
215 string serviceName = "hwbinderService" + to_string(num);
216 return serviceName;
217 }
218
219 // This private struct is used to pass the argument to threadStart
220 // result: a pointer to Results
221 // target: the terget service number
222 typedef struct {
223 void* result;
224 int target;
225 } thread_priv_t;
226
threadStart(void * p)227 static void* threadStart(void* p) {
228 thread_priv_t* priv = (thread_priv_t*)p;
229 int target = priv->target;
230 Results* results_fifo = (Results*)priv->result;
231 Tick sta, end;
232
233 threadDump("fifo-caller");
234 uint32_t call_sta = (threadPri() << 16) | sched_getcpu();
235 sp<IScheduleTest> service = services[target];
236 sta = tickNow();
237 uint32_t ret = service->send(verbose, call_sta);
238 end = tickNow();
239 results_fifo->add_time(tickNano(sta, end));
240
241 no_inherent += (ret >> 16) & 0xffff;
242 no_sync += ret & 0xffff;
243 return 0;
244 }
245
246 // create a fifo thread to transact and wait it to finished
threadTransaction(int target,Results * results_fifo)247 static void threadTransaction(int target, Results* results_fifo) {
248 thread_priv_t thread_priv;
249
250 void* dummy;
251 pthread_t thread;
252 pthread_attr_t attr;
253 struct sched_param param;
254 thread_priv.target = target;
255 thread_priv.result = results_fifo;
256 ASSERT(!pthread_attr_init(&attr));
257 ASSERT(!pthread_attr_setschedpolicy(&attr, SCHED_FIFO));
258 param.sched_priority = sched_get_priority_max(SCHED_FIFO);
259 ASSERT(!pthread_attr_setschedparam(&attr, ¶m));
260 ASSERT(!pthread_create(&thread, &attr, threadStart, &thread_priv));
261 ASSERT(!pthread_join(thread, &dummy));
262 }
263
serviceFx(const string & serviceName,Pipe p)264 static void serviceFx(const string& serviceName, Pipe p) {
265 // Start service.
266 sp<IScheduleTest> server = IScheduleTest::getService(serviceName, true);
267 status_t status = server->registerAsService(serviceName);
268 if (status != ::android::OK) {
269 cout << "Failed to register service " << serviceName.c_str() << endl;
270 exit(EXIT_FAILURE);
271 }
272 // tell main I'm init-ed
273 p.signal();
274 // wait for kill
275 p.wait();
276 exit(EXIT_SUCCESS);
277 }
278
makeServiceProces(string service_name)279 static Pipe makeServiceProces(string service_name) {
280 auto pipe_pair = Pipe::createPipePair();
281 pid_t pid = fork();
282 if (pid) {
283 // parent
284 return move(get<0>(pipe_pair));
285 } else {
286 threadDump("service");
287 // child
288 serviceFx(service_name, move(get<1>(pipe_pair)));
289 // never get here
290 ASSERT(0);
291 return move(get<0>(pipe_pair));
292 }
293 }
294
clientFx(int num,int server_count,int iterations,Pipe p)295 static void clientFx(int num, int server_count, int iterations, Pipe p) {
296 Results results_other(false), results_fifo(trace);
297
298 for (int i = 0; i < server_count; i++) {
299 sp<IScheduleTest> service =
300 IScheduleTest::getService(generateServiceName(i), pass_through);
301 ASSERT(service != nullptr);
302 if (pass_through) {
303 ASSERT(!service->isRemote());
304 } else {
305 ASSERT(service->isRemote());
306 }
307 services.push_back(service);
308 }
309 // tell main I'm init-ed
310 p.signal();
311 // wait for kick-off
312 p.wait();
313
314 // Client for each pair iterates here
315 // each iterations contains exatcly 2 transactions
316 for (int i = 0; i < iterations; i++) {
317 Tick sta, end;
318 // the target is paired to make it easier to diagnose
319 int target = num;
320
321 // 1. transaction by fifo thread
322 threadTransaction(target, &results_fifo);
323 threadDump("other-caller");
324
325 uint32_t call_sta = (threadPri() << 16) | sched_getcpu();
326 sp<IScheduleTest> service = services[target];
327 // 2. transaction by other thread
328 sta = tickNow();
329 uint32_t ret = service->send(verbose, call_sta);
330 end = tickNow();
331 results_other.add_time(tickNano(sta, end));
332 no_inherent += (ret >> 16) & 0xffff;
333 no_sync += ret & 0xffff;
334 }
335 // tell main i'm done
336 p.signal();
337 // wait for kill
338 p.wait();
339 // Client for each pair dump here
340 int no_trans = iterations * 2;
341 double sync_ratio = (1.0 - (double)no_sync / no_trans);
342 cout << "\"P" << num << "\":{\"SYNC\":\""
343 << ((sync_ratio > GOOD_SYNC_MIN) ? "GOOD" : "POOR") << "\","
344 << "\"S\":" << (no_trans - no_sync) << ",\"I\":" << no_trans << ","
345 << "\"R\":" << sync_ratio << "," << endl;
346
347 cout << " \"other_ms\":";
348 results_other.dump();
349 cout << "," << endl;
350 cout << " \"fifo_ms\": ";
351 results_fifo.dump();
352 cout << endl;
353 cout << "}," << endl;
354 exit(no_inherent);
355 }
356
makeClientProcess(int num,int iterations,int no_pair)357 static Pipe makeClientProcess(int num, int iterations, int no_pair) {
358 auto pipe_pair = Pipe::createPipePair();
359 pid_t pid = fork();
360 if (pid) {
361 // parent
362 return move(get<0>(pipe_pair));
363 } else {
364 // child
365 threadDump("client");
366 clientFx(num, no_pair, iterations, move(get<1>(pipe_pair)));
367 // never get here
368 ASSERT(0);
369 return move(get<0>(pipe_pair));
370 }
371 }
372
waitAll(vector<Pipe> & v)373 static void waitAll(vector<Pipe>& v) {
374 for (size_t i = 0; i < v.size(); i++) {
375 v[i].wait();
376 }
377 }
378
signalAll(vector<Pipe> & v)379 static void signalAll(vector<Pipe>& v) {
380 for (size_t i = 0; i < v.size(); i++) {
381 v[i].signal();
382 }
383 }
384
385 // This test is modified from frameworks/native/libs/binder/tests/sch-dbg.cpp
386 // The difference is sch-dbg tests binder transaction and this one test
387 // HwBinder transaction.
main(int argc,char ** argv)388 int main(int argc, char** argv) {
389 setenv("TREBLE_TESTING_OVERRIDE", "true", true);
390
391 vector<Pipe> client_pipes;
392 vector<Pipe> service_pipes;
393
394 for (int i = 1; i < argc; i++) {
395 if (string(argv[i]) == "-passthrough") {
396 pass_through = true;
397 }
398 if (string(argv[i]) == "-i") {
399 iterations = atoi(argv[i + 1]);
400 i++;
401 continue;
402 }
403 if (string(argv[i]) == "-pair") {
404 no_pair = atoi(argv[i + 1]);
405 i++;
406 continue;
407 }
408 if (string(argv[i]) == "-deadline_us") {
409 deadline_us = atoi(argv[i + 1]);
410 i++;
411 continue;
412 }
413 if (string(argv[i]) == "-v") {
414 verbose = 1;
415 }
416 // The -trace argument is used like that:
417 //
418 // First start trace with atrace command as usual
419 // >atrace --async_start sched freq
420 //
421 // then use the -trace arguments like
422 // -trace -deadline_us 2500
423 //
424 // This makes the program to stop trace once it detects a transaction
425 // duration over the deadline. By writing '0' to
426 // /sys/kernel/debug/tracing and halt the process. The tracelog is
427 // then available on /sys/kernel/debug/trace
428 if (string(argv[i]) == "-trace") {
429 trace = 1;
430 }
431 }
432 if (!pass_through) {
433 // Create services.
434 for (int i = 0; i < no_pair; i++) {
435 string serviceName = generateServiceName(i);
436 service_pipes.push_back(makeServiceProces(serviceName));
437 }
438 // Wait until all services are up.
439 waitAll(service_pipes);
440 }
441 if (trace && !traceIsOn()) {
442 cout << "trace is not running" << endl;
443 cout << "check " << trace_path + "/tracing_on" << endl;
444 cout << "use atrace --async_start first" << endl;
445 exit(-1);
446 }
447 threadDump("main");
448 cout << "{" << endl;
449 cout << "\"cfg\":{\"pair\":" << (no_pair) << ",\"iterations\":" << iterations
450 << ",\"deadline_us\":" << deadline_us << "}," << endl;
451
452 // the main process fork 2 processes for each pairs
453 // 1 server + 1 client
454 // each has a pipe to communicate with
455 for (int i = 0; i < no_pair; i++) {
456 client_pipes.push_back(makeClientProcess(i, iterations, no_pair));
457 }
458 // wait client to init
459 waitAll(client_pipes);
460
461 // kick off clients
462 signalAll(client_pipes);
463
464 // wait client to finished
465 waitAll(client_pipes);
466
467 if (!pass_through) {
468 // Kill all the services.
469 for (int i = 0; i < no_pair; i++) {
470 int status;
471 service_pipes[i].signal();
472 wait(&status);
473 if (status != 0) {
474 cout << "nonzero child status" << status << endl;
475 }
476 }
477 }
478 for (int i = 0; i < no_pair; i++) {
479 int status;
480 client_pipes[i].signal();
481 wait(&status);
482 // the exit status is number of transactions without priority inheritance
483 // detected in the child process
484 no_inherent += status;
485 }
486 cout << "\"inheritance\": " << (no_inherent == 0 ? "\"PASS\"" : "\"FAIL\"")
487 << endl;
488 cout << "}" << endl;
489 return -no_inherent;
490 }
491