/* * 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 #include #include #include #include #include #include #include #include #include #include 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 void send(const T& v) { int error = write(m_writeFd, &v, sizeof(T)); ASSERT_TRUE(error >= 0); } template void recv(T& v) { int error = read(m_readFd, &v, sizeof(T)); ASSERT_TRUE(error >= 0); } static tuple 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 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> workers; for (int i = 0; i < service_count; i++) { sp 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 start, end; // Prepare data to IPC hidl_vec 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 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(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 std::move(get<0>(pipe_pair)); } else { /* child */ service_fx(service_name, std::move(get<1>(pipe_pair))); /* never get here */ return std::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 std::move(get<0>(pipe_pair)); } else { /* child */ worker_fx(num, iterations, service_count, get_stub, std::move(get<1>(pipe_pair))); /* never get here */ return std::move(get<0>(pipe_pair)); } } void wait_all(vector& v) { for (size_t i = 0; i < v.size(); i++) { v[i].wait(); } } void signal_all(vector& v) { for (size_t i = 0; i < v.size(); i++) { v[i].signal(); } } int main(int argc, char *argv[]) { android::hardware::details::setTrebleTestingOverride(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 worker_pipes; vector 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 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 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; }