diff options
author | 2021-08-11 14:55:58 -0700 | |
---|---|---|
committer | 2021-08-20 18:29:32 -0700 | |
commit | 5effd856f5e7ed5041c843fd16b08b8ee8414f03 (patch) | |
tree | 8505f16e5d2b3d4ab5f2945d1df39a8ac71a9c5c | |
parent | e4343a09d7b4a468778e73c140ad8a2c0f751e0d (diff) |
Add MultiStateCounter
This native object is used to track values per-state. For example,
if the state changes from 0 to 1 between two updateValue calls,
the delta between the values is distributed to the states 0 and 1
in accordance with the time spent in those states.
Bug: 197162116
Test: atest libbattery_test
Change-Id: Ie304db5c93f4aa9676d12d0a8ab53b6867b24fff
-rw-r--r-- | PREUPLOAD.cfg | 2 | ||||
-rw-r--r-- | libs/battery/Android.bp | 37 | ||||
-rw-r--r-- | libs/battery/LongArrayMultiStateCounter.cpp | 79 | ||||
-rw-r--r-- | libs/battery/LongArrayMultiStateCounter.h | 29 | ||||
-rw-r--r-- | libs/battery/LongArrayMultiStateCounterTest.cpp | 54 | ||||
-rw-r--r-- | libs/battery/MultiStateCounter.h | 182 | ||||
-rw-r--r-- | libs/battery/MultiStateCounterTest.cpp | 105 |
7 files changed, 488 insertions, 0 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 8bcb1e5da5..4dd4f7999d 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,5 +1,6 @@ [Builtin Hooks] clang_format = true +bpfmt = true [Builtin Hooks Options] # Only turn on clang-format check for the following subfolders. @@ -26,6 +27,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp services/vibratorservice/ services/vr/ vulkan/ +bpfmt = -d [Hook Scripts] owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$" diff --git a/libs/battery/Android.bp b/libs/battery/Android.bp new file mode 100644 index 0000000000..c860324359 --- /dev/null +++ b/libs/battery/Android.bp @@ -0,0 +1,37 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_library { + name: "libbattery", + srcs: [ + "LongArrayMultiStateCounter.cpp", + ], + shared_libs: [ + "liblog", + ], + cflags: [ + "-Werror", + "-Wall", + "-Wextra", + ], + export_include_dirs: ["."], +} + +cc_test { + name: "libbattery_test", + srcs: [ + "MultiStateCounterTest.cpp", + "LongArrayMultiStateCounterTest.cpp", + ], + static_libs: ["libbattery"], + shared_libs: [ + "liblog", + ], + cflags: [ + "-Werror", + "-Wall", + "-Wextra", + ], +} diff --git a/libs/battery/LongArrayMultiStateCounter.cpp b/libs/battery/LongArrayMultiStateCounter.cpp new file mode 100644 index 0000000000..68e088394a --- /dev/null +++ b/libs/battery/LongArrayMultiStateCounter.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * Android BPF library - public API + * + * 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 "LongArrayMultiStateCounter.h" +#include <log/log.h> + +namespace android { +namespace battery { + +template <> +bool LongArrayMultiStateCounter::delta(const std::vector<uint64_t>& previousValue, + const std::vector<uint64_t>& newValue, + std::vector<uint64_t>* outValue) const { + size_t size = previousValue.size(); + if (newValue.size() != size) { + ALOGE("Incorrect array size: %d, should be %d", (int)newValue.size(), (int)size); + return false; + } + + bool is_delta_valid = true; + for (int i = size - 1; i >= 0; i--) { + if (newValue[i] >= previousValue[i]) { + (*outValue)[i] = newValue[i] - previousValue[i]; + } else { + (*outValue)[i] = 0; + is_delta_valid = false; + } + } + return is_delta_valid; +} + +template <> +void LongArrayMultiStateCounter::add(std::vector<uint64_t>* value1, + const std::vector<uint64_t>& value2, const uint64_t numerator, + const uint64_t denominator) const { + if (numerator != denominator) { + for (int i = value2.size() - 1; i >= 0; i--) { + // The caller ensures that denominator != 0 + (*value1)[i] += value2[i] * numerator / denominator; + } + } else { + for (int i = value2.size() - 1; i >= 0; i--) { + (*value1)[i] += value2[i]; + } + } +} + +template <> +std::string LongArrayMultiStateCounter::valueToString(const std::vector<uint64_t>& v) const { + std::stringstream s; + s << "{ "; + bool first = true; + for (uint64_t n : v) { + if (!first) { + s << ", "; + } + s << n; + first = false; + } + s << "}"; + return s.str(); +} + +} // namespace battery +} // namespace android diff --git a/libs/battery/LongArrayMultiStateCounter.h b/libs/battery/LongArrayMultiStateCounter.h new file mode 100644 index 0000000000..f3439f6a0c --- /dev/null +++ b/libs/battery/LongArrayMultiStateCounter.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * Android BPF library - public API + * + * 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. + */ + +#pragma once + +#include <vector> +#include "MultiStateCounter.h" + +namespace android { +namespace battery { + +typedef MultiStateCounter<std::vector<uint64_t>> LongArrayMultiStateCounter; + +} // namespace battery +} // namespace android diff --git a/libs/battery/LongArrayMultiStateCounterTest.cpp b/libs/battery/LongArrayMultiStateCounterTest.cpp new file mode 100644 index 0000000000..24cb437eaa --- /dev/null +++ b/libs/battery/LongArrayMultiStateCounterTest.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * Android BPF library - public API + * + * 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 <gtest/gtest.h> +#include "LongArrayMultiStateCounter.h" + +namespace android { +namespace battery { + +class LongArrayMultiStateCounterTest : public testing::Test {}; + +TEST_F(LongArrayMultiStateCounterTest, stateChange) { + LongArrayMultiStateCounter testCounter(2, 0, std::vector<uint64_t>(4), 1000); + testCounter.setState(1, 2000); + testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000); + + // Time was split in half between the two states, so the counts will be split 50:50 too + EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(0)); + EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(1)); +} + +TEST_F(LongArrayMultiStateCounterTest, accumulation) { + LongArrayMultiStateCounter testCounter(2, 0, std::vector<uint64_t>(4), 1000); + testCounter.setState(1, 2000); + testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000); + testCounter.setState(0, 4000); + testCounter.updateValue(std::vector<uint64_t>({200, 300, 400, 500}), 8000); + + // The first delta is split 50:50: + // 0: {50, 100, 150, 200} + // 1: {50, 100, 150, 200} + // The second delta is split 4:1 + // 0: {80, 80, 80, 80} + // 1: {20, 20, 20, 20} + EXPECT_EQ(std::vector<uint64_t>({130, 180, 230, 280}), testCounter.getCount(0)); + EXPECT_EQ(std::vector<uint64_t>({70, 120, 170, 220}), testCounter.getCount(1)); +} + +} // namespace battery +} // namespace android diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h new file mode 100644 index 0000000000..9f56b29dfb --- /dev/null +++ b/libs/battery/MultiStateCounter.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * Android BPF library - public API + * + * 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. + */ + +#pragma once + +#include <inttypes.h> +#include <log/log.h> +#include <time.h> +#include <sstream> +#include <string> + +/** + * An object that can track changes of some value over time, taking into account an additional + * dimension: the object's state. As the tracked value changes, the deltas are distributed + * among the object states in accordance with the time spent in those states. + */ +namespace android { +namespace battery { + +typedef uint16_t state_t; + +template <class T> +class MultiStateCounter { + uint16_t stateCount; + state_t currentState; + time_t lastStateChangeTimestamp; + T emptyValue; + T lastValue; + time_t lastUpdateTimestamp; + T deltaValue; + + struct State { + time_t timeInStateSinceUpdate; + T counter; + }; + + State* states; + +public: + MultiStateCounter(uint16_t stateCount, state_t initialState, const T& emptyValue, + time_t timestamp); + + virtual ~MultiStateCounter(); + + void setState(state_t state, time_t timestamp); + + void updateValue(const T& value, time_t timestamp); + + const T& getCount(state_t state); + + std::string toString(); + +private: + /** + * Subtracts previousValue from newValue and returns the result in outValue. + * Returns true iff the combination of previousValue and newValue is valid + * (newValue >= prevValue) + */ + bool delta(const T& previousValue, const T& newValue, T* outValue) const; + + /** + * Adds value2 to value1 and stores the result in value1. Denominator is + * guaranteed to be non-zero. + */ + void add(T* value1, const T& value2, const uint64_t numerator, + const uint64_t denominator) const; + + std::string valueToString(const T& value) const; +}; + +// ---------------------- MultiStateCounter Implementation ------------------------- +// Since MultiStateCounter is a template, the implementation must be inlined. + +template <class T> +MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, state_t initialState, + const T& emptyValue, time_t timestamp) + : stateCount(stateCount), + currentState(initialState), + lastStateChangeTimestamp(timestamp), + emptyValue(emptyValue), + lastValue(emptyValue), + lastUpdateTimestamp(timestamp), + deltaValue(emptyValue) { + states = new State[stateCount]; + for (int i = 0; i < stateCount; i++) { + states[i].timeInStateSinceUpdate = 0; + states[i].counter = emptyValue; + } +} + +template <class T> +MultiStateCounter<T>::~MultiStateCounter() { + delete[] states; +}; + +template <class T> +void MultiStateCounter<T>::setState(state_t state, time_t timestamp) { + if (timestamp >= lastStateChangeTimestamp) { + states[currentState].timeInStateSinceUpdate += timestamp - lastStateChangeTimestamp; + } else { + ALOGE("setState is called with an earlier timestamp: %lu, previous timestamp: %lu\n", + (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp); + // The accumulated durations have become unreliable. For example, if the timestamp + // sequence was 1000, 2000, 1000, 3000, if we accumulated the positive deltas, + // we would get 4000, which is greater than (last - first). This could lead to + // counts exceeding 100%. + for (int i = 0; i < stateCount; i++) { + states[i].timeInStateSinceUpdate = 0; + } + } + currentState = state; + lastStateChangeTimestamp = timestamp; +} + +template <class T> +void MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) { + // Confirm the current state for the side-effect of updating the time-in-state + // counter for the current state. + setState(currentState, timestamp); + + if (timestamp > lastUpdateTimestamp) { + if (delta(lastValue, value, &deltaValue)) { + time_t timeSinceUpdate = timestamp - lastUpdateTimestamp; + for (int i = 0; i < stateCount; i++) { + time_t timeInState = states[i].timeInStateSinceUpdate; + if (timeInState) { + add(&states[i].counter, deltaValue, timeInState, timeSinceUpdate); + states[i].timeInStateSinceUpdate = 0; + } + } + } else { + std::stringstream str; + str << "updateValue is called with a value " << valueToString(value) + << ", which is lower than the previous value " << valueToString(lastValue) << "\n"; + ALOGE("%s", str.str().c_str()); + } + } else if (timestamp < lastUpdateTimestamp) { + ALOGE("updateValue is called with an earlier timestamp: %lu, previous timestamp: %lu\n", + (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp); + } + lastValue = value; + lastUpdateTimestamp = timestamp; +} + +template <class T> +const T& MultiStateCounter<T>::getCount(state_t state) { + return states[state].counter; +} + +template <class T> +std::string MultiStateCounter<T>::toString() { + std::stringstream str; + str << "currentState: " << currentState + << " lastStateChangeTimestamp: " << lastStateChangeTimestamp + << " lastUpdateTimestamp: " << lastUpdateTimestamp << " states: ["; + for (int i = 0; i < stateCount; i++) { + if (i != 0) { + str << ", "; + } + str << i << ": time: " << states[i].timeInStateSinceUpdate + << " counter: " << valueToString(states[i].counter); + } + str << "]"; + return str.str(); +} + +} // namespace battery +} // namespace android diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp new file mode 100644 index 0000000000..942d5cadf5 --- /dev/null +++ b/libs/battery/MultiStateCounterTest.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * Android BPF library - public API + * + * 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 <gtest/gtest.h> +#include "MultiStateCounter.h" + +namespace android { +namespace battery { + +typedef MultiStateCounter<double> DoubleMultiStateCounter; + +template <> +bool DoubleMultiStateCounter::delta(const double& previousValue, const double& newValue, + double* outValue) const { + *outValue = newValue - previousValue; + return *outValue >= 0; +} + +template <> +void DoubleMultiStateCounter::add(double* value1, const double& value2, const uint64_t numerator, + const uint64_t denominator) const { + if (numerator != denominator) { + // The caller ensures that denominator != 0 + *value1 += value2 * numerator / denominator; + } else { + *value1 += value2; + } +} + +template <> +std::string DoubleMultiStateCounter::valueToString(const double& v) const { + return std::to_string(v); +} + +class MultiStateCounterTest : public testing::Test {}; + +TEST_F(MultiStateCounterTest, constructor) { + DoubleMultiStateCounter testCounter(3, 1, 0, 1000); + testCounter.setState(1, 2000); + testCounter.updateValue(3.14, 3000); + + EXPECT_DOUBLE_EQ(0, testCounter.getCount(0)); + EXPECT_DOUBLE_EQ(3.14, testCounter.getCount(1)); + EXPECT_DOUBLE_EQ(0, testCounter.getCount(2)); +} + +TEST_F(MultiStateCounterTest, stateChange) { + DoubleMultiStateCounter testCounter(3, 1, 0, 0); + testCounter.setState(2, 1000); + testCounter.updateValue(6.0, 3000); + + EXPECT_DOUBLE_EQ(0, testCounter.getCount(0)); + EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1)); + EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2)); +} + +TEST_F(MultiStateCounterTest, timeAdjustment_setState) { + DoubleMultiStateCounter testCounter(3, 1, 0, 0); + testCounter.setState(2, 2000); + + // Time moves back + testCounter.setState(1, 1000); + testCounter.updateValue(6.0, 3000); + + EXPECT_DOUBLE_EQ(0, testCounter.getCount(0)); + + // We were in state 1 from 0 to 2000, which was erased because the time moved back. + // Then from 1000 to 3000, so we expect the count to be 6 * (2000/3000) + EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(1)); + + // No time was effectively accumulated for state 2, because the timestamp moved back + // while we were in state 2. + EXPECT_DOUBLE_EQ(0, testCounter.getCount(2)); +} + +TEST_F(MultiStateCounterTest, timeAdjustment_updateValue) { + DoubleMultiStateCounter testCounter(1, 0, 0, 0); + testCounter.updateValue(6.0, 2000); + + // Time moves back. The negative delta from 2000 to 1000 is ignored + testCounter.updateValue(8.0, 1000); + testCounter.updateValue(11.0, 3000); + + // The total accumulated count is: + // 6.0 // For the period 0-2000 + // +(11.0-8.0) // For the period 1000-3000 + EXPECT_DOUBLE_EQ(9.0, testCounter.getCount(0)); +} + +} // namespace battery +} // namespace android |