Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8f93ab17 authored by Martijn Coenen's avatar Martijn Coenen Committed by Gerrit Code Review
Browse files

Merge changes from topic 'binder_tests_aosp'

* changes:
  Add test to check buffer hdr not in user space
  Add test for death notification workqueue.
  Fix percentile calculations
  Refine the binder latency test
  Add options to specify payload size and use client/server pairs
  Add binder test for bug 30445380
  binder: tests: Check that cookie and binder high bits are zero
  binder: tests: Fix warnings
parents 920514db 336cdd34
Loading
Loading
Loading
Loading
+190 −3
Original line number Diff line number Diff line
@@ -32,6 +32,13 @@

using namespace android;

static ::testing::AssertionResult IsPageAligned(void *buf) {
    if (((unsigned long)buf & ((unsigned long)PAGE_SIZE - 1)) == 0)
        return ::testing::AssertionSuccess();
    else
        return ::testing::AssertionFailure() << buf << " is not page aligned";
}

static testing::Environment* binder_env;
static char *binderservername;
static char *binderserversuffix;
@@ -44,7 +51,9 @@ enum BinderLibTestTranscationCode {
    BINDER_LIB_TEST_REGISTER_SERVER,
    BINDER_LIB_TEST_ADD_SERVER,
    BINDER_LIB_TEST_CALL_BACK,
    BINDER_LIB_TEST_CALL_BACK_VERIFY_BUF,
    BINDER_LIB_TEST_NOP_CALL_BACK,
    BINDER_LIB_TEST_GET_SELF_TRANSACTION,
    BINDER_LIB_TEST_GET_ID_TRANSACTION,
    BINDER_LIB_TEST_INDIRECT_TRANSACTION,
    BINDER_LIB_TEST_SET_ERROR_TRANSACTION,
@@ -56,6 +65,7 @@ enum BinderLibTestTranscationCode {
    BINDER_LIB_TEST_EXIT_TRANSACTION,
    BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION,
    BINDER_LIB_TEST_GET_PTR_SIZE_TRANSACTION,
    BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION,
};

pid_t start_server_process(int arg2)
@@ -263,17 +273,23 @@ class BinderLibTestEvent
            pthread_mutex_unlock(&m_waitMutex);
            return ret;
        }
        pthread_t getTriggeringThread()
        {
            return m_triggeringThread;
        }
    protected:
        void triggerEvent(void) {
            pthread_mutex_lock(&m_waitMutex);
            pthread_cond_signal(&m_waitCond);
            m_eventTriggered = true;
            m_triggeringThread = pthread_self();
            pthread_mutex_unlock(&m_waitMutex);
        };
    private:
        pthread_mutex_t m_waitMutex;
        pthread_cond_t m_waitCond;
        bool m_eventTriggered;
        pthread_t m_triggeringThread;
};

class BinderLibTestCallBack : public BBinder, public BinderLibTestEvent
@@ -281,6 +297,7 @@ class BinderLibTestCallBack : public BBinder, public BinderLibTestEvent
    public:
        BinderLibTestCallBack()
            : m_result(NOT_ENOUGH_DATA)
            , m_prev_end(NULL)
        {
        }
        status_t getResult(void)
@@ -300,12 +317,35 @@ class BinderLibTestCallBack : public BBinder, public BinderLibTestEvent
                m_result = data.readInt32();
                triggerEvent();
                return NO_ERROR;
            case BINDER_LIB_TEST_CALL_BACK_VERIFY_BUF: {
                sp<IBinder> server;
                int ret;
                const uint8_t *buf = data.data();
                size_t size = data.dataSize();
                if (m_prev_end) {
                    /* 64-bit kernel needs at most 8 bytes to align buffer end */
                    EXPECT_LE((size_t)(buf - m_prev_end), (size_t)8);
                } else {
                    EXPECT_TRUE(IsPageAligned((void *)buf));
                }

                m_prev_end = buf + size + data.objectsCount() * sizeof(binder_size_t);

                if (size > 0) {
                    server = static_cast<BinderLibTestEnv *>(binder_env)->getServer();
                    ret = server->transact(BINDER_LIB_TEST_INDIRECT_TRANSACTION,
                                           data, reply);
                    EXPECT_EQ(NO_ERROR, ret);
                }
                return NO_ERROR;
            }
            default:
                return UNKNOWN_TRANSACTION;
            }
        }

        status_t m_result;
        const uint8_t *m_prev_end;
};

