blob: 5c7c9f46c1feb80fd5e1a7f986b0c373b9031302 [file]
/*
* Copyright (C) 2020 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 "VibratorHalControllerBenchmarks"
#include <android/binder_process.h>
#include <benchmark/benchmark.h>
#include <vibratorservice/VibratorHalController.h>
#include <future>
using ::aidl::android::hardware::vibrator::CompositeEffect;
using ::aidl::android::hardware::vibrator::CompositePrimitive;
using ::aidl::android::hardware::vibrator::Effect;
using ::aidl::android::hardware::vibrator::EffectStrength;
using ::benchmark::Counter;
using ::benchmark::Fixture;
using ::benchmark::kMicrosecond;
using ::benchmark::State;
using ::benchmark::internal::Benchmark;
using std::chrono::milliseconds;
using namespace android;
using namespace std::chrono_literals;
// Fixed number of iterations for benchmarks that trigger a vibration on the loop.
// They require slow cleanup to ensure a stable state on each run and less noisy metrics.
static constexpr auto VIBRATION_ITERATIONS = 500;
// Timeout to wait for vibration callback completion.
static constexpr auto VIBRATION_CALLBACK_TIMEOUT = 100ms;
// Max duration the vibrator can be turned on, in milliseconds.
static constexpr auto MAX_ON_DURATION_MS = milliseconds(UINT16_MAX);
// Helper to wait for the vibrator to become idle between vibrate bench iterations.
class HalCallback {
public:
HalCallback(std::function<void()>&& waitFn, std::function<void()>&& completeFn)
: mWaitFn(std::move(waitFn)), mCompleteFn(std::move(completeFn)) {}
~HalCallback() = default;
std::function<void()> completeFn() const { return mCompleteFn; }
void waitForComplete() const { mWaitFn(); }
private:
std::function<void()> mWaitFn;
std::function<void()> mCompleteFn;
};
// Helper for vibration callbacks, kept by the Fixture until all pending callbacks are done.
class HalCallbacks {
public:
HalCallback next() {
std::unique_lock<std::mutex> lock(mMutex);
auto id = mCurrentId++;
mPendingPromises[id] = std::promise<void>();
mPendingFutures[id] = mPendingPromises[id].get_future(); // Can only be called once.
return HalCallback([&, id]() { waitForComplete(id); }, [&, id]() { onComplete(id); });
}
void onComplete(int32_t id) {
std::unique_lock<std::mutex> lock(mMutex);
auto promise = mPendingPromises.find(id);
if (promise != mPendingPromises.end()) {
promise->second.set_value();
mPendingPromises.erase(promise);
}
}
void waitForComplete(int32_t id) {
// Wait until the HAL has finished processing previous vibration before starting a new one,
// so the HAL state is consistent on each run and metrics are less noisy. Some of the newest
// HAL implementations are waiting on previous vibration cleanup and might be significantly
// slower, so make sure we measure vibrations on a clean slate.
if (mPendingFutures[id].wait_for(VIBRATION_CALLBACK_TIMEOUT) == std::future_status::ready) {
mPendingFutures.erase(id);
}
}
void waitForPending() {
// Wait for pending callbacks from the test, possibly skipped with error.
for (auto& [id, future] : mPendingFutures) {
future.wait_for(VIBRATION_CALLBACK_TIMEOUT);
}
mPendingFutures.clear();
{
std::unique_lock<std::mutex> lock(mMutex);
mPendingPromises.clear();
}
}
private:
std::mutex mMutex;
std::map<int32_t, std::promise<void>> mPendingPromises GUARDED_BY(mMutex);
std::map<int32_t, std::future<void>> mPendingFutures;
int32_t mCurrentId;
};
class VibratorBench : public Fixture {
public:
void SetUp(State& /*state*/) override {
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
mController.init();
}
void TearDown(State& /*state*/) override {
turnVibratorOff();
disableExternalControl();
mCallbacks.waitForPending();
}
static void DefaultConfig(Benchmark* b) { b->Unit(kMicrosecond); }
static void DefaultArgs(Benchmark* /*b*/) {
// none
}
protected:
vibrator::HalController mController;
HalCallbacks mCallbacks;
static void SlowBenchConfig(Benchmark* b) { b->Iterations(VIBRATION_ITERATIONS); }
auto getOtherArg(const State& state, std::size_t index) const { return state.range(index + 0); }
vibrator::HalResult<void> turnVibratorOff() {
return mController.doWithRetry<void>([](auto hal) { return hal->off(); }, "off");
}
vibrator::HalResult<void> disableExternalControl() {
auto disableExternalControlFn = [](auto hal) { return hal->setExternalControl(false); };
return mController.doWithRetry<void>(disableExternalControlFn, "setExternalControl false");
}
bool shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities query, State& state) {
auto result = mController.getInfo().capabilities;
if (result.isFailed()) {
state.SkipWithError(result.errorMessage());
return true;
}
if (!result.isOk()) {
state.SkipWithMessage("capability result is unsupported");
return true;
}
if ((result.value() & query) != query) {
state.SkipWithMessage("missing capability");
return true;
}
return false;
}
template <class R>
bool shouldSkipWithError(const vibrator::HalFunction<vibrator::HalResult<R>>& halFn,
const char* label, State& state) {
return shouldSkipWithError(mController.doWithRetry<R>(halFn, label), state);
}
template <class R>
bool shouldSkipWithError(const vibrator::HalResult<R>& result, State& state) {
if (result.isFailed()) {
state.SkipWithError(result.errorMessage());
return true;
}
return false;
}
};
class SlowVibratorBench : public VibratorBench {
public:
static void DefaultConfig(Benchmark* b) {
VibratorBench::DefaultConfig(b);
SlowBenchConfig(b);
}
};
#define BENCHMARK_WRAPPER(fixt, test, code) \
BENCHMARK_DEFINE_F(fixt, test) \
/* NOLINTNEXTLINE */ \
(State& state){code} BENCHMARK_REGISTER_F(fixt, test) \
->Apply(fixt::DefaultConfig) \
->Apply(fixt::DefaultArgs)
BENCHMARK_WRAPPER(VibratorBench, init, {
for (auto _ : state) {
// Setup
state.PauseTiming();
vibrator::HalController controller;
state.ResumeTiming();
// Test
controller.init();
}
});
BENCHMARK_WRAPPER(VibratorBench, initCached, {
// First call to cache values.
mController.init();
for (auto _ : state) {
mController.init();
}
});
BENCHMARK_WRAPPER(VibratorBench, ping, {
auto pingFn = [](auto hal) { return hal->ping(); };
for (auto _ : state) {
if (shouldSkipWithError<void>(pingFn, "ping", state)) {
return;
}
}
});
BENCHMARK_WRAPPER(VibratorBench, tryReconnect, {
for (auto _ : state) {
mController.tryReconnect();
}
});
BENCHMARK_WRAPPER(SlowVibratorBench, on, {
auto duration = MAX_ON_DURATION_MS;
for (auto _ : state) {
// Setup
state.PauseTiming();
auto cb = mCallbacks.next();
auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
state.ResumeTiming();
// Test
if (shouldSkipWithError<void>(onFn, "on", state)) {
return;
}
// Cleanup
state.PauseTiming();
if (shouldSkipWithError(turnVibratorOff(), state)) {
return;
}
cb.waitForComplete();
state.ResumeTiming();
}
});
BENCHMARK_WRAPPER(SlowVibratorBench, off, {
auto duration = MAX_ON_DURATION_MS;
for (auto _ : state) {
// Setup
state.PauseTiming();
auto cb = mCallbacks.next();
auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
if (shouldSkipWithError<void>(onFn, "on", state)) {
return;
}
auto offFn = [&](auto hal) { return hal->off(); };
state.ResumeTiming();
// Test
if (shouldSkipWithError<void>(offFn, "off", state)) {
return;
}
// Cleanup
state.PauseTiming();
cb.waitForComplete();
state.ResumeTiming();
}
});
BENCHMARK_WRAPPER(VibratorBench, setAmplitude, {
if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
return;
}
auto duration = MAX_ON_DURATION_MS;
auto amplitude = 1.0f;
auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
auto onFn = [&](auto hal) { return hal->on(duration, [&]() {}); };
if (shouldSkipWithError<void>(onFn, "on", state)) {
return;
}
for (auto _ : state) {
if (shouldSkipWithError<void>(setAmplitudeFn, "setAmplitude", state)) {
return;
}
}
});
BENCHMARK_WRAPPER(VibratorBench, setExternalControl, {
if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
return;
}
auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
for (auto _ : state) {
// Test
if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
return;
}
// Cleanup
state.PauseTiming();
if (shouldSkipWithError(disableExternalControl(), state)) {
return;
}
state.ResumeTiming();
}
});
BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitude, {
auto externalAmplitudeControl = vibrator::Capabilities::EXTERNAL_CONTROL &
vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL;
if (shouldSkipWithMissingCapabilityMessage(externalAmplitudeControl, state)) {
return;
}
auto amplitude = 1.0f;
auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
return;
}
for (auto _ : state) {
if (shouldSkipWithError<void>(setAmplitudeFn, "setExternalAmplitude", state)) {
return;
}
}
});
BENCHMARK_WRAPPER(VibratorBench, getInfo, {
for (auto _ : state) {
// Setup
state.PauseTiming();
vibrator::HalController controller;
controller.init();
state.ResumeTiming();
controller.getInfo();
}
});
BENCHMARK_WRAPPER(VibratorBench, getInfoCached, {
// First call to cache values.
mController.getInfo();
for (auto _ : state) {
mController.getInfo();
}
});
class VibratorEffectsBench : public VibratorBench {
public:
static void DefaultArgs(Benchmark* b) {
vibrator::HalController controller;
auto effectsResult = controller.getInfo().supportedEffects;
if (!effectsResult.isOk()) {
return;
}
std::vector<Effect> supported = effectsResult.value();
b->ArgNames({"Effect", "Strength"});
if (supported.empty()) {
b->Args({static_cast<long>(-1), static_cast<long>(-1)});
return;
}
for (const auto& effect : ndk::enum_range<Effect>()) {
if (std::find(supported.begin(), supported.end(), effect) == supported.end()) {
continue;
}
for (const auto& strength : ndk::enum_range<EffectStrength>()) {
b->Args({static_cast<long>(effect), static_cast<long>(strength)});
}
}
}
protected:
bool hasArgs(const State& state) const { return this->getOtherArg(state, 0) >= 0; }
auto getEffect(const State& state) const {
return static_cast<Effect>(this->getOtherArg(state, 0));
}
auto getStrength(const State& state) const {
return static_cast<EffectStrength>(this->getOtherArg(state, 1));
}
};
class SlowVibratorEffectsBench : public VibratorEffectsBench {
public:
static void DefaultConfig(Benchmark* b) {
VibratorBench::DefaultConfig(b);
SlowBenchConfig(b);
}
};
BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, {
if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
return;
}
if (!hasArgs(state)) {
state.SkipWithMessage("missing args");
return;
}
int32_t id = 1;
auto effect = getEffect(state);
auto strength = getStrength(state);
auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
for (auto _ : state) {
// Test
if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
return;
}
// Cleanup
state.PauseTiming();
if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
return;
}
state.ResumeTiming();
}
});
BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, {
if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
return;
}
if (!hasArgs(state)) {
state.SkipWithMessage("missing args");
return;
}
int32_t id = 1;
auto effect = getEffect(state);
auto strength = getStrength(state);
auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
for (auto _ : state) {
// Setup
state.PauseTiming();
if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
return;
}
state.ResumeTiming();
// Test
if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
return;
}
}
});
BENCHMARK_WRAPPER(SlowVibratorEffectsBench, performEffect, {
if (!hasArgs(state)) {
state.SkipWithMessage("missing args");
return;
}
auto effect = getEffect(state);
auto strength = getStrength(state);
for (auto _ : state) {
// Setup
state.PauseTiming();
auto cb = mCallbacks.next();
auto performFn = [&](auto hal) {
return hal->performEffect(effect, strength, cb.completeFn());
};
state.ResumeTiming();
// Test
if (shouldSkipWithError<milliseconds>(performFn, "performEffect", state)) {
return;
}
// Cleanup
state.PauseTiming();
if (shouldSkipWithError(turnVibratorOff(), state)) {
return;
}
cb.waitForComplete();
state.ResumeTiming();
}
});
class SlowVibratorPrimitivesBench : public VibratorBench {
public:
static void DefaultConfig(Benchmark* b) {
VibratorBench::DefaultConfig(b);
SlowBenchConfig(b);
}
static void DefaultArgs(Benchmark* b) {
vibrator::HalController controller;
auto primitivesResult = controller.getInfo().supportedPrimitives;
if (!primitivesResult.isOk()) {
return;
}
std::vector<CompositePrimitive> supported = primitivesResult.value();
b->ArgNames({"Primitive"});
if (supported.empty()) {
b->Args({static_cast<long>(-1)});
return;
}
for (const auto& primitive : ndk::enum_range<CompositePrimitive>()) {
if (std::find(supported.begin(), supported.end(), primitive) == supported.end()) {
continue;
}
if (primitive == CompositePrimitive::NOOP) {
continue;
}
b->Args({static_cast<long>(primitive)});
}
}
protected:
bool hasArgs(const State& state) const { return this->getOtherArg(state, 0) >= 0; }
auto getPrimitive(const State& state) const {
return static_cast<CompositePrimitive>(this->getOtherArg(state, 0));
}
};
BENCHMARK_WRAPPER(SlowVibratorPrimitivesBench, performComposedEffect, {
if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::COMPOSE_EFFECTS, state)) {
return;
}
if (!hasArgs(state)) {
state.SkipWithMessage("missing args");
return;
}
CompositeEffect effect;
effect.primitive = getPrimitive(state);
effect.scale = 1.0f;
effect.delayMs = static_cast<int32_t>(0);
std::vector<CompositeEffect> effects = {effect};
for (auto _ : state) {
// Setup
state.PauseTiming();
auto cb = mCallbacks.next();
auto performFn = [&](auto hal) {
return hal->performComposedEffect(effects, cb.completeFn());
};
state.ResumeTiming();
// Test
if (shouldSkipWithError<milliseconds>(performFn, "performComposedEffect", state)) {
return;
}
// Cleanup
state.PauseTiming();
if (shouldSkipWithError(turnVibratorOff(), state)) {
return;
}
cb.waitForComplete();
state.ResumeTiming();
}
});
BENCHMARK_MAIN();