| /* |
| * 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(); |