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
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
new file mode 100644
index 0000000..9f56b29
--- /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