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