#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace android; enum BinderWorkerServiceCode { BINDER_NOP = IBinder::FIRST_CALL_TRANSACTION, }; #define ASSERT(cond) \ do { \ if (!(cond)) { \ cerr << __func__ << ":" << __LINE__ << " condition:" << #cond \ << " failed\n" \ << endl; \ exit(EXIT_FAILURE); \ } \ } while (0) vector > workers; // the ratio that the service is synced on the same cpu beyond // GOOD_SYNC_MIN is considered as good #define GOOD_SYNC_MIN (0.6) #define DUMP_PRESICION 2 string trace_path = "/sys/kernel/debug/tracing"; // the default value int no_process = 2; int iterations = 100; int payload_size = 16; int no_inherent = 0; int no_sync = 0; int verbose = 0; int trace; bool traceIsOn() { fstream file; file.open(trace_path + "/tracing_on", ios::in); char on; file >> on; file.close(); return on == '1'; } void traceStop() { ofstream file; file.open(trace_path + "/tracing_on", ios::out | ios::trunc); file << '0' << endl; file.close(); } // the deadline latency that we are interested in uint64_t deadline_us = 2500; int thread_pri() { struct sched_param param; int policy; ASSERT(!pthread_getschedparam(pthread_self(), &policy, ¶m)); return param.sched_priority; } void thread_dump(const char* prefix) { struct sched_param param; int policy; if (!verbose) return; cout << "--------------------------------------------------" << endl; cout << setw(12) << left << prefix << " pid: " << getpid() << " tid: " << gettid() << " cpu: " << sched_getcpu() << endl; ASSERT(!pthread_getschedparam(pthread_self(), &policy, ¶m)); string s = (policy == SCHED_OTHER) ? "SCHED_OTHER" : (policy == SCHED_FIFO) ? "SCHED_FIFO" : (policy == SCHED_RR) ? "SCHED_RR" : "???"; cout << setw(12) << left << s << param.sched_priority << endl; return; } class BinderWorkerService : public BBinder { public: BinderWorkerService() { } ~BinderWorkerService() { } virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) { (void)flags; (void)data; (void)reply; switch (code) { // The transaction format is like // // data[in]: int32: caller priority // int32: caller cpu // // reply[out]: int32: 1 if caller's priority != callee's priority // int32: 1 if caller's cpu != callee's cpu // // note the caller cpu read here is not always correct // there're still chances that the caller got switched out // right after it read the cpu number and still before the transaction. case BINDER_NOP: { thread_dump("binder"); int priority = thread_pri(); int priority_caller = data.readInt32(); int h = 0, s = 0; if (priority_caller != priority) { h++; if (verbose) { cout << "err priority_caller:" << priority_caller << ", priority:" << priority << endl; } } if (priority == sched_get_priority_max(SCHED_FIFO)) { int cpu = sched_getcpu(); int cpu_caller = data.readInt32(); if (cpu != cpu_caller) { s++; } } reply->writeInt32(h); reply->writeInt32(s); return NO_ERROR; } default: return UNKNOWN_TRANSACTION; }; } }; class Pipe { int m_readFd; int m_writeFd; Pipe(int readFd, int writeFd) : m_readFd{readFd}, m_writeFd{writeFd} { } Pipe(const Pipe&) = delete; Pipe& operator=(const Pipe&) = delete; Pipe& operator=(const Pipe&&) = delete; public: Pipe(Pipe&& rval) noexcept { m_readFd = rval.m_readFd; m_writeFd = rval.m_writeFd; rval.m_readFd = 0; rval.m_writeFd = 0; } ~Pipe() { if (m_readFd) close(m_readFd); if (m_writeFd) close(m_writeFd); } void signal() { bool val = true; int error = write(m_writeFd, &val, sizeof(val)); ASSERT(error >= 0); }; void wait() { bool val = false; int error = read(m_readFd, &val, sizeof(val)); ASSERT(error >= 0); } template void send(const T& v) { int error = write(m_writeFd, &v, sizeof(T)); ASSERT(error >= 0); } template void recv(T& v) { int error = read(m_readFd, &v, sizeof(T)); ASSERT(error >= 0); } static tuple createPipePair() { int a[2]; int b[2]; int error1 = pipe(a); int error2 = pipe(b); ASSERT(error1 >= 0); ASSERT(error2 >= 0); return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1])); } }; typedef chrono::time_point Tick; static inline Tick tickNow() { return chrono::high_resolution_clock::now(); } static inline uint64_t tickNano(Tick& sta, Tick& end) { return uint64_t(chrono::duration_cast(end - sta).count()); } struct Results { uint64_t m_best = 0xffffffffffffffffULL; uint64_t m_worst = 0; uint64_t m_transactions = 0; uint64_t m_total_time = 0; uint64_t m_miss = 0; bool tracing; explicit Results(bool _tracing) : tracing(_tracing) { } inline bool miss_deadline(uint64_t nano) { return nano > deadline_us * 1000; } void add_time(uint64_t nano) { m_best = min(nano, m_best); m_worst = max(nano, m_worst); m_transactions += 1; m_total_time += nano; if (miss_deadline(nano)) m_miss++; if (miss_deadline(nano) && tracing) { // There might be multiple process pair running the test concurrently // each may execute following statements and only the first one actually // stop the trace and any traceStop() afterthen has no effect. traceStop(); cout << endl; cout << "deadline triggered: halt & stop trace" << endl; cout << "log:" + trace_path + "/trace" << endl; cout << endl; exit(1); } } void dump() { double best = (double)m_best / 1.0E6; double worst = (double)m_worst / 1.0E6; double average = (double)m_total_time / m_transactions / 1.0E6; // FIXME: libjson? int W = DUMP_PRESICION + 2; cout << setprecision(DUMP_PRESICION) << "{ \"avg\":" << setw(W) << left << average << ",\"wst\":" << setw(W) << left << worst << ",\"bst\":" << setw(W) << left << best << ",\"miss\":" << left << m_miss << ",\"meetR\":" << left << setprecision(DUMP_PRESICION + 3) << (1.0 - (double)m_miss / m_transactions) << "}"; } }; String16 generateServiceName(int num) { char num_str[32]; snprintf(num_str, sizeof(num_str), "%d", num); String16 serviceName = String16("binderWorker") + String16(num_str); return serviceName; } static void parcel_fill(Parcel& data, int sz, int priority, int cpu) { ASSERT(sz >= (int)sizeof(uint32_t) * 2); data.writeInt32(priority); data.writeInt32(cpu); sz -= sizeof(uint32_t); while (sz > (int)sizeof(uint32_t)) { data.writeInt32(0); sz -= sizeof(uint32_t); } } typedef struct { void* result; int target; } thread_priv_t; static void* thread_start(void* p) { thread_priv_t* priv = (thread_priv_t*)p; int target = priv->target; Results* results_fifo = (Results*)priv->result; Parcel data, reply; Tick sta, end; parcel_fill(data, payload_size, thread_pri(), sched_getcpu()); thread_dump("fifo-caller"); sta = tickNow(); status_t ret = workers[target]->transact(BINDER_NOP, data, &reply); end = tickNow(); results_fifo->add_time(tickNano(sta, end)); no_inherent += reply.readInt32(); no_sync += reply.readInt32(); return nullptr; } // create a fifo thread to transact and wait it to finished static void thread_transaction(int target, Results* results_fifo) { thread_priv_t thread_priv; void* dummy; pthread_t thread; pthread_attr_t attr; struct sched_param param; thread_priv.target = target; thread_priv.result = results_fifo; ASSERT(!pthread_attr_init(&attr)); ASSERT(!pthread_attr_setschedpolicy(&attr, SCHED_FIFO)); param.sched_priority = sched_get_priority_max(SCHED_FIFO); ASSERT(!pthread_attr_setschedparam(&attr, ¶m)); ASSERT(!pthread_create(&thread, &attr, &thread_start, &thread_priv)); ASSERT(!pthread_join(thread, &dummy)); } #define is_client(_num) ((_num) >= (no_process / 2)) void worker_fx(int num, int no_process, int iterations, int payload_size, Pipe p) { int dummy; Results results_other(false), results_fifo(trace); // Create BinderWorkerService and for go. ProcessState::self()->startThreadPool(); sp serviceMgr = defaultServiceManager(); sp service = new BinderWorkerService; serviceMgr->addService(generateServiceName(num), service); // init done p.signal(); // wait for kick-off p.wait(); // If client/server pairs, then half the workers are // servers and half are clients int server_count = no_process / 2; for (int i = 0; i < server_count; i++) { // self service is in-process so just skip if (num == i) continue; workers.push_back(serviceMgr->getService(generateServiceName(i))); } // Client for each pair iterates here // each iterations contains exatcly 2 transactions for (int i = 0; is_client(num) && i < iterations; i++) { Parcel data, reply; Tick sta, end; // the target is paired to make it easier to diagnose int target = num % server_count; // 1. transaction by fifo thread thread_transaction(target, &results_fifo); parcel_fill(data, payload_size, thread_pri(), sched_getcpu()); thread_dump("other-caller"); // 2. transaction by other thread sta = tickNow(); ASSERT(NO_ERROR == workers[target]->transact(BINDER_NOP, data, &reply)); end = tickNow(); results_other.add_time(tickNano(sta, end)); no_inherent += reply.readInt32(); no_sync += reply.readInt32(); } // Signal completion to master and wait. p.signal(); p.wait(); p.send(&dummy); // wait for kill p.wait(); // Client for each pair dump here if (is_client(num)) { int no_trans = iterations * 2; double sync_ratio = (1.0 - (double)no_sync / no_trans); // FIXME: libjson? cout << "\"P" << (num - server_count) << "\":{\"SYNC\":\"" << ((sync_ratio > GOOD_SYNC_MIN) ? "GOOD" : "POOR") << "\"," << "\"S\":" << (no_trans - no_sync) << ",\"I\":" << no_trans << "," << "\"R\":" << sync_ratio << "," << endl; cout << " \"other_ms\":"; results_other.dump(); cout << "," << endl; cout << " \"fifo_ms\": "; results_fifo.dump(); cout << endl; cout << "}," << endl; } exit(no_inherent); } Pipe make_process(int num, int iterations, int no_process, int payload_size) { auto pipe_pair = Pipe::createPipePair(); pid_t pid = fork(); if (pid) { // parent return move(get<0>(pipe_pair)); } else { // child thread_dump(is_client(num) ? "client" : "server"); worker_fx(num, no_process, iterations, payload_size, move(get<1>(pipe_pair))); // never get here return move(get<0>(pipe_pair)); } } void wait_all(vector& v) { for (size_t i = 0; i < v.size(); i++) { v[i].wait(); } } void signal_all(vector& v) { for (size_t i = 0; i < v.size(); i++) { v[i].signal(); } } // This test is modified from binderThroughputTest.cpp int main(int argc, char** argv) { for (int i = 1; i < argc; i++) { if (string(argv[i]) == "-i") { iterations = atoi(argv[i + 1]); i++; continue; } if (string(argv[i]) == "-pair") { no_process = 2 * atoi(argv[i + 1]); i++; continue; } if (string(argv[i]) == "-deadline_us") { deadline_us = atoi(argv[i + 1]); i++; continue; } if (string(argv[i]) == "-v") { verbose = 1; } // The -trace argument is used like that: // // First start trace with atrace command as usual // >atrace --async_start sched freq // // then use schd-dbg with -trace arguments //./schd-dbg -trace -deadline_us 2500 // // This makes schd-dbg to stop trace once it detects a transaction // duration over the deadline. By writing '0' to // /sys/kernel/debug/tracing and halt the process. The tracelog is // then available on /sys/kernel/debug/trace if (string(argv[i]) == "-trace") { trace = 1; } } if (trace && !traceIsOn()) { cout << "trace is not running" << endl; cout << "check " << trace_path + "/tracing_on" << endl; cout << "use atrace --async_start first" << endl; exit(-1); } vector pipes; thread_dump("main"); // FIXME: libjson? cout << "{" << endl; cout << "\"cfg\":{\"pair\":" << (no_process / 2) << ",\"iterations\":" << iterations << ",\"deadline_us\":" << deadline_us << "}," << endl; // the main process fork 2 processes for each pairs // 1 server + 1 client // each has a pipe to communicate with for (int i = 0; i < no_process; i++) { pipes.push_back(make_process(i, iterations, no_process, payload_size)); } // wait for init done wait_all(pipes); // kick-off iterations signal_all(pipes); // wait for completion wait_all(pipes); // start to send result signal_all(pipes); for (int i = 0; i < no_process; i++) { int status; // kill pipes[i].signal(); wait(&status); // the exit status is number of transactions without priority inheritance // detected in the child process no_inherent += status; } // FIXME: libjson? cout << "\"inheritance\": " << (no_inherent == 0 ? "\"PASS\"" : "\"FAIL\"") << endl; cout << "}" << endl; return -no_inherent; }