385 lines
11 KiB
C++
385 lines
11 KiB
C++
|
/*
|
||
|
* 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
|
||
|
*
|
||
|
* Measures the rate at which a short binder IPC operation can be
|
||
|
* performed. The operation consists of the client sending a parcel
|
||
|
* that contains two integers. For each parcel that the server
|
||
|
* receives, it adds the two integers and sends the sum back to
|
||
|
* the client.
|
||
|
*
|
||
|
* This benchmark supports the following command-line options:
|
||
|
*
|
||
|
* -c cpu - bind client to specified cpu (default: unbound)
|
||
|
* -s cpu - bind server to specified cpu (default: unbound)
|
||
|
* -n num - perform IPC operation num times (default: 1000)
|
||
|
* -d time - delay specified amount of seconds after each
|
||
|
* IPC operation. (default 1e-3)
|
||
|
*/
|
||
|
|
||
|
#include <cerrno>
|
||
|
#include <grp.h>
|
||
|
#include <iostream>
|
||
|
#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 <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;
|
||
|
unsigned int iterations;
|
||
|
float iterDelay; // End of iteration delay in seconds
|
||
|
} options = { // Set defaults
|
||
|
unbound, // Server CPU
|
||
|
unbound, // Client CPU
|
||
|
1000, // Iterations
|
||
|
1e-3, // End of iteration delay
|
||
|
};
|
||
|
|
||
|
class AddIntsService : public BBinder
|
||
|
{
|
||
|
public:
|
||
|
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 void server(void);
|
||
|
static void client(void);
|
||
|
static void bindCPU(unsigned int cpu);
|
||
|
static ostream &operator<<(ostream &stream, const String16& str);
|
||
|
static ostream &operator<<(ostream &stream, const cpu_set_t& set);
|
||
|
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
int rv;
|
||
|
|
||
|
// 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:n: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 'n': // iterations
|
||
|
options.iterations = strtoul(optarg, &chptr, 10);
|
||
|
if (*chptr != '\0') {
|
||
|
cerr << "Invalid iterations specified of: " << optarg << endl;
|
||
|
exit(4);
|
||
|
}
|
||
|
if (options.iterations < 1) {
|
||
|
cerr << "Less than 1 iteration specified by: "
|
||
|
<< optarg << endl;
|
||
|
exit(5);
|
||
|
}
|
||
|
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 << " -n num - iterations" << endl;
|
||
|
cerr << " -d time - delay after operation in seconds" << endl;
|
||
|
exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Display selected options
|
||
|
cout << "serverCPU: ";
|
||
|
if (options.serverCPU == unbound) {
|
||
|
cout << " unbound";
|
||
|
} else {
|
||
|
cout << options.serverCPU;
|
||
|
}
|
||
|
cout << endl;
|
||
|
cout << "clientCPU: ";
|
||
|
if (options.clientCPU == unbound) {
|
||
|
cout << " unbound";
|
||
|
} else {
|
||
|
cout << options.clientCPU;
|
||
|
}
|
||
|
cout << endl;
|
||
|
cout << "iterations: " << options.iterations << endl;
|
||
|
cout << "iterDelay: " << options.iterDelay << endl;
|
||
|
|
||
|
// Fork client, use this process as server
|
||
|
fflush(stdout);
|
||
|
switch (pid_t pid = fork()) {
|
||
|
case 0: // Child
|
||
|
client();
|
||
|
return 0;
|
||
|
|
||
|
default: // Parent
|
||
|
server();
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void 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;
|
||
|
}
|
||
|
|
||
|
// Start threads to handle server work
|
||
|
proc->startThreadPool();
|
||
|
}
|
||
|
|
||
|
static void client(void)
|
||
|
{
|
||
|
int rv;
|
||
|
sp<IServiceManager> sm = defaultServiceManager();
|
||
|
double min = FLT_MAX, max = 0.0, total = 0.0; // Time in seconds for all
|
||
|
// the IPC calls.
|
||
|
|
||
|
// If needed bind to client CPU
|
||
|
if (options.clientCPU != unbound) { bindCPU(options.clientCPU); }
|
||
|
|
||
|
// Attach to service
|
||
|
sp<IBinder> binder;
|
||
|
do {
|
||
|
binder = sm->getService(serviceName);
|
||
|
if (binder != 0) break;
|
||
|
cout << serviceName << " not published, waiting..." << endl;
|
||
|
usleep(500000); // 0.5 s
|
||
|
} while(true);
|
||
|
|
||
|
// Perform the IPC operations
|
||
|
for (unsigned int iter = 0; iter < options.iterations; iter++) {
|
||
|
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.
|
||
|
int val1 = iter;
|
||
|
int val2 = iter + 3;
|
||
|
int expected = val1 + val2; // Expect to get the sum back
|
||
|
send.writeInt32(val1);
|
||
|
send.writeInt32(val2);
|
||
|
|
||
|
// Send the parcel, while timing how long it takes for
|
||
|
// the answer to return.
|
||
|
struct timespec start;
|
||
|
clock_gettime(CLOCK_MONOTONIC, &start);
|
||
|
if ((rv = binder->transact(AddIntsService::ADD_INTS,
|
||
|
send, &reply)) != 0) {
|
||
|
cerr << "binder->transact failed, rv: " << rv
|
||
|
<< " errno: " << errno << endl;
|
||
|
exit(10);
|
||
|
}
|
||
|
struct timespec current;
|
||
|
clock_gettime(CLOCK_MONOTONIC, ¤t);
|
||
|
|
||
|
// Calculate how long this operation took and update the stats
|
||
|
struct timespec deltaTimespec = tsDelta(&start, ¤t);
|
||
|
double delta = ts2double(&deltaTimespec);
|
||
|
min = (delta < min) ? delta : min;
|
||
|
max = (delta > max) ? delta : max;
|
||
|
total += delta;
|
||
|
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); }
|
||
|
}
|
||
|
|
||
|
// Display the results
|
||
|
cout << "Time per iteration min: " << min
|
||
|
<< " avg: " << (total / options.iterations)
|
||
|
<< " max: " << max
|
||
|
<< endl;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|