/* * 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; }