class TestDeathRecipient : public IBinder::DeathRecipient, public BinderLibTestEvent
@@ -389,7 +429,7 @@ TEST_F(BinderLibTest, IndirectGetId2)

    ret = reply.readInt32(&count);
    ASSERT_EQ(NO_ERROR, ret);
    EXPECT_EQ(ARRAY_SIZE(serverId), count);
    EXPECT_EQ(ARRAY_SIZE(serverId), (size_t)count);

    for (size_t i = 0; i < (size_t)count; i++) {
        BinderLibTestBundle replyi(&reply);
@@ -439,7 +479,7 @@ TEST_F(BinderLibTest, IndirectGetId3)

    ret = reply.readInt32(&count);
    ASSERT_EQ(NO_ERROR, ret);
    EXPECT_EQ(ARRAY_SIZE(serverId), count);
    EXPECT_EQ(ARRAY_SIZE(serverId), (size_t)count);

    for (size_t i = 0; i < (size_t)count; i++) {
        int32_t counti;
@@ -604,6 +644,65 @@ TEST_F(BinderLibTest, DeathNotificationMultiple)
    }
}

TEST_F(BinderLibTest, DeathNotificationThread)
{
    status_t ret;
    sp<BinderLibTestCallBack> callback;
    sp<IBinder> target = addServer();
    ASSERT_TRUE(target != NULL);
    sp<IBinder> client = addServer();
    ASSERT_TRUE(client != NULL);

    sp<TestDeathRecipient> testDeathRecipient = new TestDeathRecipient();

    ret = target->linkToDeath(testDeathRecipient);
    EXPECT_EQ(NO_ERROR, ret);

    {
        Parcel data, reply;
        ret = target->transact(BINDER_LIB_TEST_EXIT_TRANSACTION, data, &reply, TF_ONE_WAY);
        EXPECT_EQ(0, ret);
    }

    /* Make sure it's dead */
    testDeathRecipient->waitEvent(5);

    /* Now, pass the ref to another process and ask that process to
     * call linkToDeath() on it, and wait for a response. This tests
     * two things:
     * 1) You still get death notifications when calling linkToDeath()
     *    on a ref that is already dead when it was passed to you.
     * 2) That death notifications are not directly pushed to the thread
     *    registering them, but to the threadpool (proc workqueue) instead.
     *
     * 2) is tested because the thread handling BINDER_LIB_TEST_DEATH_TRANSACTION
     * is blocked on a condition variable waiting for the death notification to be
     * called; therefore, that thread is not available for handling proc work.
     * So, if the death notification was pushed to the thread workqueue, the callback
     * would never be called, and the test would timeout and fail.
     *
     * Note that we can't do this part of the test from this thread itself, because
     * the binder driver would only push death notifications to the thread if
     * it is a looper thread, which this thread is not.
     *
     * See b/23525545 for details.
     */
    {
        Parcel data, reply;

        callback = new BinderLibTestCallBack();
        data.writeStrongBinder(target);
        data.writeStrongBinder(callback);
        ret = client->transact(BINDER_LIB_TEST_LINK_DEATH_TRANSACTION, data, &reply, TF_ONE_WAY);
        EXPECT_EQ(NO_ERROR, ret);
    }

    ret = callback->waitEvent(5);
    EXPECT_EQ(NO_ERROR, ret);
    ret = callback->getResult();
    EXPECT_EQ(NO_ERROR, ret);
}

