/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* Binder add integers benchmark (Using google-benchmark library)
*
*/
#include <cerrno>
#include <grp.h>
#include <iostream>
#include <iomanip>
#include <libgen.h>
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <benchmark/benchmark.h>
#include <utils/Log.h>
#include <testUtil.h>
using namespace android;
using namespace std;
const int unbound = -1; // Indicator for a thread not bound to a specific CPU
String16 serviceName("test.binderAddInts");
struct options {
int serverCPU;
int clientCPU;
float iterDelay; // End of iteration delay in seconds
} options = { // Set defaults
unbound, // Server CPU
unbound, // Client CPU
0.0, // End of iteration delay
};
class AddIntsService : public BBinder
{
public:
explicit AddIntsService(int cpu = unbound);
virtual ~AddIntsService() {}
enum command {
ADD_INTS = 0x120,
};
virtual status_t onTransact(uint32_t code,
const Parcel& data, Parcel* reply,
uint32_t flags = 0);
private:
int cpu_;
};
// File scope function prototypes
static bool server(void);
static void BM_addInts(benchmark::State& state);
static void bindCPU(unsigned int cpu);
static ostream &operator<<(ostream &stream, const String16& str);
static ostream &operator<<(ostream &stream, const cpu_set_t& set);
static bool server(void)
{
int rv;
// Add the service
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
if ((rv = sm->addService(serviceName,
new AddIntsService(options.serverCPU))) != 0) {
cerr << "addService " << serviceName << " failed, rv: " << rv
<< " errno: " << errno << endl;
return false;
}
// Start threads to handle server work
proc->startThreadPool();
return true;
}
static void BM_addInts(benchmark::State& state)
{
int rv;
sp<IServiceManager> sm = defaultServiceManager();
// If needed bind to client CPU
if (options.clientCPU != unbound) { bindCPU(options.clientCPU); }
// Attach to service
sp<IBinder> binder;
for (int i = 0; i < 3; i++) {
binder = sm->getService(serviceName);
if (binder != 0) break;
cout << serviceName << " not published, waiting..." << endl;
usleep(500000); // 0.5 s
}
if (binder == 0) {
cout << serviceName << " failed to publish, aborting" << endl;
return;
}
unsigned int iter = 0;
// Perform the IPC operations in the benchmark
while (state.KeepRunning()) {
Parcel send, reply;
// Create parcel to be sent. Will use the iteration cound
// and the iteration count + 3 as the two integer values
// to be sent.
state.PauseTiming();
int val1 = iter;
int val2 = iter + 3;
int expected = val1 + val2; // Expect to get the sum back
send.writeInt32(val1);
send.writeInt32(val2);
state.ResumeTiming();
// Send the parcel, while timing how long it takes for
// the answer to return.
if ((rv = binder->transact(AddIntsService::ADD_INTS,
send, &reply)) != 0) {
cerr << "binder->transact failed, rv: " << rv
<< " errno: " << errno << endl;
exit(10);
}
state.PauseTiming();
int result = reply.readInt32();
if (result != (int) (iter + iter + 3)) {
cerr << "Unexpected result for iteration " << iter << endl;
cerr << " result: " << result << endl;
cerr << "expected: " << expected << endl;
}
if (options.iterDelay > 0.0) { testDelaySpin(options.iterDelay); }
state.ResumeTiming();
}
}
BENCHMARK(BM_addInts);
AddIntsService::AddIntsService(int cpu): cpu_(cpu) {
if (cpu != unbound) { bindCPU(cpu); }
}
// Server function that handles parcels received from the client
status_t AddIntsService::onTransact(uint32_t code, const Parcel &data,
Parcel* reply, uint32_t /* flags */) {
int val1, val2;
status_t rv(0);
int cpu;
// If server bound to a particular CPU, check that
// were executing on that CPU.
if (cpu_ != unbound) {
cpu = sched_getcpu();
if (cpu != cpu_) {
cerr << "server onTransact on CPU " << cpu << " expected CPU "
<< cpu_ << endl;
exit(20);
}
}
// Perform the requested operation
switch (code) {
case ADD_INTS:
val1 = data.readInt32();
val2 = data.readInt32();
reply->writeInt32(val1 + val2);
break;
default:
cerr << "server onTransact unknown code, code: " << code << endl;
exit(21);
}
return rv;
}
static void bindCPU(unsigned int cpu)
{
int rv;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
rv = sched_setaffinity(0, sizeof(cpuset), &cpuset);
if (rv != 0) {
cerr << "bindCPU failed, rv: " << rv << " errno: " << errno << endl;
perror(NULL);
exit(30);
}
}
static ostream &operator<<(ostream &stream, const String16& str)
{
for (unsigned int n1 = 0; n1 < str.size(); n1++) {
if ((str[n1] > 0x20) && (str[n1] < 0x80)) {
stream << (char) str[n1];
} else {
stream << '~';
}
}
return stream;
}
static ostream &operator<<(ostream &stream, const cpu_set_t& set)
{
for (unsigned int n1 = 0; n1 < CPU_SETSIZE; n1++) {
if (CPU_ISSET(n1, &set)) {
if (n1 != 0) { stream << ' '; }
stream << n1;
}
}
return stream;
}
int main(int argc, char *argv[])
{
int rv;
::benchmark::Initialize(&argc, argv);
// Determine CPUs available for use.
// This testcase limits its self to using CPUs that were
// available at the start of the benchmark.
cpu_set_t availCPUs;
if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
cerr << "sched_getaffinity failure, rv: " << rv
<< " errno: " << errno << endl;
exit(1);
}
// Parse command line arguments
int opt;
while ((opt = getopt(argc, argv, "s:c:d:?")) != -1) {
char *chptr; // character pointer for command-line parsing
switch (opt) {
case 'c': // client CPU
case 's': { // server CPU
// Parse the CPU number
int cpu = strtoul(optarg, &chptr, 10);
if (*chptr != '\0') {
cerr << "Invalid cpu specified for -" << (char) opt
<< " option of: " << optarg << endl;
exit(2);
}
// Is the CPU available?
if (!CPU_ISSET(cpu, &availCPUs)) {
cerr << "CPU " << optarg << " not currently available" << endl;
cerr << " Available CPUs: " << availCPUs << endl;
exit(3);
}
// Record the choice
*((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
break;
}
case 'd': // delay between each iteration
options.iterDelay = strtod(optarg, &chptr);
if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
cerr << "Invalid delay specified of: " << optarg << endl;
exit(6);
}
break;
case '?':
default:
cerr << basename(argv[0]) << " [options]" << endl;
cerr << " options:" << endl;
cerr << " -s cpu - server CPU number" << endl;
cerr << " -c cpu - client CPU number" << endl;
cerr << " -d time - delay after operation in seconds" << endl;
exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
}
}
fflush(stdout);
switch (pid_t pid = fork()) {
case 0: // Child
::benchmark::RunSpecifiedBenchmarks();
return 0;
default: // Parent
if (!server()) { break; }
// Wait for all children to end
do {
int stat;
rv = wait(&stat);
if ((rv == -1) && (errno == ECHILD)) { break; }
if (rv == -1) {
cerr << "wait failed, rv: " << rv << " errno: "
<< errno << endl;
perror(NULL);
exit(8);
}
} while (1);
return 0;
case -1: // Error
exit(9);
}
}