diff options
author | Louis Huemiller <lhuemill@google.com> | 2010-10-22 10:35:43 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2010-10-22 10:35:43 -0700 |
commit | 0bdc4387cad6355da33178c597f7aa4d931c09e1 (patch) | |
tree | 969717782768c557eefc6d6446520d66f931565e /tests | |
parent | 133a37ede3e955093c6004d94496e2cdc2cc1264 (diff) | |
parent | d2447fd2505466a8c30cdca247325f79ba95be34 (diff) | |
download | extras-0bdc4387cad6355da33178c597f7aa4d931c09e1.tar.gz |
Merge "Binder add ints benchmark"
Diffstat (limited to 'tests')
-rw-r--r-- | tests/binder/Android.mk | 17 | ||||
-rw-r--r-- | tests/binder/benchmarks/Android.mk | 48 | ||||
-rw-r--r-- | tests/binder/benchmarks/binderAddInts.cpp | 458 | ||||
-rw-r--r-- | tests/include/testUtil.h | 15 | ||||
-rw-r--r-- | tests/lib/testUtil/testUtil.c | 94 |
5 files changed, 599 insertions, 33 deletions
diff --git a/tests/binder/Android.mk b/tests/binder/Android.mk new file mode 100644 index 00000000..4343259b --- /dev/null +++ b/tests/binder/Android.mk @@ -0,0 +1,17 @@ +# +# 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. +# + +include $(call all-subdir-makefiles) diff --git a/tests/binder/benchmarks/Android.mk b/tests/binder/benchmarks/Android.mk new file mode 100644 index 00000000..53d35a07 --- /dev/null +++ b/tests/binder/benchmarks/Android.mk @@ -0,0 +1,48 @@ +# +# 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. +# + +ifneq ($(TARGET_SIMULATOR),true) # GTest needs STLport, which the simulator + # doesn't support + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := eng tests +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativebenchmark + +LOCAL_STATIC_LIBRARIES += \ + libgtest \ + libgtest_main \ + libtestUtil + +LOCAL_SHARED_LIBRARIES += \ + libutils \ + libstlport \ + libbinder + +LOCAL_C_INCLUDES += \ + bionic \ + bionic/libstdc++/include \ + external/stlport/stlport \ + external/gtest/include \ + system/extras/tests/include \ + frameworks/base/include + +LOCAL_MODULE := binderAddInts +LOCAL_SRC_FILES := binderAddInts.cpp +include $(BUILD_EXECUTABLE) + +endif diff --git a/tests/binder/benchmarks/binderAddInts.cpp b/tests/binder/benchmarks/binderAddInts.cpp new file mode 100644 index 00000000..88937dc5 --- /dev/null +++ b/tests/binder/benchmarks/binderAddInts.cpp @@ -0,0 +1,458 @@ +/* + * 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_; +}; + +// Workaround for missing sched_setaffinity(2) and getcpu(2) +#define CPU_SETSIZE 1024 +struct getcpu_cache; +typedef struct { uint64_t bits[CPU_SETSIZE / 64]; } cpu_set_t; +static int sched_getaffinity(pid_t pid, unsigned int cpusetsize, + cpu_set_t *set); +static int sched_setaffinity(pid_t pid, unsigned int cpusetsize, + cpu_set_t *mask); +static int getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache); +static int CPU_ISSET(int cpu, const cpu_set_t *set); +static void CPU_SET(int cpu, cpu_set_t *set); +static void CPU_ZERO(cpu_set_t *set); + +// 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) { delay(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) { + getcpu((unsigned int *) &cpu, NULL, NULL); + 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; +} + +// ======== Local implementation of system calls with missing bionic call stubs +static int sched_getaffinity(pid_t pid, unsigned int cpusetsize, + cpu_set_t *set) +{ + int rv; + + rv = syscall(__NR_sched_getaffinity, pid, cpusetsize, set); + if (rv < 0) { return rv; } + + // Kernel implementation of sched_getaffinity() returns the number + // of bytes in the set that it set. Set the rest of our set bits + // to 0. + memset(((char *) set) + rv, 0x00, sizeof(cpu_set_t) - rv); + + return 0; +} + +static int sched_setaffinity(pid_t pid, unsigned int cpusetsize, + cpu_set_t *mask) +{ + int rv; + + rv = syscall(__NR_sched_setaffinity, pid, cpusetsize, mask); + + return rv; +} + +static int getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache) +{ + int rv; + + rv = syscall(345, cpu, node, tcache); + + return rv; +} + +static int CPU_ISSET(int cpu, const cpu_set_t *set) +{ + if (cpu < 0) { return 0; } + if ((unsigned) cpu >= (sizeof(cpu_set_t) * CHAR_BIT)) { return 0; } + + if ((*((uint64_t *)set + (cpu / 64))) & (1ULL << (cpu % 64))) { + return true; + } + + return false; +} + +static void CPU_SET(int cpu, cpu_set_t *set) +{ + if (cpu < 0) { return; } + if ((unsigned) cpu > (sizeof(cpu_set_t) * CHAR_BIT)) { return; } + + *((uint64_t *)set + (cpu / 64)) |= 1ULL << (cpu % 64); +} + +static void CPU_ZERO(cpu_set_t *set) +{ + memset(set, 0x00, sizeof(cpu_set_t)); +} diff --git a/tests/include/testUtil.h b/tests/include/testUtil.h index d1d09549..8199c036 100644 --- a/tests/include/testUtil.h +++ b/tests/include/testUtil.h @@ -22,15 +22,18 @@ #include <stdio.h> #include <sys/time.h> -#ifdef __cplusplus -extern "C" { -#endif +__BEGIN_DECLS // Time Utilities -double tv2double(const struct timeval *val); struct timespec double2ts(double amt); +struct timeval double2tv(double amt); +double ts2double(const struct timespec *val); +double tv2double(const struct timeval *val); +struct timespec tsDelta(const struct timespec *first, + const struct timespec *second); struct timeval tvDelta(const struct timeval *first, const struct timeval *second); + void delay(float amt); // Pseudo Random Utilities @@ -47,8 +50,6 @@ void testPrint(FILE *stream, const char *fmt, ...); testPrint(stderr, __VA_ARGS__); \ } while (0) -#ifdef __cplusplus -} -#endif +__END_DECLS #endif diff --git a/tests/lib/testUtil/testUtil.c b/tests/lib/testUtil/testUtil.c index 1c4572d5..b0170148 100644 --- a/tests/lib/testUtil/testUtil.c +++ b/tests/lib/testUtil/testUtil.c @@ -33,13 +33,25 @@ typedef unsigned int bool_t; #define false (!true) #define MAXSTR 200 + static const char *logCatTag; +static const unsigned int uSecsPerSec = 1000000; +static const unsigned int nSecsPerSec = 1000000000; + +// struct timespec to double +double ts2double(const struct timespec *val) +{ + double rv; + + rv = val->tv_sec; + rv += (double) val->tv_nsec / nSecsPerSec; + + return rv; +} // struct timeval to double -double -tv2double(const struct timeval *val) +double tv2double(const struct timeval *val) { - const unsigned int uSecsPerSec = 1000000; double rv; rv = val->tv_sec; @@ -49,10 +61,8 @@ tv2double(const struct timeval *val) } // double to struct timespec -struct timespec -double2ts(double amt) +struct timespec double2ts(double amt) { - const unsigned int nSecsPerSec = 1000000000; struct timespec rv; rv.tv_sec = floor(amt); @@ -66,14 +76,51 @@ double2ts(double amt) return rv; } +// double to struct timeval +struct timeval double2tv(double amt) +{ + struct timeval rv; + + rv.tv_sec = floor(amt); + rv.tv_usec = (amt - rv.tv_sec) * uSecsPerSec; + // TODO: Handle cases where amt is negative + while ((unsigned) rv.tv_usec >= uSecsPerSec) { + rv.tv_usec -= uSecsPerSec; + rv.tv_sec++; + } + + return rv; +} + +// Delta (difference) between two struct timespec. +// It is expected that the time given by the structure pointed to by +// second, is later than the time pointed to by first. +struct timespec tsDelta(const struct timespec *first, + const struct timespec *second) +{ + struct timespec rv; + + assert(first != NULL); + assert(second != NULL); + assert(first->tv_nsec >= 0 && first->tv_nsec < nSecsPerSec); + assert(second->tv_nsec >= 0 && second->tv_nsec < nSecsPerSec); + rv.tv_sec = second->tv_sec - first->tv_sec; + if (second->tv_nsec >= first->tv_nsec) { + rv.tv_nsec = second->tv_nsec - first->tv_nsec; + } else { + rv.tv_nsec = (second->tv_nsec + nSecsPerSec) - first->tv_nsec; + rv.tv_sec--; + } + + return rv; +} + // Delta (difference) between two struct timeval. // It is expected that the time given by the structure pointed to by // second, is later than the time pointed to by first. -struct timeval -tvDelta(const struct timeval *first, - const struct timeval *second) +struct timeval tvDelta(const struct timeval *first, + const struct timeval *second) { - const unsigned int uSecsPerSec = 1000000; struct timeval rv; assert(first != NULL); @@ -91,8 +138,7 @@ tvDelta(const struct timeval *first, return rv; } -void -testPrint(FILE *stream, const char *fmt, ...) +void testPrint(FILE *stream, const char *fmt, ...) { char line[MAXSTR]; va_list args; @@ -109,15 +155,13 @@ testPrint(FILE *stream, const char *fmt, ...) } // Set tag used while logging to the logcat error interface -void -testSetLogCatTag(const char *tag) +void testSetLogCatTag(const char *tag) { logCatTag = tag; } // Obtain pointer to current log to logcat error interface tag -const char * -testGetLogCatTag(void) +const char * testGetLogCatTag(void) { return logCatTag; } @@ -130,8 +174,7 @@ testGetLogCatTag(void) * Precondition: srand48() called to set the seed of * the pseudo random number generator. */ -int -testRandBool(void) +int testRandBool(void) { /* Use the most significant bit from lrand48(), because the * less significant bits are less random across different seeds @@ -145,25 +188,24 @@ testRandBool(void) // The amt variable is of type float and thus non-integer amounts // of time can be specified. This function automatically handles cases // where nanosleep(2) returns early due to reception of a signal. -void -delay(float amt) +void delay(float amt) { - struct timeval start, current, delta; + struct timespec start, current, delta; struct timespec remaining; // Get the time at which we started - gettimeofday(&start, NULL); + clock_gettime(CLOCK_MONOTONIC, &start); do { // Get current time - gettimeofday(¤t, NULL); + clock_gettime(CLOCK_MONOTONIC, ¤t); // How much time is left - delta = tvDelta(&start, ¤t); - if (tv2double(&delta) > amt) { break; } + delta = tsDelta(&start, ¤t); + if (ts2double(&delta) > amt) { break; } // Request to sleep for the remaining time - remaining = double2ts(amt - tv2double(&delta)); + remaining = double2ts(amt - ts2double(&delta)); (void) nanosleep(&remaining, NULL); } while (true); } |