TEST_F(BinderLibTest, PassFile) {
    int ret;
    int pipefd[2];
@@ -631,7 +730,7 @@ TEST_F(BinderLibTest, PassFile) {
    }

    ret = read(pipefd[0], buf, sizeof(buf));
    EXPECT_EQ(sizeof(buf), ret);
    EXPECT_EQ(sizeof(buf), (size_t)ret);
    EXPECT_EQ(write_value, buf[0]);

    waitForReadData(pipefd[0], 5000); /* wait for other proccess to close pipe */
@@ -670,6 +769,81 @@ TEST_F(BinderLibTest, PromoteRemote) {
    EXPECT_GE(ret, 0);
}

TEST_F(BinderLibTest, CheckHandleZeroBinderHighBitsZeroCookie) {
    status_t ret;
    Parcel data, reply;

    ret = m_server->transact(BINDER_LIB_TEST_GET_SELF_TRANSACTION, data, &reply);
    EXPECT_EQ(NO_ERROR, ret);

    const flat_binder_object *fb = reply.readObject(false);
    ASSERT_TRUE(fb != NULL);
    EXPECT_EQ(fb->hdr.type, BINDER_TYPE_HANDLE);
    EXPECT_EQ(ProcessState::self()->getStrongProxyForHandle(fb->handle), m_server);
    EXPECT_EQ(fb->cookie, (binder_uintptr_t)0);
    EXPECT_EQ(fb->binder >> 32, (binder_uintptr_t)0);
}

TEST_F(BinderLibTest, FreedBinder) {
    status_t ret;

    sp<IBinder> server = addServer();
    ASSERT_TRUE(server != NULL);

    __u32 freedHandle;
    wp<IBinder> keepFreedBinder;
    {
        Parcel data, reply;
        data.writeBool(false); /* request weak reference */
        ret = server->transact(BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION, data, &reply);
        ASSERT_EQ(NO_ERROR, ret);
        struct flat_binder_object *freed = (struct flat_binder_object *)(reply.data());
        freedHandle = freed->handle;
        /* Add a weak ref to the freed binder so the driver does not
         * delete its reference to it - otherwise the transaction
         * fails regardless of whether the driver is fixed.
         */
        keepFreedBinder = reply.readWeakBinder();
    }
    {
        Parcel data, reply;
        data.writeStrongBinder(server);
        /* Replace original handle with handle to the freed binder */
        struct flat_binder_object *strong = (struct flat_binder_object *)(data.data());
        __u32 oldHandle = strong->handle;
        strong->handle = freedHandle;
        ret = server->transact(BINDER_LIB_TEST_ADD_STRONG_REF_TRANSACTION, data, &reply);
        /* Returns DEAD_OBJECT (-32) if target crashes and
         * FAILED_TRANSACTION if the driver rejects the invalid
         * object.
         */
        EXPECT_EQ((status_t)FAILED_TRANSACTION, ret);
        /* Restore original handle so parcel destructor does not use
         * the wrong handle.
         */
        strong->handle = oldHandle;
    }
}

TEST_F(BinderLibTest, CheckNoHeaderMappedInUser) {
    status_t ret;
    Parcel data, reply;
    sp<BinderLibTestCallBack> callBack = new BinderLibTestCallBack();
    for (int i = 0; i < 2; i++) {
        BinderLibTestBundle datai;
        datai.appendFrom(&data, 0, data.dataSize());

        data.freeData();
        data.writeInt32(1);
        data.writeStrongBinder(callBack);
        data.writeInt32(BINDER_LIB_TEST_CALL_BACK_VERIFY_BUF);

        datai.appendTo(&data);
    }
    ret = m_server->transact(BINDER_LIB_TEST_INDIRECT_TRANSACTION, data, &reply);
    EXPECT_EQ(NO_ERROR, ret);
}

class BinderLibTestService : public BBinder
{
    public:
@@ -771,6 +945,9 @@ class BinderLibTestService : public BBinder
                binder->transact(BINDER_LIB_TEST_CALL_BACK, data2, &reply2);
                return NO_ERROR;
            }
            case BINDER_LIB_TEST_GET_SELF_TRANSACTION:
                reply->writeStrongBinder(this);
                return NO_ERROR;
            case BINDER_LIB_TEST_GET_ID_TRANSACTION:
                reply->writeInt32(m_id);
                return NO_ERROR;
@@ -884,6 +1061,16 @@ class BinderLibTestService : public BBinder
                while (wait(NULL) != -1 || errno != ECHILD)
                    ;
                exit(EXIT_SUCCESS);
            case BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION: {
                bool strongRef = data.readBool();
                sp<IBinder> binder = new BBinder();
                if (strongRef) {
                    reply->writeStrongBinder(binder);
                } else {
                    reply->writeWeakBinder(binder);
                }
                return NO_ERROR;
            }
            default:
                return UNKNOWN_TRANSACTION;
            };
