summaryrefslogtreecommitdiff
path: root/vts
diff options
context:
space:
mode:
authorHoward Chen <howardsoc@google.com>2017-05-24 17:11:54 +0800
committerHoward Chen <howardsoc@google.com>2017-05-26 15:46:50 +0800
commita2962f59fafd7a96ba081735f18cda87ddf6f8f6 (patch)
tree68b7983335cf731e555caf8725a3cc9a5189ac30 /vts
parentdb88e3d7b8593e66811f94580855d5796604b3b8 (diff)
downloadlibhwbinder-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.mk2
-rw-r--r--vts/performance/Latency.cpp399
-rw-r--r--vts/performance/PerfTest.cpp213
-rw-r--r--vts/performance/PerfTest.h155
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, &param));
+ REQUIRE(!pthread_getschedparam(pthread_self(), &policy, &param));
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, &param));
+ REQUIRE(!pthread_getschedparam(pthread_self(), &policy, &param));
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, &param));
- ASSERT(!pthread_create(&thread, &attr, threadStart, &thread_arg));
- ASSERT(!pthread_join(thread, &dummy));
+ REQUIRE(!pthread_attr_setschedparam(&attr, &param));
+ 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