1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #define LOG_TAG "HwbinderThroughputTest" 17 18 #include <unistd.h> 19 #include <sys/wait.h> 20 21 #include <cstring> 22 #include <iostream> 23 #include <string> 24 #include <tuple> 25 #include <vector> 26 27 #include <log/log.h> 28 29 #include <android/hardware/tests/libhwbinder/1.0/IBenchmark.h> 30 #include <hidl/HidlSupport.h> 31 32 using namespace std; 33 using namespace android; 34 using namespace android::hardware; 35 36 // Generated HIDL files 37 using android::hardware::tests::libhwbinder::V1_0::IBenchmark; 38 39 #define ASSERT_TRUE(cond) \ 40 do { \ 41 if (!(cond)) {\ 42 cerr << __func__ << ":" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \ 43 exit(EXIT_FAILURE); \ 44 } \ 45 } while (0) 46 47 class Pipe { 48 int m_readFd; 49 int m_writeFd; 50 Pipe(int readFd, int writeFd) 51 : m_readFd{readFd}, m_writeFd{writeFd} { 52 } 53 Pipe(const Pipe &) = delete; 54 Pipe& operator=(const Pipe &) = delete; 55 Pipe& operator=(const Pipe &&) = delete; 56 public: 57 Pipe(Pipe&& rval) noexcept { 58 m_readFd = rval.m_readFd; 59 m_writeFd = rval.m_writeFd; 60 rval.m_readFd = 0; 61 rval.m_writeFd = 0; 62 } 63 ~Pipe() { 64 if (m_readFd) 65 close(m_readFd); 66 if (m_writeFd) 67 close(m_writeFd); 68 } 69 void signal() { 70 bool val = true; 71 int error = write(m_writeFd, &val, sizeof(val)); 72 ASSERT_TRUE(error >= 0); 73 } 74 void wait() { 75 bool val = false; 76 int error = read(m_readFd, &val, sizeof(val)); 77 ASSERT_TRUE(error >= 0); 78 } 79 template<typename T> void send(const T& v) { 80 int error = write(m_writeFd, &v, sizeof(T)); 81 ASSERT_TRUE(error >= 0); 82 } 83 template<typename T> void recv(T& v) { 84 int error = read(m_readFd, &v, sizeof(T)); 85 ASSERT_TRUE(error >= 0); 86 } 87 static tuple<Pipe, Pipe> createPipePair() { 88 int a[2]; 89 int b[2]; 90 91 int error1 = pipe(a); 92 int error2 = pipe(b); 93 ASSERT_TRUE(error1 >= 0); 94 ASSERT_TRUE(error2 >= 0); 95 96 return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1])); 97 } 98 }; 99 100 static const uint32_t num_buckets = 128; 101 static const uint64_t max_time_bucket = 50ull * 1000000; 102 static const uint64_t time_per_bucket = max_time_bucket / num_buckets; 103 static constexpr float time_per_bucket_ms = time_per_bucket / 1.0E6; 104 105 struct ProcResults { 106 uint64_t m_best = max_time_bucket; 107 uint64_t m_worst = 0; 108 uint32_t m_buckets[num_buckets] = {0}; 109 uint64_t m_transactions = 0; 110 uint64_t m_total_time = 0; 111 112 // Add a new latency data point and update the aggregation info 113 // e.g. best/worst/total_time. 114 void add_time(uint64_t time) { 115 m_buckets[min(time, max_time_bucket - 1) / time_per_bucket] += 1; 116 m_best = min(time, m_best); 117 m_worst = max(time, m_worst); 118 m_transactions += 1; 119 m_total_time += time; 120 } 121 // Combine two sets of latency data points and update the aggregation info. 122 static ProcResults combine(const ProcResults& a, const ProcResults& b) { 123 ProcResults ret; 124 for (uint32_t i = 0; i < num_buckets; i++) { 125 ret.m_buckets[i] = a.m_buckets[i] + b.m_buckets[i]; 126 } 127 ret.m_worst = max(a.m_worst, b.m_worst); 128 ret.m_best = min(a.m_best, b.m_best); 129 ret.m_transactions = a.m_transactions + b.m_transactions; 130 ret.m_total_time = a.m_total_time + b.m_total_time; 131 return ret; 132 } 133 // Calculate and report the final aggregated results. 134 void dump() { 135 double best = (double) m_best / 1.0E6; 136 double worst = (double) m_worst / 1.0E6; 137 double average = (double) m_total_time / m_transactions / 1.0E6; 138 cout << "average:" 139 << average 140 << "ms worst:" 141 << worst 142 << "ms best:" 143 << best 144 << "ms" 145 << endl; 146 147 uint64_t cur_total = 0; 148 for (uint32_t i = 0; i < num_buckets; i++) { 149 float cur_time = time_per_bucket_ms * i + 0.5f * time_per_bucket_ms; 150 if ((cur_total < 0.5f * m_transactions) 151 && (cur_total + m_buckets[i] >= 0.5f * m_transactions)) { 152 cout << "50%: " << cur_time << " "; 153 } 154 if ((cur_total < 0.9f * m_transactions) 155 && (cur_total + m_buckets[i] >= 0.9f * m_transactions)) { 156 cout << "90%: " << cur_time << " "; 157 } 158 if ((cur_total < 0.95f * m_transactions) 159 && (cur_total + m_buckets[i] >= 0.95f * m_transactions)) { 160 cout << "95%: " << cur_time << " "; 161 } 162 if ((cur_total < 0.99f * m_transactions) 163 && (cur_total + m_buckets[i] >= 0.99f * m_transactions)) { 164 cout << "99%: " << cur_time << " "; 165 } 166 cur_total += m_buckets[i]; 167 } 168 cout << endl; 169 170 } 171 }; 172 173 string generateServiceName(int num) { 174 string serviceName = "hwbinderService" + to_string(num); 175 return serviceName; 176 } 177 178 void service_fx(const string &serviceName, Pipe p) { 179 // Start service. 180 sp<IBenchmark> server = IBenchmark::getService(serviceName, true); 181 ALOGD("Registering %s", serviceName.c_str()); 182 status_t status = server->registerAsService(serviceName); 183 if (status != ::android::OK) { 184 ALOGE("Failed to register service %s", serviceName.c_str()); 185 exit(EXIT_FAILURE); 186 } 187 188 ALOGD("Starting %s", serviceName.c_str()); 189 190 // Signal service started to master and wait to exit. 191 p.signal(); 192 p.wait(); 193 exit(EXIT_SUCCESS); 194 } 195 196 void worker_fx( 197 int num, 198 int iterations, 199 int service_count, 200 bool get_stub, 201 Pipe p) { 202 srand(num); 203 p.signal(); 204 p.wait(); 205 206 // Get references to test services. 207 vector<sp<IBenchmark>> workers; 208 209 for (int i = 0; i < service_count; i++) { 210 sp<IBenchmark> service = IBenchmark::getService( 211 generateServiceName(i), get_stub); 212 ASSERT_TRUE(service != NULL); 213 if (get_stub) { 214 ASSERT_TRUE(!service->isRemote()); 215 } else { 216 ASSERT_TRUE(service->isRemote()); 217 } 218 workers.push_back(service); 219 } 220 221 ProcResults results; 222 chrono::time_point<chrono::high_resolution_clock> start, end; 223 // Prepare data to IPC 224 hidl_vec<uint8_t> data_vec; 225 data_vec.resize(16); 226 for (size_t i = 0; i < data_vec.size(); i++) { 227 data_vec[i] = i; 228 } 229 // Run the benchmark. 230 for (int i = 0; i < iterations; i++) { 231 // Randomly pick a service. 232 int target = rand() % service_count; 233 234 start = chrono::high_resolution_clock::now(); 235 Return<void> ret = workers[target]->sendVec(data_vec, [&](const auto &) {}); 236 if (!ret.isOk()) { 237 cout << "thread " << num << " failed status: " 238 << ret.description() << endl; 239 exit(EXIT_FAILURE); 240 } 241 end = chrono::high_resolution_clock::now(); 242 243 uint64_t cur_time = uint64_t( 244 chrono::duration_cast<chrono::nanoseconds>(end - start).count()); 245 results.add_time(cur_time); 246 } 247 // Signal completion to master and wait. 248 p.signal(); 249 p.wait(); 250 251 // Send results to master and wait for go to exit. 252 p.send(results); 253 p.wait(); 254 255 exit (EXIT_SUCCESS); 256 } 257 258 Pipe make_service(string service_name) { 259 auto pipe_pair = Pipe::createPipePair(); 260 pid_t pid = fork(); 261 if (pid) { 262 /* parent */ 263 return move(get<0>(pipe_pair)); 264 } else { 265 /* child */ 266 service_fx(service_name, move(get<1>(pipe_pair))); 267 /* never get here */ 268 return move(get<0>(pipe_pair)); 269 } 270 } 271 272 Pipe make_worker(int num, int iterations, int service_count, bool get_stub) { 273 auto pipe_pair = Pipe::createPipePair(); 274 pid_t pid = fork(); 275 if (pid) { 276 /* parent */ 277 return move(get<0>(pipe_pair)); 278 } else { 279 /* child */ 280 worker_fx(num, iterations, service_count, get_stub, 281 move(get<1>(pipe_pair))); 282 /* never get here */ 283 return move(get<0>(pipe_pair)); 284 } 285 } 286 287 void wait_all(vector<Pipe>& v) { 288 for (size_t i = 0; i < v.size(); i++) { 289 v[i].wait(); 290 } 291 } 292 293 void signal_all(vector<Pipe>& v) { 294 for (size_t i = 0; i < v.size(); i++) { 295 v[i].signal(); 296 } 297 } 298 299 int main(int argc, char *argv[]) { 300 setenv("TREBLE_TESTING_OVERRIDE", "true", true); 301 302 enum HwBinderMode { 303 kBinderize = 0, 304 kPassthrough = 1, 305 }; 306 HwBinderMode mode = HwBinderMode::kBinderize; 307 308 // Num of workers. 309 int workers = 2; 310 // Num of services. 311 int services = -1; 312 int iterations = 10000; 313 314 vector<Pipe> worker_pipes; 315 vector<Pipe> service_pipes; 316 317 // Parse arguments. 318 for (int i = 1; i < argc; i++) { 319 if (string(argv[i]) == "-m") { 320 if (!strcmp(argv[i + 1], "PASSTHROUGH")) { 321 mode = HwBinderMode::kPassthrough; 322 } 323 i++; 324 continue; 325 } 326 if (string(argv[i]) == "-w") { 327 workers = atoi(argv[i + 1]); 328 i++; 329 continue; 330 } 331 if (string(argv[i]) == "-i") { 332 iterations = atoi(argv[i + 1]); 333 i++; 334 continue; 335 } 336 if (string(argv[i]) == "-s") { 337 services = atoi(argv[i + 1]); 338 i++; 339 continue; 340 } 341 } 342 // If service number is not provided, set it the same as the worker number. 343 if (services == -1) { 344 services = workers; 345 } 346 if (mode == HwBinderMode::kBinderize) { 347 // Create services. 348 vector<pid_t> pIds; 349 for (int i = 0; i < services; i++) { 350 string serviceName = generateServiceName(i); 351 cout << "creating service: " << serviceName << endl; 352 service_pipes.push_back(make_service(serviceName)); 353 } 354 // Wait until all services are up. 355 wait_all(service_pipes); 356 } 357 358 // Create workers (test clients). 359 bool get_stub = mode == HwBinderMode::kBinderize ? false : true; 360 for (int i = 0; i < workers; i++) { 361 worker_pipes.push_back(make_worker(i, iterations, services, get_stub)); 362 } 363 // Wait untill all workers are ready. 364 wait_all(worker_pipes); 365 366 // Run the workers and wait for completion. 367 chrono::time_point<chrono::high_resolution_clock> start, end; 368 cout << "waiting for workers to complete" << endl; 369 start = chrono::high_resolution_clock::now(); 370 signal_all(worker_pipes); 371 wait_all(worker_pipes); 372 end = chrono::high_resolution_clock::now(); 373 374 // Calculate overall throughput. 375 double iterations_per_sec = double(iterations * workers) 376 / (chrono::duration_cast < chrono::nanoseconds 377 > (end - start).count() / 1.0E9); 378 cout << "iterations per sec: " << iterations_per_sec << endl; 379 380 // Collect all results from the workers. 381 cout << "collecting results" << endl; 382 signal_all(worker_pipes); 383 ProcResults tot_results; 384 for (int i = 0; i < workers; i++) { 385 ProcResults tmp_results; 386 worker_pipes[i].recv(tmp_results); 387 tot_results = ProcResults::combine(tot_results, tmp_results); 388 } 389 tot_results.dump(); 390 391 if (mode == HwBinderMode::kBinderize) { 392 // Kill all the services. 393 cout << "killing services" << endl; 394 signal_all(service_pipes); 395 for (int i = 0; i < services; i++) { 396 int status; 397 wait(&status); 398 if (status != 0) { 399 cout << "nonzero child status" << status << endl; 400 } 401 } 402 } 403 // Kill all the workers. 404 cout << "killing workers" << endl; 405 signal_all(worker_pipes); 406 for (int i = 0; i < workers; i++) { 407 int status; 408 wait(&status); 409 if (status != 0) { 410 cout << "nonzero child status" << status << endl; 411 } 412 } 413 return 0; 414 } 415