diff options
author | Howard Chen <howardsoc@google.com> | 2017-05-24 17:11:54 +0800 |
---|---|---|
committer | Howard Chen <howardsoc@google.com> | 2017-05-26 15:46:50 +0800 |
commit | a2962f59fafd7a96ba081735f18cda87ddf6f8f6 (patch) | |
tree | 68b7983335cf731e555caf8725a3cc9a5189ac30 /vts | |
parent | db88e3d7b8593e66811f94580855d5796604b3b8 (diff) | |
download | libhwbinder-a2962f59fafd7a96ba081735f18cda87ddf6f8f6.tar.gz |
Extract PerfTest.[h|cpp] from Latency.cpp
This patch
* extracts PerfTest.h| PerfTest.cpp which includes
- Results for latency statistics
- Pipe for IPC
- PResulls for process statistics
* meets the coding style according to g3 style
* More comments and usage example
Test: pixel/oc-dev/libhwbinder_latency -raw_data -i 100
Bug: 38252294
Change-Id: I5c9760291895c7f53e334bce18c6231b52d4d74e
Diffstat (limited to 'vts')
-rw-r--r-- | vts/performance/Android.mk | 2 | ||||
-rw-r--r-- | vts/performance/Latency.cpp | 399 | ||||
-rw-r--r-- | vts/performance/PerfTest.cpp | 213 | ||||
-rw-r--r-- | vts/performance/PerfTest.h | 155 |
4 files changed, 453 insertions, 316 deletions
diff --git a/vts/performance/Android.mk b/vts/performance/Android.mk index 80c9377..b238715 100644 --- a/vts/performance/Android.mk +++ b/vts/performance/Android.mk @@ -83,7 +83,7 @@ LOCAL_MODULE := libhwbinder_latency LOCAL_MODULE_TAGS := eng tests -LOCAL_SRC_FILES := Latency.cpp +LOCAL_SRC_FILES := Latency.cpp PerfTest.cpp LOCAL_SHARED_LIBRARIES := \ libhwbinder \ libhidlbase \ diff --git a/vts/performance/Latency.cpp b/vts/performance/Latency.cpp index d861e31..a4c0751 100644 --- a/vts/performance/Latency.cpp +++ b/vts/performance/Latency.cpp @@ -14,28 +14,18 @@ * limitations under the License. */ #include <android/hardware/tests/libhwbinder/1.0/IScheduleTest.h> -#include <cstdio> -#include <cstdlib> -#include <cstring> +#include <hidl/LegacySupport.h> +#include <pthread.h> +#include <sys/wait.h> #include <fstream> #include <iomanip> #include <iostream> -#include <list> #include <string> -#include <tuple> -#include <vector> - -#include <hidl/LegacySupport.h> -#include <pthread.h> -#include <sys/wait.h> -#include <unistd.h> - -using namespace std; -using namespace android; -using namespace android::hardware; - -using android::hardware::tests::libhwbinder::V1_0::IScheduleTest; +#include "PerfTest.h" +#ifdef ASSERT +#undef ASSERT +#endif #define ASSERT(cond) \ do { \ if (!(cond)) { \ @@ -44,57 +34,67 @@ using android::hardware::tests::libhwbinder::V1_0::IScheduleTest; } \ } while (0) -vector<sp<IScheduleTest> > services; - -// the ratio that the service is synced on the same cpu beyond -// GOOD_SYNC_MIN is considered as good -#define GOOD_SYNC_MIN (0.6) - -#define DUMP_PRICISION 2 +#define REQUIRE(stat) \ + do { \ + int cond = (stat); \ + ASSERT(cond); \ + } while (0) -string trace_path = "/sys/kernel/debug/tracing"; +using android::hardware::registerPassthroughServiceImplementation; +using android::hardware::tests::libhwbinder::V1_0::IScheduleTest; +using android::sp; +using std::cerr; +using std::cout; +using std::endl; +using std::fstream; +using std::left; +using std::ios; +using std::get; +using std::move; +using std::to_string; +using std::setprecision; +using std::setw; +using std::string; +using std::vector; + +static vector<sp<IScheduleTest> > services; // default arguments -bool dump_raw_data = false; -int no_pair = 1; -int iterations = 100; -int verbose = 0; -int is_tracing; -bool pass_through = false; +static bool dump_raw_data = false; +static int no_pair = 1; +static int iterations = 100; +static int verbose = 0; +static int is_tracing; +static bool pass_through = false; // the deadline latency that we are interested in -uint64_t deadline_us = 2500; +static uint64_t deadline_us = 2500; static bool traceIsOn() { fstream file; - file.open(trace_path + "/tracing_on", ios::in); + file.open(TRACE_PATH "/tracing_on", ios::in); char on; file >> on; file.close(); return on == '1'; } -static void traceStop() { - ofstream file; - file.open(trace_path + "/tracing_on", ios::out | ios::trunc); - file << '0' << endl; - file.close(); -} - static int threadGetPri() { - struct sched_param param; + sched_param param; int policy; - ASSERT(!pthread_getschedparam(pthread_self(), &policy, ¶m)); + REQUIRE(!pthread_getschedparam(pthread_self(), &policy, ¶m)); return param.sched_priority; } static void threadDumpPri(const char* prefix) { - struct sched_param param; + sched_param param; int policy; - if (!verbose) return; + if (!verbose) { + return; + } cout << "--------------------------------------------------" << endl; cout << setw(12) << left << prefix << " pid: " << getpid() << " tid: " << gettid() << " cpu: " << sched_getcpu() << endl; - ASSERT(!pthread_getschedparam(pthread_self(), &policy, ¶m)); + REQUIRE(!pthread_getschedparam(pthread_self(), &policy, ¶m)); string s = (policy == SCHED_OTHER) ? "SCHED_OTHER" @@ -103,244 +103,13 @@ static void threadDumpPri(const char* prefix) { return; } -// This IPC class is widely used in binder/hwbinder tests. -// The common usage is the main process to create the Pipe and forks. -// Both parent and child hold a object and each wait() on parent -// needs a signal() on the child to wake up and vice versa. -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(error >= 0); - }; - void wait() { - bool val = false; - int error = read(m_readFd, &val, sizeof(val)); - ASSERT(error >= 0); - } - template <typename T> - void send(const T& v) { - int error = write(m_writeFd, &v, sizeof(T)); - ASSERT(error >= 0); - } - template <typename T> - void recv(T& v) { - int error = read(m_readFd, &v, sizeof(T)); - ASSERT(error >= 0); - } - static tuple<Pipe, Pipe> createPipePair() { - int a[2]; - int b[2]; - - int error1 = pipe(a); - int error2 = pipe(b); - ASSERT(error1 >= 0); - ASSERT(error2 >= 0); - - return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1])); - } -}; - -typedef chrono::time_point<chrono::high_resolution_clock> Tick; - -static inline Tick tickNow() { - return chrono::high_resolution_clock::now(); -} - -static inline uint64_t tickNano(Tick& sta, Tick& end) { - return uint64_t(chrono::duration_cast<chrono::nanoseconds>(end - sta).count()); -} - -// statistics of latency -class Results { - 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; - - uint64_t m_best = 0xffffffffffffffffULL; - uint64_t m_worst = 0; - uint64_t m_transactions = 0; - uint64_t m_total_time = 0; - uint64_t m_miss = 0; - uint32_t m_buckets[num_buckets] = {0}; ///< statistics for the distribution - list<uint64_t>* raw_data = nullptr; ///< list for raw-data - bool tracing = false; ///< halt the trace log on a deadline miss - bool raw_dump = false; ///< record the raw data for the dump after - - public: - void setTrace(bool _tracing) { tracing = _tracing; } - inline uint64_t getTransactions() { return m_transactions; } - inline bool missDeadline(uint64_t nano) { return nano > deadline_us * 1000; } - // Combine two sets of latency data points and update the aggregation info. - static Results combine(const Results& a, const Results& b) { - Results 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_miss = a.m_miss + b.m_miss; - ret.m_total_time = a.m_total_time + b.m_total_time; - return ret; - } - // add a new transaction latency record - void addTime(uint64_t nano) { - m_buckets[min(nano, max_time_bucket - 1) / time_per_bucket] += 1; - m_best = min(nano, m_best); - m_worst = max(nano, m_worst); - if (raw_dump) { - raw_data->push_back(nano); - } - m_transactions += 1; - m_total_time += nano; - if (missDeadline(nano)) m_miss++; - if (missDeadline(nano) && tracing) { - // There might be multiple process pair running the test concurrently - // each may execute following statements and only the first one actually - // stop the trace and any traceStop() afterthen has no effect. - traceStop(); - cout << endl; - cout << "deadline triggered: halt & stop trace" << endl; - cout << "log:" + trace_path + "/trace" << endl; - cout << endl; - exit(1); - } - } - // setup raw dump - void setupRawData() { - raw_dump = true; - if (raw_data == nullptr) - raw_data = new list<uint64_t>; - else - raw_data->clear(); - } - // dump and flush the raw data - void flushRawData() { - if (raw_dump) { - bool first = true; - cout << "["; - for (auto nano : *raw_data) { - cout << (first ? "" : ",") << to_string(nano); - first = false; - } - cout << "]," << endl; - delete raw_data; - raw_data = nullptr; - } - } - // dump average, best, worst latency in json - 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; - int W = DUMP_PRICISION + 2; - cout << std::setprecision(DUMP_PRICISION) << "{ \"avg\":" << setw(W) << left << average - << ", \"wst\":" << setw(W) << left << worst << ", \"bst\":" << setw(W) << left << best - << ", \"miss\":" << left << m_miss - << ", \"meetR\":" << setprecision(DUMP_PRICISION + 3) << left - << (1.0 - (double)m_miss / m_transactions) << "}"; - } - // dump latency distribution in json - void dumpDistribution() { - uint64_t cur_total = 0; - cout << "{ "; - cout << std::setprecision(DUMP_PRICISION + 3); - 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 << "\"p50\":" << cur_time << ", "; - } - if ((cur_total < 0.9f * m_transactions) && - (cur_total + m_buckets[i] >= 0.9f * m_transactions)) { - cout << "\"p90\":" << cur_time << ", "; - } - if ((cur_total < 0.95f * m_transactions) && - (cur_total + m_buckets[i] >= 0.95f * m_transactions)) { - cout << "\"p95\":" << cur_time << ", "; - } - if ((cur_total < 0.99f * m_transactions) && - (cur_total + m_buckets[i] >= 0.99f * m_transactions)) { - cout << "\"p99\": " << cur_time; - } - cur_total += m_buckets[i]; - } - cout << "}"; - } -}; -// statistics of a process pair -class PResults { - public: - static PResults combine(const PResults& a, const PResults& b) { - PResults ret; - ret.no_inherent = a.no_inherent + b.no_inherent; - ret.no_sync = a.no_sync + b.no_sync; - ret.other = Results::combine(a.other, b.other); - ret.fifo = Results::combine(a.fifo, b.fifo); - return ret; - } - - int no_inherent = 0; ///< #transactions that does not inherit priority - int no_sync = 0; ///< #transactions that are not synced - Results other; ///< statistics of CFS-other transactions - Results fifo; ///< statistics of RT-fifo transactions - PResults() { - fifo.setTrace(is_tracing); - if (dump_raw_data) fifo.setupRawData(); - } - - // dump and flush the raw data - void flushRawData() { fifo.flushRawData(); } - - void dump() { - int no_trans = other.getTransactions() + fifo.getTransactions(); - double sync_ratio = (1.0 - (double)(no_sync) / no_trans); - cout << "{\"SYNC\":\"" << ((sync_ratio > GOOD_SYNC_MIN) ? "GOOD" : "POOR") << "\"," - << "\"S\":" << (no_trans - no_sync) << ",\"I\":" << no_trans << "," - << "\"R\":" << sync_ratio << "," << endl; - cout << " \"other_ms\":"; - other.dump(); - cout << "," << endl; - cout << " \"fifo_ms\": "; - fifo.dump(); - cout << "," << endl; - cout << " \"otherdis\":"; - other.dumpDistribution(); - cout << "," << endl; - cout << " \"fifodis\": "; - fifo.dumpDistribution(); - cout << endl; - cout << "}," << endl; - } -}; - -struct threadArg { +struct ThreadArg { void* result; ///< pointer to PResults int target; ///< the terget service number }; static void* threadStart(void* p) { - threadArg* priv = (threadArg*)p; + ThreadArg* priv = (ThreadArg*)p; int target = priv->target; PResults* presults = (PResults*)priv->result; Tick sta, end; @@ -348,40 +117,37 @@ static void* threadStart(void* p) { threadDumpPri("fifo-caller"); uint32_t call_sta = (threadGetPri() << 16) | sched_getcpu(); sp<IScheduleTest> service = services[target]; - asm("" ::: "memory"); - sta = tickNow(); + TICK_NOW(sta); uint32_t ret = service->send(verbose, call_sta); - end = tickNow(); - asm("" ::: "memory"); - presults->fifo.addTime(tickNano(sta, end)); + TICK_NOW(end); + presults->fifo.addTime(tickDiffNS(sta, end)); - presults->no_inherent += (ret >> 16) & 0xffff; - presults->no_sync += ret & 0xffff; + presults->nNotInherent += (ret >> 16) & 0xffff; + presults->nNotSync += ret & 0xffff; return 0; } // create a fifo thread to transact and wait it to finished static void threadTransaction(int target, PResults* presults) { - threadArg thread_arg; - + ThreadArg thread_arg; void* dummy; pthread_t thread; pthread_attr_t attr; - struct sched_param param; + sched_param param; thread_arg.target = target; thread_arg.result = presults; - ASSERT(!pthread_attr_init(&attr)); - ASSERT(!pthread_attr_setschedpolicy(&attr, SCHED_FIFO)); + REQUIRE(!pthread_attr_init(&attr)); + REQUIRE(!pthread_attr_setschedpolicy(&attr, SCHED_FIFO)); param.sched_priority = sched_get_priority_max(SCHED_FIFO); - ASSERT(!pthread_attr_setschedparam(&attr, ¶m)); - ASSERT(!pthread_create(&thread, &attr, threadStart, &thread_arg)); - ASSERT(!pthread_join(thread, &dummy)); + REQUIRE(!pthread_attr_setschedparam(&attr, ¶m)); + REQUIRE(!pthread_create(&thread, &attr, threadStart, &thread_arg)); + REQUIRE(!pthread_join(thread, &dummy)); } static void serviceFx(const string& serviceName, Pipe p) { // Start service. if (registerPassthroughServiceImplementation<IScheduleTest>(serviceName) != ::android::OK) { - cout << "Failed to register service " << serviceName.c_str() << endl; + cerr << "Failed to register service " << serviceName.c_str() << endl; exit(EXIT_FAILURE); } // tell main I'm init-ed @@ -410,6 +176,11 @@ static Pipe makeServiceProces(string service_name) { static void clientFx(int num, int server_count, int iterations, Pipe p) { PResults presults; + presults.fifo.setTracingMode(is_tracing, deadline_us); + if (dump_raw_data) { + presults.fifo.setupRawData(); + } + for (int i = 0; i < server_count; i++) { sp<IScheduleTest> service = IScheduleTest::getService("hwbinderService" + to_string(i), pass_through); @@ -427,7 +198,7 @@ static void clientFx(int num, int server_count, int iterations, Pipe p) { p.wait(); // Client for each pair iterates here - // each iterations contains exatcly 2 transactions + // each iterations contains exactly 2 transactions for (int i = 0; i < iterations; i++) { Tick sta, end; // the target is paired to make it easier to diagnose @@ -440,14 +211,12 @@ static void clientFx(int num, int server_count, int iterations, Pipe p) { uint32_t call_sta = (threadGetPri() << 16) | sched_getcpu(); sp<IScheduleTest> service = services[target]; // 2. transaction by other thread - asm("" ::: "memory"); - sta = tickNow(); + TICK_NOW(sta); uint32_t ret = service->send(verbose, call_sta); - end = tickNow(); - asm("" ::: "memory"); - presults.other.addTime(tickNano(sta, end)); - presults.no_inherent += (ret >> 16) & 0xffff; - presults.no_sync += ret & 0xffff; + TICK_NOW(end); + presults.other.addTime(tickDiffNS(sta, end)); + presults.nNotInherent += (ret >> 16) & 0xffff; + presults.nNotSync += ret & 0xffff; } // tell main i'm done p.signal(); @@ -459,7 +228,8 @@ static void clientFx(int num, int server_count, int iterations, Pipe p) { presults.flushRawData(); } cout.flush(); - p.send(presults); + int sent = p.send(presults); + ASSERT(sent >= 0); // wait for kill p.wait(); @@ -504,10 +274,8 @@ static void help() { cout << "-trace # halt the trace on a dealine hit" << endl; exit(0); } -// This test is modified from frameworks/native/libs/binder/tests/sch-dbg.cpp -// The difference is sch-dbg tests binder transaction and this one test -// HwBinder transaction. -// Test + +// Test: // // libhwbinder_latency -i 1 -v // libhwbinder_latency -i 10000 -pair 4 @@ -576,9 +344,9 @@ int main(int argc, char** argv) { waitAll(service_pipes); } if (is_tracing && !traceIsOn()) { - cout << "trace is not running" << endl; - cout << "check " << trace_path + "/tracing_on" << endl; - cout << "use atrace --async_start first" << endl; + cerr << "trace is not running" << endl; + cerr << "check " << TRACE_PATH "/tracing_on" << endl; + cerr << "use atrace --async_start first" << endl; exit(EXIT_FAILURE); } threadDumpPri("main"); @@ -606,7 +374,8 @@ int main(int argc, char** argv) { PResults total, presults[no_pair]; for (int i = 0; i < no_pair; i++) { client_pipes[i].signal(); - client_pipes[i].recv(presults[i]); + int recvd = client_pipes[i].recv(presults[i]); + ASSERT(recvd >= 0); total = PResults::combine(total, presults[i]); } cout << "\"ALL\":"; @@ -619,13 +388,13 @@ int main(int argc, char** argv) { if (!pass_through) { signalAll(service_pipes); } - int no_inherent = 0; + int nNotInherent = 0; for (int i = 0; i < no_pair; i++) { - no_inherent += presults[i].no_inherent; + nNotInherent += presults[i].nNotInherent; } - cout << "\"inheritance\": " << (no_inherent == 0 ? "\"PASS\"" : "\"FAIL\"") << endl; + cout << "\"inheritance\": " << (nNotInherent == 0 ? "\"PASS\"" : "\"FAIL\"") << endl; cout << "}" << endl; // kill all signalAll(client_pipes); - return -no_inherent; + return -nNotInherent; } diff --git a/vts/performance/PerfTest.cpp b/vts/performance/PerfTest.cpp new file mode 100644 index 0000000..0641df4 --- /dev/null +++ b/vts/performance/PerfTest.cpp @@ -0,0 +1,213 @@ +/* + * 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. + */ + +#include "PerfTest.h" +#include <fstream> +#include <iomanip> +#include <iostream> +#include <string> + +#ifdef ASSERT +#undef ASSERT +#endif +#define ASSERT(cond) \ + do { \ + if (!(cond)) { \ + cerr << __func__ << ":" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +// the ratio that the service is synced on the same cpu beyond +// GOOD_SYNC_MIN is considered as good +#define GOOD_SYNC_MIN (0.6) + +// the precision used for cout to dump float +#define DUMP_PRICISION 2 + +using std::cerr; +using std::cout; +using std::endl; +using std::left; +using std::ios; +using std::min; +using std::max; +using std::to_string; +using std::setprecision; +using std::setw; +using std::ofstream; +using std::make_tuple; + +tuple<Pipe, Pipe> Pipe::createPipePair() { + int a[2]; + int b[2]; + + int error1 = pipe(a); + int error2 = pipe(b); + ASSERT(error1 >= 0); + ASSERT(error2 >= 0); + + return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1])); +} + +Pipe::Pipe(Pipe&& rval) noexcept { + fd_read_ = rval.fd_read_; + fd_write_ = rval.fd_write_; + rval.fd_read_ = 0; + rval.fd_write_ = 0; +} + +Pipe::~Pipe() { + if (fd_read_) { + close(fd_read_); + } + if (fd_write_) { + close(fd_write_); + } +} + +Results Results::combine(const Results& a, const Results& b) { + Results ret; + for (uint32_t i = 0; i < kNumBuckets; i++) { + ret.buckets_[i] = a.buckets_[i] + b.buckets_[i]; + } + ret.worst_ = max(a.worst_, b.worst_); + ret.best_ = min(a.best_, b.best_); + ret.transactions_ = a.transactions_ + b.transactions_; + ret.miss_ = a.miss_ + b.miss_; + ret.total_time_ = a.total_time_ + b.total_time_; + return ret; +} + +static void traceStop() { + ofstream file; + file.open(TRACE_PATH "/tracing_on", ios::out | ios::trunc); + file << '0' << endl; + file.close(); +} + +void Results::addTime(uint64_t nano) { + buckets_[min(nano, kMaxTimeBucket - 1) / kTimePerBucket] += 1; + best_ = min(nano, best_); + worst_ = max(nano, worst_); + if (raw_dump_) { + raw_data_->push_back(nano); + } + transactions_ += 1; + total_time_ += nano; + if (missDeadline(nano)) { + miss_++; + if (tracing_) { + // There might be multiple process pair running the test concurrently + // each may execute following statements and only the first one actually + // stop the trace and any traceStop() after then has no effect. + traceStop(); + cerr << endl; + cerr << "deadline triggered: halt & stop trace" << endl; + cerr << "log:" TRACE_PATH "/trace" << endl; + cerr << endl; + exit(EXIT_FAILURE); + } + } +} + +void Results::setupRawData() { + raw_dump_ = true; + if (raw_data_ == nullptr) { + raw_data_ = new list<uint64_t>; + } else { + raw_data_->clear(); + } +} + +void Results::flushRawData() { + if (raw_dump_) { + bool first = true; + cout << "["; + for (auto nano : *raw_data_) { + cout << (first ? "" : ",") << to_string(nano); + first = false; + } + cout << "]," << endl; + delete raw_data_; + raw_data_ = nullptr; + } +} + +void Results::dump() const { + double best = (double)best_ / 1.0E6; + double worst = (double)worst_ / 1.0E6; + double average = (double)total_time_ / transactions_ / 1.0E6; + int W = DUMP_PRICISION + 2; + cout << std::setprecision(DUMP_PRICISION) << "{ \"avg\":" << setw(W) << left << average + << ", \"wst\":" << setw(W) << left << worst << ", \"bst\":" << setw(W) << left << best + << ", \"miss\":" << left << miss_ << ", \"meetR\":" << setprecision(DUMP_PRICISION + 3) + << left << (1.0 - (double)miss_ / transactions_) << "}"; +} + +void Results::dumpDistribution() const { + uint64_t cur_total = 0; + cout << "{ "; + cout << std::setprecision(DUMP_PRICISION + 3); + for (uint32_t i = 0; i < kNumBuckets; i++) { + float cur_time = kTimePerBucketMS * i + 0.5f * kTimePerBucketMS; + float accumulation = cur_total + buckets_[i]; + if ((cur_total < 0.5f * transactions_) && (accumulation >= 0.5f * transactions_)) { + cout << "\"p50\":" << cur_time << ", "; + } + if ((cur_total < 0.9f * transactions_) && (accumulation >= 0.9f * transactions_)) { + cout << "\"p90\":" << cur_time << ", "; + } + if ((cur_total < 0.95f * transactions_) && (accumulation >= 0.95f * transactions_)) { + cout << "\"p95\":" << cur_time << ", "; + } + if ((cur_total < 0.99f * transactions_) && (accumulation >= 0.99f * transactions_)) { + cout << "\"p99\": " << cur_time; + } + cur_total += buckets_[i]; + } + cout << "}"; +} + +PResults PResults::combine(const PResults& a, const PResults& b) { + PResults ret; + ret.nNotInherent = a.nNotInherent + b.nNotInherent; + ret.nNotSync = a.nNotSync + b.nNotSync; + ret.other = Results::combine(a.other, b.other); + ret.fifo = Results::combine(a.fifo, b.fifo); + return ret; +} + +void PResults::dump() const { + int no_trans = other.getTransactions() + fifo.getTransactions(); + double sync_ratio = (1.0 - (double)nNotSync / no_trans); + cout << "{\"SYNC\":\"" << ((sync_ratio > GOOD_SYNC_MIN) ? "GOOD" : "POOR") << "\"," + << "\"S\":" << (no_trans - nNotSync) << ",\"I\":" << no_trans << "," + << "\"R\":" << sync_ratio << "," << endl; + cout << " \"other_ms\":"; + other.dump(); + cout << "," << endl; + cout << " \"fifo_ms\": "; + fifo.dump(); + cout << "," << endl; + cout << " \"otherdis\":"; + other.dumpDistribution(); + cout << "," << endl; + cout << " \"fifodis\": "; + fifo.dumpDistribution(); + cout << endl; + cout << "}," << endl; +} diff --git a/vts/performance/PerfTest.h b/vts/performance/PerfTest.h new file mode 100644 index 0000000..3fa4b8d --- /dev/null +++ b/vts/performance/PerfTest.h @@ -0,0 +1,155 @@ +/* + * 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. + */ +#ifndef HWBINDER_PERF_TEST_H +#define HWBINDER_PERF_TEST_H + +#include <unistd.h> +#include <chrono> +#include <list> +#include <tuple> + +#define TRACE_PATH "/sys/kernel/debug/tracing" + +using std::list; +using std::tuple; + +// Pipe is a object used for IPC between parent process and child process. +// This IPC class is widely used in binder/hwbinder tests. +// The common usage is the main process to create the Pipe and forks. +// Both parent and child hold a object. Each recv() on one side requires +// a send() on the other side to unblock. +class Pipe { + public: + static tuple<Pipe, Pipe> createPipePair(); + Pipe(Pipe&& rval); + ~Pipe(); + inline void signal() { + bool val = true; + send(val); + } + inline void wait() { + bool val = false; + recv(val); + } + + // write a data struct + template <typename T> + int send(const T& v) { + return write(fd_write_, &v, sizeof(T)); + } + // read a data struct + template <typename T> + int recv(T& v) { + return read(fd_read_, &v, sizeof(T)); + } + + private: + int fd_read_; // file descriptor to read + int fd_write_; // file descriptor to write + Pipe(int read_fd, int write_fd) : fd_read_{read_fd}, fd_write_{write_fd} {} + Pipe(const Pipe&) = delete; + Pipe& operator=(const Pipe&) = delete; + Pipe& operator=(const Pipe&&) = delete; +}; + +// statistics of latency +// common usage: +// +// Results r; +// Tick sta, end; +// TICK_NOW(sta); +// ... do something ... +// TICK_NOW(end); +// r.addTime(tickDiffNS(sta, end)); +// +// r.dump(); +// r.dumpDistribution(); +// +class Results { + public: + // enable the deadline miss detection which stops the trace recording after + // a transaction latency > deadline_us_ is detected. + void setTracingMode(bool tracing, uint64_t deadline_us) { + tracing_ = tracing; + deadline_us_ = deadline_us; + } + inline uint64_t getTransactions() const { return transactions_; } + inline bool missDeadline(uint64_t nano) const { return nano > deadline_us_ * 1000; } + // Combine two sets of latency data points and update the aggregation info. + static Results combine(const Results& a, const Results& b); + // add a new transaction latency record + void addTime(uint64_t nano); + // prepare for raw data recording, it may allocate resources which requires + // a flushRawData() to release + void setupRawData(); + // dump the raw data and release the resource + void flushRawData(); + // dump average, best, worst latency in json + void dump() const; + // dump latency distribution in json + void dumpDistribution() const; + + private: + static const uint32_t kNumBuckets = 128; + static const uint64_t kMaxTimeBucket = 50ull * 1000000; + static const uint64_t kTimePerBucket = kMaxTimeBucket / kNumBuckets; + static constexpr float kTimePerBucketMS = kTimePerBucket / 1.0E6; + uint64_t best_ = 0xffffffffffffffffULL; // best transaction latency in ns. + uint64_t worst_ = 0; // worst transaction latency in ns. + uint64_t transactions_ = 0; // number of transactions + uint64_t total_time_ = 0; // total transaction time + uint64_t miss_ = 0; // number of transactions whose latency > deadline + uint32_t buckets_[kNumBuckets] = {0}; // statistics for the distribution + list<uint64_t>* raw_data_ = nullptr; // list for raw-data + bool tracing_ = false; // halt the trace log on a deadline miss + bool raw_dump_ = false; // record the raw data for the dump after + uint64_t deadline_us_ = 2500; // latency deadline in us. +}; + +// statistics of a process pair +class PResults { + public: + static PResults combine(const PResults& a, const PResults& b); + int nNotInherent = 0; ///< #transactions that does not inherit priority + int nNotSync = 0; ///< #transactions that are not synced + Results other; ///< statistics of CFS-other transactions + Results fifo; ///< statistics of RT-fifo transactions + // dump and flush the raw data + inline void flushRawData() { fifo.flushRawData(); } + // dump in json + void dump() const; +}; + +// Tick keeps timestamp +typedef std::chrono::time_point<std::chrono::high_resolution_clock> Tick; + +// get current timestamp as a Tick +static inline Tick tickNow() { + return std::chrono::high_resolution_clock::now(); +} + +#define TICK_NOW(_tick) \ + do { \ + asm volatile("" ::: "memory"); \ + _tick = tickNow(); \ + asm volatile("" ::: "memory"); \ + } while (0) + +// get nano seconds between sta & end +static inline uint64_t tickDiffNS(Tick& sta, Tick& end) { + return uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(end - sta).count()); +} +#endif |