You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
504 lines
14 KiB
504 lines
14 KiB
#include <binder/Binder.h>
|
|
#include <binder/IBinder.h>
|
|
#include <binder/IPCThreadState.h>
|
|
#include <binder/IServiceManager.h>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include <pthread.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <fstream>
|
|
|
|
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<sp<IBinder> > 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 <typename T>
|
|
void send(const T& v) {
|
|
int error = write(m_writeFd, &v, sizeof(T));
|
|
ASSERT(error >= 0);
|
|
}
|
|
template <typename T>
|
|
void recv(T& v) {
|
|
int error = read(m_readFd, &v, sizeof(T));
|
|
ASSERT(error >= 0);
|
|
}
|
|
static tuple<Pipe, Pipe> 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<chrono::high_resolution_clock> 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<chrono::nanoseconds>(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;
|
|
// TODO: 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);
|
|
ASSERT(ret == NO_ERROR);
|
|
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<IServiceManager> serviceMgr = defaultServiceManager();
|
|
sp<BinderWorkerService> 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);
|
|
// TODO: 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<Pipe>& v) {
|
|
for (size_t i = 0; i < v.size(); i++) {
|
|
v[i].wait();
|
|
}
|
|
}
|
|
|
|
void signal_all(vector<Pipe>& 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<Pipe> pipes;
|
|
thread_dump("main");
|
|
// TODO: 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;
|
|
}
|
|
// TODO: libjson?
|
|
cout << "\"inheritance\": " << (no_inherent == 0 ? "\"PASS\"" : "\"FAIL\"")
|
|
<< endl;
|
|
cout << "}" << endl;
|
|
return -no_inherent;
|
|
}
|