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