From 5effd856f5e7ed5041c843fd16b08b8ee8414f03 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Wed, 11 Aug 2021 14:55:58 -0700 Subject: 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 --- libs/battery/LongArrayMultiStateCounterTest.cpp | 54 +++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 libs/battery/LongArrayMultiStateCounterTest.cpp (limited to 'libs/battery/LongArrayMultiStateCounterTest.cpp') 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 +#include "LongArrayMultiStateCounter.h" + +namespace android { +namespace battery { + +class LongArrayMultiStateCounterTest : public testing::Test {}; + +TEST_F(LongArrayMultiStateCounterTest, stateChange) { + LongArrayMultiStateCounter testCounter(2, 0, std::vector(4), 1000); + testCounter.setState(1, 2000); + testCounter.updateValue(std::vector({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({50, 100, 150, 200}), testCounter.getCount(0)); + EXPECT_EQ(std::vector({50, 100, 150, 200}), testCounter.getCount(1)); +} + +TEST_F(LongArrayMultiStateCounterTest, accumulation) { + LongArrayMultiStateCounter testCounter(2, 0, std::vector(4), 1000); + testCounter.setState(1, 2000); + testCounter.updateValue(std::vector({100, 200, 300, 400}), 3000); + testCounter.setState(0, 4000); + testCounter.updateValue(std::vector({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({130, 180, 230, 280}), testCounter.getCount(0)); + EXPECT_EQ(std::vector({70, 120, 170, 220}), testCounter.getCount(1)); +} + +} // namespace battery +} // namespace android -- cgit v1.2.3-59-g8ed1b From 12aaf8e35911cd5f036917de9a6f388d611ed51c Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Fri, 3 Sep 2021 19:07:23 -0700 Subject: Simplify initialization and add setValue to support parceling Bug: 197162116 Test: atest libbattery_test Change-Id: I4278206eab049d714c5278e6b10ba3155e17142f --- libs/battery/LongArrayMultiStateCounter.cpp | 2 +- libs/battery/LongArrayMultiStateCounterTest.cpp | 19 ++++- libs/battery/MultiStateCounter.h | 107 +++++++++++++++--------- libs/battery/MultiStateCounterTest.cpp | 35 ++++++-- 4 files changed, 116 insertions(+), 47 deletions(-) (limited to 'libs/battery/LongArrayMultiStateCounterTest.cpp') diff --git a/libs/battery/LongArrayMultiStateCounter.cpp b/libs/battery/LongArrayMultiStateCounter.cpp index 68e088394a..125cfaffa4 100644 --- a/libs/battery/LongArrayMultiStateCounter.cpp +++ b/libs/battery/LongArrayMultiStateCounter.cpp @@ -62,7 +62,7 @@ void LongArrayMultiStateCounter::add(std::vector* value1, template <> std::string LongArrayMultiStateCounter::valueToString(const std::vector& v) const { std::stringstream s; - s << "{ "; + s << "{"; bool first = true; for (uint64_t n : v) { if (!first) { diff --git a/libs/battery/LongArrayMultiStateCounterTest.cpp b/libs/battery/LongArrayMultiStateCounterTest.cpp index 24cb437eaa..e4e6b2a49f 100644 --- a/libs/battery/LongArrayMultiStateCounterTest.cpp +++ b/libs/battery/LongArrayMultiStateCounterTest.cpp @@ -24,7 +24,9 @@ namespace battery { class LongArrayMultiStateCounterTest : public testing::Test {}; TEST_F(LongArrayMultiStateCounterTest, stateChange) { - LongArrayMultiStateCounter testCounter(2, 0, std::vector(4), 1000); + LongArrayMultiStateCounter testCounter(2, std::vector(4)); + testCounter.updateValue(std::vector({0, 0, 0, 0}), 1000); + testCounter.setState(0, 1000); testCounter.setState(1, 2000); testCounter.updateValue(std::vector({100, 200, 300, 400}), 3000); @@ -34,7 +36,9 @@ TEST_F(LongArrayMultiStateCounterTest, stateChange) { } TEST_F(LongArrayMultiStateCounterTest, accumulation) { - LongArrayMultiStateCounter testCounter(2, 0, std::vector(4), 1000); + LongArrayMultiStateCounter testCounter(2, std::vector(4)); + testCounter.updateValue(std::vector({0, 0, 0, 0}), 1000); + testCounter.setState(0, 1000); testCounter.setState(1, 2000); testCounter.updateValue(std::vector({100, 200, 300, 400}), 3000); testCounter.setState(0, 4000); @@ -50,5 +54,16 @@ TEST_F(LongArrayMultiStateCounterTest, accumulation) { EXPECT_EQ(std::vector({70, 120, 170, 220}), testCounter.getCount(1)); } +TEST_F(LongArrayMultiStateCounterTest, toString) { + LongArrayMultiStateCounter testCounter(2, std::vector(4)); + testCounter.updateValue(std::vector({0, 0, 0, 0}), 1000); + testCounter.setState(0, 1000); + testCounter.setState(1, 2000); + testCounter.updateValue(std::vector({100, 200, 300, 400}), 3000); + + EXPECT_STREQ("[0: {50, 100, 150, 200}, 1: {50, 100, 150, 200}] updated: 3000 currentState: 1", + testCounter.toString().c_str()); +} + } // namespace battery } // namespace android diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h index 9f56b29dfb..40de068a95 100644 --- a/libs/battery/MultiStateCounter.h +++ b/libs/battery/MultiStateCounter.h @@ -51,15 +51,18 @@ class MultiStateCounter { State* states; public: - MultiStateCounter(uint16_t stateCount, state_t initialState, const T& emptyValue, - time_t timestamp); + MultiStateCounter(uint16_t stateCount, const T& emptyValue); virtual ~MultiStateCounter(); void setState(state_t state, time_t timestamp); + void setValue(state_t state, const T& value); + void updateValue(const T& value, time_t timestamp); + uint16_t getStateCount(); + const T& getCount(state_t state); std::string toString(); @@ -86,14 +89,13 @@ private: // Since MultiStateCounter is a template, the implementation must be inlined. template -MultiStateCounter::MultiStateCounter(uint16_t stateCount, state_t initialState, - const T& emptyValue, time_t timestamp) +MultiStateCounter::MultiStateCounter(uint16_t stateCount, const T& emptyValue) : stateCount(stateCount), - currentState(initialState), - lastStateChangeTimestamp(timestamp), + currentState(0), + lastStateChangeTimestamp(-1), emptyValue(emptyValue), lastValue(emptyValue), - lastUpdateTimestamp(timestamp), + lastUpdateTimestamp(-1), deltaValue(emptyValue) { states = new State[stateCount]; for (int i = 0; i < stateCount; i++) { @@ -109,53 +111,68 @@ MultiStateCounter::~MultiStateCounter() { template void MultiStateCounter::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; + if (lastStateChangeTimestamp >= 0) { + 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 +void MultiStateCounter::setValue(state_t state, const T& value) { + states[state].counter = value; +} + template void MultiStateCounter::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; + if (lastUpdateTimestamp >= 0) { + 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 { - 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); } - } 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 +uint16_t MultiStateCounter::getStateCount() { + return stateCount; +} + template const T& MultiStateCounter::getCount(state_t state) { return states[state].counter; @@ -164,17 +181,29 @@ const T& MultiStateCounter::getCount(state_t state) { template std::string MultiStateCounter::toString() { std::stringstream str; - str << "currentState: " << currentState - << " lastStateChangeTimestamp: " << lastStateChangeTimestamp - << " lastUpdateTimestamp: " << lastUpdateTimestamp << " states: ["; + str << "["; for (int i = 0; i < stateCount; i++) { if (i != 0) { str << ", "; } - str << i << ": time: " << states[i].timeInStateSinceUpdate - << " counter: " << valueToString(states[i].counter); + str << i << ": " << valueToString(states[i].counter); + if (states[i].timeInStateSinceUpdate > 0) { + str << " timeInStateSinceUpdate: " << states[i].timeInStateSinceUpdate; + } } str << "]"; + if (lastUpdateTimestamp >= 0) { + str << " updated: " << lastUpdateTimestamp; + } + if (lastStateChangeTimestamp >= 0) { + str << " currentState: " << currentState; + if (lastStateChangeTimestamp > lastUpdateTimestamp) { + str << " stateChanged: " << lastStateChangeTimestamp; + } + } else { + str << " currentState: none"; + } + return str.str(); } diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp index 942d5cadf5..87c80c53d3 100644 --- a/libs/battery/MultiStateCounterTest.cpp +++ b/libs/battery/MultiStateCounterTest.cpp @@ -49,8 +49,9 @@ std::string DoubleMultiStateCounter::valueToString(const double& v) const { class MultiStateCounterTest : public testing::Test {}; TEST_F(MultiStateCounterTest, constructor) { - DoubleMultiStateCounter testCounter(3, 1, 0, 1000); - testCounter.setState(1, 2000); + DoubleMultiStateCounter testCounter(3, 0); + testCounter.updateValue(0, 0); + testCounter.setState(1, 0); testCounter.updateValue(3.14, 3000); EXPECT_DOUBLE_EQ(0, testCounter.getCount(0)); @@ -59,7 +60,9 @@ TEST_F(MultiStateCounterTest, constructor) { } TEST_F(MultiStateCounterTest, stateChange) { - DoubleMultiStateCounter testCounter(3, 1, 0, 0); + DoubleMultiStateCounter testCounter(3, 0); + testCounter.updateValue(0, 0); + testCounter.setState(1, 0); testCounter.setState(2, 1000); testCounter.updateValue(6.0, 3000); @@ -69,7 +72,9 @@ TEST_F(MultiStateCounterTest, stateChange) { } TEST_F(MultiStateCounterTest, timeAdjustment_setState) { - DoubleMultiStateCounter testCounter(3, 1, 0, 0); + DoubleMultiStateCounter testCounter(3, 0); + testCounter.updateValue(0, 0); + testCounter.setState(1, 0); testCounter.setState(2, 2000); // Time moves back @@ -88,7 +93,9 @@ TEST_F(MultiStateCounterTest, timeAdjustment_setState) { } TEST_F(MultiStateCounterTest, timeAdjustment_updateValue) { - DoubleMultiStateCounter testCounter(1, 0, 0, 0); + DoubleMultiStateCounter testCounter(1, 0); + testCounter.updateValue(0, 0); + testCounter.setState(0, 0); testCounter.updateValue(6.0, 2000); // Time moves back. The negative delta from 2000 to 1000 is ignored @@ -101,5 +108,23 @@ TEST_F(MultiStateCounterTest, timeAdjustment_updateValue) { EXPECT_DOUBLE_EQ(9.0, testCounter.getCount(0)); } +TEST_F(MultiStateCounterTest, toString) { + DoubleMultiStateCounter testCounter(2, 0); + + EXPECT_STREQ("[0: 0.000000, 1: 0.000000] currentState: none", testCounter.toString().c_str()); + + testCounter.updateValue(0, 0); + testCounter.setState(1, 0); + testCounter.setState(1, 2000); + EXPECT_STREQ("[0: 0.000000, 1: 0.000000 timeInStateSinceUpdate: 2000]" + " updated: 0 currentState: 1 stateChanged: 2000", + testCounter.toString().c_str()); + + testCounter.updateValue(3.14, 3000); + + EXPECT_STREQ("[0: 0.000000, 1: 3.140000] updated: 3000 currentState: 1", + testCounter.toString().c_str()); +} + } // namespace battery } // namespace android -- cgit v1.2.3-59-g8ed1b