+122 −39
Original line number Diff line number Diff line
@@ -101,18 +101,21 @@ public:
};

static const uint32_t num_buckets = 128;
static const uint64_t max_time_bucket = 50ull * 1000000;
static const uint64_t time_per_bucket = max_time_bucket / num_buckets;
static constexpr float time_per_bucket_ms = time_per_bucket / 1.0E6;
static uint64_t max_time_bucket = 50ull * 1000000;
static uint64_t time_per_bucket = max_time_bucket / num_buckets;

struct ProcResults {
    uint64_t m_best = max_time_bucket;
    uint64_t m_worst = 0;
    uint32_t m_buckets[num_buckets] = {0};
    uint64_t m_transactions = 0;
    uint64_t m_long_transactions = 0;
    uint64_t m_total_time = 0;
    uint64_t m_best = max_time_bucket;

    void add_time(uint64_t time) {
        if (time > max_time_bucket) {
            m_long_transactions++;
        }
        m_buckets[min(time, max_time_bucket-1) / time_per_bucket] += 1;
        m_best = min(time, m_best);
        m_worst = max(time, m_worst);
@@ -127,16 +130,24 @@ struct ProcResults {
        ret.m_worst = max(a.m_worst, b.m_worst);
        ret.m_best = min(a.m_best, b.m_best);
        ret.m_transactions = a.m_transactions + b.m_transactions;
        ret.m_long_transactions = a.m_long_transactions + b.m_long_transactions;
        ret.m_total_time = a.m_total_time + b.m_total_time;
        return ret;
    }
    void dump() {
        if (m_long_transactions > 0) {
            cout << (double)m_long_transactions / m_transactions << "% of transactions took longer "
                "than estimated max latency. Consider setting -m to be higher than "
                 << m_worst / 1000 << " microseconds" << endl;
        }

        double best = (double)m_best / 1.0E6;
        double worst = (double)m_worst / 1.0E6;
        double average = (double)m_total_time / m_transactions / 1.0E6;
        cout << "average:" << average << "ms worst:" << worst << "ms best:" << best << "ms" << endl;

        uint64_t cur_total = 0;
        float time_per_bucket_ms = time_per_bucket / 1.0E6;
        for (int i = 0; i < num_buckets; i++) {
            float cur_time = time_per_bucket_ms * i + 0.5f * time_per_bucket_ms;
            if ((cur_total < 0.5f * m_transactions) && (cur_total + m_buckets[i] >= 0.5f * m_transactions)) {
@@ -154,7 +165,6 @@ struct ProcResults {
            cur_total += m_buckets[i];
        }
        cout << endl;

    }
};

@@ -166,10 +176,11 @@ String16 generateServiceName(int num)
    return serviceName;
}

void worker_fx(
    int num,
void worker_fx(int num,
               int worker_count,
               int iterations,
               int payload_size,
               bool cs_pair,
               Pipe p)
{
    // Create BinderWorkerService and for go.
@@ -182,22 +193,32 @@ void worker_fx(
    p.signal();
    p.wait();

    // If client/server pairs, then half the workers are
    // servers and half are clients
    int server_count = cs_pair ? worker_count / 2 : worker_count;

    // Get references to other binder services.
    cout << "Created BinderWorker" << num << endl;
    (void)worker_count;
    vector<sp<IBinder> > workers;
    for (int i = 0; i < worker_count; i++) {
    for (int i = 0; i < server_count; i++) {
        if (num == i)
            continue;
        workers.push_back(serviceMgr->getService(generateServiceName(i)));
    }

    // Run the benchmark.
    // Run the benchmark if client
    ProcResults results;
    chrono::time_point<chrono::high_resolution_clock> start, end;
    for (int i = 0; i < iterations; i++) {
        int target = rand() % workers.size();
    for (int i = 0; (!cs_pair || num >= server_count) && i < iterations; i++) {
        Parcel data, reply;
        int target = cs_pair ? num % server_count : rand() % workers.size();
        int sz = payload_size;

        while (sz > sizeof(uint32_t)) {
            data.writeInt32(0);
            sz -= sizeof(uint32_t);
        }
        start = chrono::high_resolution_clock::now();
        status_t ret = workers[target]->transact(BINDER_NOP, data, &reply);
        end = chrono::high_resolution_clock::now();
@@ -210,6 +231,7 @@ void worker_fx(
           exit(EXIT_FAILURE);
        }
    }

    // Signal completion to master and wait.
    p.signal();
    p.wait();
@@ -221,7 +243,7 @@ void worker_fx(
    exit(EXIT_SUCCESS);
}

Pipe make_worker(int num, int iterations, int worker_count)
Pipe make_worker(int num, int iterations, int worker_count, int payload_size, bool cs_pair)
{
    auto pipe_pair = Pipe::createPipePair();
    pid_t pid = fork();
@@ -230,7 +252,7 @@ Pipe make_worker(int num, int iterations, int worker_count)
        return move(get<0>(pipe_pair));
    } else {
        /* child */
        worker_fx(num, worker_count, iterations, move(get<1>(pipe_pair)));
        worker_fx(num, worker_count, iterations, payload_size, cs_pair, move(get<1>(pipe_pair)));
        /* never get here */
        return move(get<0>(pipe_pair));
    }
@@ -251,35 +273,19 @@ void signal_all(vector<Pipe>& v)
    }
}

int main(int argc, char *argv[])
void run_main(int iterations,
              int workers,
              int payload_size,
              int cs_pair,
              bool training_round=false)
{
    int workers = 2;
    int iterations = 10000;
    (void)argc;
    (void)argv;
    vector<Pipe> pipes;

    // Parse arguments.
    for (int i = 1; i < argc; i++) {
        if (string(argv[i]) == "-w") {
            workers = atoi(argv[i+1]);
            i++;
            continue;
        }
        if (string(argv[i]) == "-i") {
            iterations = atoi(argv[i+1]);
            i++;
            continue;
        }
    }

    // Create all the workers and wait for them to spawn.
    for (int i = 0; i < workers; i++) {
        pipes.push_back(make_worker(i, iterations, workers));
        pipes.push_back(make_worker(i, iterations, workers, payload_size, cs_pair));
    }
    wait_all(pipes);


    // Run the workers and wait for completion.
    chrono::time_point<chrono::high_resolution_clock> start, end;
    cout << "waiting for workers to complete" << endl;
@@ -301,7 +307,6 @@ int main(int argc, char *argv[])
        pipes[i].recv(tmp_results);
        tot_results = ProcResults::combine(tot_results, tmp_results);
    }
    tot_results.dump();

    // Kill all the workers.
    cout << "killing workers" << endl;
@@ -313,5 +318,83 @@ int main(int argc, char *argv[])
            cout << "nonzero child status" << status << endl;
        }
    }
    if (training_round) {
        // sets max_time_bucket to 2 * m_worst from the training round.
        // Also needs to adjust time_per_bucket accordingly.
        max_time_bucket = 2 * tot_results.m_worst;
        time_per_bucket = max_time_bucket / num_buckets;
        cout << "Max latency during training: " << tot_results.m_worst / 1.0E6 << "ms" << endl;
    } else {
            tot_results.dump();
    }
}

int main(int argc, char *argv[])
{
    int workers = 2;
    int iterations = 10000;
    int payload_size = 0;
    bool cs_pair = false;
    bool training_round = false;
    (void)argc;
    (void)argv;

    // Parse arguments.
    for (int i = 1; i < argc; i++) {
        if (string(argv[i]) == "--help") {
            cout << "Usage: binderThroughputTest [OPTIONS]" << endl;
            cout << "\t-i N    : Specify number of iterations." << endl;
            cout << "\t-m N    : Specify expected max latency in microseconds." << endl;
            cout << "\t-p      : Split workers into client/server pairs." << endl;
            cout << "\t-s N    : Specify payload size." << endl;
            cout << "\t-t N    : Run training round." << endl;
            cout << "\t-w N    : Specify total number of workers." << endl;
            return 0;
        }
        if (string(argv[i]) == "-w") {
            workers = atoi(argv[i+1]);
            i++;
            continue;
        }
        if (string(argv[i]) == "-i") {
            iterations = atoi(argv[i+1]);
            i++;
            continue;
        }
        if (string(argv[i]) == "-s") {
            payload_size = atoi(argv[i+1]);
            i++;
        }
        if (string(argv[i]) == "-p") {
            // client/server pairs instead of spreading
            // requests to all workers. If true, half
            // the workers become clients and half servers
            cs_pair = true;
        }
        if (string(argv[i]) == "-t") {
            // Run one training round before actually collecting data
            // to get an approximation of max latency.
            training_round = true;
        }
        if (string(argv[i]) == "-m") {
            // Caller specified the max latency in microseconds.
            // No need to run training round in this case.
            if (atoi(argv[i+1]) > 0) {
                max_time_bucket = strtoull(argv[i+1], (char **)NULL, 10) * 1000;
                i++;
            } else {
                cout << "Max latency -m must be positive." << endl;
                exit(EXIT_FAILURE);
            }
        }
    }

    if (training_round) {
        cout << "Start training round" << endl;
        run_main(iterations, workers, payload_size, cs_pair, training_round=true);
        cout << "Completed training round" << endl << endl;
    }

    run_main(iterations, workers, payload_size, cs_pair);
    return 0;
}
+31 −12
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ vector<sp<IBinder> > workers;
// GOOD_SYNC_MIN is considered as good
#define GOOD_SYNC_MIN (0.6)

#define DUMP_PRICISION 3
#define DUMP_PRESICION 2

string trace_path = "/sys/kernel/debug/tracing";

@@ -246,10 +246,11 @@ struct Results {
    double worst = (double)m_worst / 1.0E6;
    double average = (double)m_total_time / m_transactions / 1.0E6;
    // FIXME: libjson?
    cout << std::setprecision(DUMP_PRICISION) << "{ \"avg\":" << setw(5) << left
         << average << ", \"wst\":" << setw(5) << left << worst
         << ", \"bst\":" << setw(5) << left << best << ", \"miss\":" << setw(5)
         << left << m_miss << ", \"meetR\":" << setw(3) << left
    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) << "}";
  }
};
@@ -272,8 +273,15 @@ static void parcel_fill(Parcel& data, int sz, int priority, int cpu) {
  }
}

