| /* |
| * Copyright (C) 2016 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. |
| */ |
| #define LOG_TAG "HwbinderThroughputTest" |
| |
| #include <unistd.h> |
| #include <sys/wait.h> |
| |
| #include <cstring> |
| #include <iostream> |
| #include <string> |
| #include <tuple> |
| #include <vector> |
| |
| #include <log/log.h> |
| |
| #include <android/hardware/tests/libhwbinder/1.0/IBenchmark.h> |
| #include <hidl/HidlSupport.h> |
| |
| using namespace std; |
| using namespace android; |
| using namespace android::hardware; |
| |
| // Generated HIDL files |
| using android::hardware::tests::libhwbinder::V1_0::IBenchmark; |
| |
| #define ASSERT_TRUE(cond) \ |
| do { \ |
| if (!(cond)) {\ |
| cerr << __func__ << ":" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \ |
| exit(EXIT_FAILURE); \ |
| } \ |
| } while (0) |
| |
| class Pipe { |
| int m_readFd; |
| int m_writeFd; |
| Pipe(int readFd, int writeFd) |
| : m_readFd{readFd}, m_writeFd{writeFd} { |
| } |
| Pipe(const Pipe &) = delete; |
| Pipe& operator=(const Pipe &) = delete; |
| Pipe& operator=(const Pipe &&) = delete; |
| public: |
| Pipe(Pipe&& rval) noexcept { |
| m_readFd = rval.m_readFd; |
| m_writeFd = rval.m_writeFd; |
| rval.m_readFd = 0; |
| rval.m_writeFd = 0; |
| } |
| ~Pipe() { |
| if (m_readFd) |
| close(m_readFd); |
| if (m_writeFd) |
| close(m_writeFd); |
| } |
| void signal() { |
| bool val = true; |
| int error = write(m_writeFd, &val, sizeof(val)); |
| ASSERT_TRUE(error >= 0); |
| } |
| void wait() { |
| bool val = false; |
| int error = read(m_readFd, &val, sizeof(val)); |
| ASSERT_TRUE(error >= 0); |
| } |
| template<typename T> void send(const T& v) { |
| int error = write(m_writeFd, &v, sizeof(T)); |
| ASSERT_TRUE(error >= 0); |
| } |
| template<typename T> void recv(T& v) { |
| int error = read(m_readFd, &v, sizeof(T)); |
| ASSERT_TRUE(error >= 0); |
| } |
| static tuple<Pipe, Pipe> createPipePair() { |
| int a[2]; |
| int b[2]; |
| |
| int error1 = pipe(a); |
| int error2 = pipe(b); |
| ASSERT_TRUE(error1 >= 0); |
| ASSERT_TRUE(error2 >= 0); |
| |
| return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1])); |
| } |
| }; |
| |
| static const uint32_t num_buckets = 128; |
| static const uint64_t max_time_bucket = 50ull * 1000000; |
| static const uint64_t time_per_bucket = max_time_bucket / num_buckets; |
| static constexpr float time_per_bucket_ms = time_per_bucket / 1.0E6; |
| |
| struct ProcResults { |
| uint64_t m_best = max_time_bucket; |
| uint64_t m_worst = 0; |
| uint32_t m_buckets[num_buckets] = {0}; |
| uint64_t m_transactions = 0; |
| uint64_t m_total_time = 0; |
| |
| // Add a new latency data point and update the aggregation info |
| // e.g. best/worst/total_time. |
| void add_time(uint64_t time) { |
| m_buckets[min(time, max_time_bucket - 1) / time_per_bucket] += 1; |
| m_best = min(time, m_best); |
| m_worst = max(time, m_worst); |
| m_transactions += 1; |
| m_total_time += time; |
| } |
| // Combine two sets of latency data points and update the aggregation info. |
| static ProcResults combine(const ProcResults& a, const ProcResults& b) { |
| ProcResults ret; |
| for (uint32_t i = 0; i < num_buckets; i++) { |
| ret.m_buckets[i] = a.m_buckets[i] + b.m_buckets[i]; |
| } |
| ret.m_worst = max(a.m_worst, b.m_worst); |
| ret.m_best = min(a.m_best, b.m_best); |
| ret.m_transactions = a.m_transactions + b.m_transactions; |
| ret.m_total_time = a.m_total_time + b.m_total_time; |
| return ret; |
| } |
| // Calculate and report the final aggregated results. |
| void dump() { |
| double best = (double) m_best / 1.0E6; |
| double worst = (double) m_worst / 1.0E6; |
| double average = (double) m_total_time / m_transactions / 1.0E6; |
| cout << "average:" |
| << average |
| << "ms worst:" |
| << worst |
| << "ms best:" |
| << best |
| << "ms" |
| << endl; |
| |
| uint64_t cur_total = 0; |
| for (uint32_t i = 0; i < num_buckets; i++) { |
| float cur_time = time_per_bucket_ms * i + 0.5f * time_per_bucket_ms; |
| if ((cur_total < 0.5f * m_transactions) |
| && (cur_total + m_buckets[i] >= 0.5f * m_transactions)) { |
| cout << "50%: " << cur_time << " "; |
| } |
| if ((cur_total < 0.9f * m_transactions) |
| && (cur_total + m_buckets[i] >= 0.9f * m_transactions)) { |
| cout << "90%: " << cur_time << " "; |
| } |
| if ((cur_total < 0.95f * m_transactions) |
| && (cur_total + m_buckets[i] >= 0.95f * m_transactions)) { |
| cout << "95%: " << cur_time << " "; |
| } |
| if ((cur_total < 0.99f * m_transactions) |
| && (cur_total + m_buckets[i] >= 0.99f * m_transactions)) { |
| cout << "99%: " << cur_time << " "; |
| } |
| cur_total += m_buckets[i]; |
| } |
| cout << endl; |
| |
| } |
| }; |
| |
| string generateServiceName(int num) { |
| string serviceName = "hwbinderService" + to_string(num); |
| return serviceName; |
| } |
| |
| void service_fx(const string &serviceName, Pipe p) { |
| // Start service. |
| sp<IBenchmark> server = IBenchmark::getService(serviceName, true); |
| ALOGD("Registering %s", serviceName.c_str()); |
| status_t status = server->registerAsService(serviceName); |
| if (status != ::android::OK) { |
| ALOGE("Failed to register service %s", serviceName.c_str()); |
| exit(EXIT_FAILURE); |
| } |
| |
| ALOGD("Starting %s", serviceName.c_str()); |
| |
| // Signal service started to master and wait to exit. |
| p.signal(); |
| p.wait(); |
| exit(EXIT_SUCCESS); |
| } |
| |
| void worker_fx( |
| int num, |
| int iterations, |
| int service_count, |
| bool get_stub, |
| Pipe p) { |
| srand(num); |
| p.signal(); |
| p.wait(); |
| |
| // Get references to test services. |
| vector<sp<IBenchmark>> workers; |
| |
| for (int i = 0; i < service_count; i++) { |
| sp<IBenchmark> service = IBenchmark::getService( |
| generateServiceName(i), get_stub); |
| ASSERT_TRUE(service != NULL); |
| if (get_stub) { |
| ASSERT_TRUE(!service->isRemote()); |
| } else { |
| ASSERT_TRUE(service->isRemote()); |
| } |
| workers.push_back(service); |
| } |
| |
| ProcResults results; |
| chrono::time_point<chrono::high_resolution_clock> start, end; |
| // Prepare data to IPC |
| hidl_vec<uint8_t> data_vec; |
| data_vec.resize(16); |
| for (size_t i = 0; i < data_vec.size(); i++) { |
| data_vec[i] = i; |
| } |
| // Run the benchmark. |
| for (int i = 0; i < iterations; i++) { |
| // Randomly pick a service. |
| int target = rand() % service_count; |
| |
| start = chrono::high_resolution_clock::now(); |
| Return<void> ret = workers[target]->sendVec(data_vec, [&](const auto &) {}); |
| if (!ret.isOk()) { |
| cout << "thread " << num << " failed status: " |
| << ret.description() << endl; |
| exit(EXIT_FAILURE); |
| } |
| end = chrono::high_resolution_clock::now(); |
| |
| uint64_t cur_time = uint64_t( |
| chrono::duration_cast<chrono::nanoseconds>(end - start).count()); |
| results.add_time(cur_time); |
| } |
| // Signal completion to master and wait. |
| p.signal(); |
| p.wait(); |
| |
| // Send results to master and wait for go to exit. |
| p.send(results); |
| p.wait(); |
| |
| exit (EXIT_SUCCESS); |
| } |
| |
| Pipe make_service(string service_name) { |
| auto pipe_pair = Pipe::createPipePair(); |
| pid_t pid = fork(); |
| if (pid) { |
| /* parent */ |
| return move(get<0>(pipe_pair)); |
| } else { |
| /* child */ |
| service_fx(service_name, move(get<1>(pipe_pair))); |
| /* never get here */ |
| return move(get<0>(pipe_pair)); |
| } |
| } |
| |
| Pipe make_worker(int num, int iterations, int service_count, bool get_stub) { |
| auto pipe_pair = Pipe::createPipePair(); |
| pid_t pid = fork(); |
| if (pid) { |
| /* parent */ |
| return move(get<0>(pipe_pair)); |
| } else { |
| /* child */ |
| worker_fx(num, iterations, service_count, get_stub, |
| move(get<1>(pipe_pair))); |
| /* never get here */ |
| return move(get<0>(pipe_pair)); |
| } |
| } |
| |
| void wait_all(vector<Pipe>& v) { |
| for (size_t i = 0; i < v.size(); i++) { |
| v[i].wait(); |
| } |
| } |
| |
| void signal_all(vector<Pipe>& v) { |
| for (size_t i = 0; i < v.size(); i++) { |
| v[i].signal(); |
| } |
| } |
| |
| int main(int argc, char *argv[]) { |
| setenv("TREBLE_TESTING_OVERRIDE", "true", true); |
| |
| enum HwBinderMode { |
| kBinderize = 0, |
| kPassthrough = 1, |
| }; |
| HwBinderMode mode = HwBinderMode::kBinderize; |
| |
| // Num of workers. |
| int workers = 2; |
| // Num of services. |
| int services = -1; |
| int iterations = 10000; |
| |
| vector<Pipe> worker_pipes; |
| vector<Pipe> service_pipes; |
| |
| // Parse arguments. |
| for (int i = 1; i < argc; i++) { |
| if (string(argv[i]) == "-m") { |
| if (!strcmp(argv[i + 1], "PASSTHROUGH")) { |
| mode = HwBinderMode::kPassthrough; |
| } |
| i++; |
| continue; |
| } |
| if (string(argv[i]) == "-w") { |
| workers = atoi(argv[i + 1]); |
| i++; |
| continue; |
| } |
| if (string(argv[i]) == "-i") { |
| iterations = atoi(argv[i + 1]); |
| i++; |
| continue; |
| } |
| if (string(argv[i]) == "-s") { |
| services = atoi(argv[i + 1]); |
| i++; |
| continue; |
| } |
| } |
| // If service number is not provided, set it the same as the worker number. |
| if (services == -1) { |
| services = workers; |
| } |
| if (mode == HwBinderMode::kBinderize) { |
| // Create services. |
| vector<pid_t> pIds; |
| for (int i = 0; i < services; i++) { |
| string serviceName = generateServiceName(i); |
| cout << "creating service: " << serviceName << endl; |
| service_pipes.push_back(make_service(serviceName)); |
| } |
| // Wait until all services are up. |
| wait_all(service_pipes); |
| } |
| |
| // Create workers (test clients). |
| bool get_stub = mode == HwBinderMode::kBinderize ? false : true; |
| for (int i = 0; i < workers; i++) { |
| worker_pipes.push_back(make_worker(i, iterations, services, get_stub)); |
| } |
| // Wait untill all workers are ready. |
| wait_all(worker_pipes); |
| |
| // Run the workers and wait for completion. |
| chrono::time_point<chrono::high_resolution_clock> start, end; |
| cout << "waiting for workers to complete" << endl; |
| start = chrono::high_resolution_clock::now(); |
| signal_all(worker_pipes); |
| wait_all(worker_pipes); |
| end = chrono::high_resolution_clock::now(); |
| |
| // Calculate overall throughput. |
| double iterations_per_sec = double(iterations * workers) |
| / (chrono::duration_cast < chrono::nanoseconds |
| > (end - start).count() / 1.0E9); |
| cout << "iterations per sec: " << iterations_per_sec << endl; |
| |
| // Collect all results from the workers. |
| cout << "collecting results" << endl; |
| signal_all(worker_pipes); |
| ProcResults tot_results; |
| for (int i = 0; i < workers; i++) { |
| ProcResults tmp_results; |
| worker_pipes[i].recv(tmp_results); |
| tot_results = ProcResults::combine(tot_results, tmp_results); |
| } |
| tot_results.dump(); |
| |
| if (mode == HwBinderMode::kBinderize) { |
| // Kill all the services. |
| cout << "killing services" << endl; |
| signal_all(service_pipes); |
| for (int i = 0; i < services; i++) { |
| int status; |
| wait(&status); |
| if (status != 0) { |
| cout << "nonzero child status" << status << endl; |
| } |
| } |
| } |
| // Kill all the workers. |
| cout << "killing workers" << endl; |
| signal_all(worker_pipes); |
| for (int i = 0; i < workers; i++) { |
| int status; |
| wait(&status); |
| if (status != 0) { |
| cout << "nonzero child status" << status << endl; |
| } |
| } |
| return 0; |
| } |