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/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 8bcb1e5..4dd4f79 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 @@
                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 0000000..c860324
--- /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 0000000..68e0883
--- /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 0000000..f3439f6
--- /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 0000000..24cb437
--- /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 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
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
new file mode 100644
index 0000000..942d5ca
--- /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