typedef struct {
  void* result;
  int target;
} thread_priv_t;

static void* thread_start(void* p) {
  Results* results_fifo = (Results*)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;

@@ -281,7 +289,7 @@ static void* thread_start(void* p) {
  thread_dump("fifo-caller");

  sta = tickNow();
  status_t ret = workers[0]->transact(BINDER_NOP, data, &reply);
  status_t ret = workers[target]->transact(BINDER_NOP, data, &reply);
  end = tickNow();
  results_fifo->add_time(tickNano(sta, end));

@@ -291,16 +299,19 @@ static void* thread_start(void* p) {
}

// create a fifo thread to transact and wait it to finished
static void thread_transaction(Results* results_fifo) {
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, &param));
  ASSERT(!pthread_create(&thread, &attr, &thread_start, results_fifo));
  ASSERT(!pthread_create(&thread, &attr, &thread_start, &thread_priv));
  ASSERT(!pthread_join(thread, &dummy));
}

@@ -316,7 +327,9 @@ void worker_fx(int num, int no_process, int iterations, int payload_size,
  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
@@ -338,7 +351,7 @@ void worker_fx(int num, int no_process, int iterations, int payload_size,
    int target = num % server_count;

    // 1. transaction by fifo thread
    thread_transaction(&results_fifo);
    thread_transaction(target, &results_fifo);
    parcel_fill(data, payload_size, thread_pri(), sched_getcpu());
    thread_dump("other-caller");

@@ -356,6 +369,7 @@ void worker_fx(int num, int no_process, int iterations, int payload_size,
  p.wait();

  p.send(&dummy);
  // wait for kill
  p.wait();
  // Client for each pair dump here
  if (is_client(num)) {
@@ -463,12 +477,17 @@ int main(int argc, char** argv) {
  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