/* * 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 #include #include #include #include /** * 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 MultiStateCounter { uint16_t stateCount; state_t currentState; time_t lastStateChangeTimestamp; T emptyValue; T lastValue; time_t lastUpdateTimestamp; T deltaValue; bool isEnabled; struct State { time_t timeInStateSinceUpdate; T counter; }; State* states; public: MultiStateCounter(uint16_t stateCount, const T& emptyValue); virtual ~MultiStateCounter(); void setEnabled(bool enabled, time_t timestamp); void setState(state_t state, time_t timestamp); void setValue(state_t state, const T& value); /** * Updates the value by distributing the delta from the previously set value * among states according to their respective time-in-state. * Returns the delta from the previously set value. */ const T& updateValue(const T& value, time_t timestamp); /** * Updates the value by distributing the specified increment among states according * to their respective time-in-state. */ void incrementValue(const T& increment, time_t timestamp); /** * Adds the specified increment to the value for the current state, without affecting * the last updated value or timestamp. Ignores partial time-in-state: the entirety of * the increment is given to the current state. */ void addValue(const T& increment); void reset(); uint16_t getStateCount(); 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 MultiStateCounter::MultiStateCounter(uint16_t stateCount, const T& emptyValue) : stateCount(stateCount), currentState(0), lastStateChangeTimestamp(-1), emptyValue(emptyValue), lastValue(emptyValue), lastUpdateTimestamp(-1), deltaValue(emptyValue), isEnabled(true) { states = new State[stateCount]; for (int i = 0; i < stateCount; i++) { states[i].timeInStateSinceUpdate = 0; states[i].counter = emptyValue; } } template MultiStateCounter::~MultiStateCounter() { delete[] states; }; template void MultiStateCounter::setEnabled(bool enabled, time_t timestamp) { if (enabled == isEnabled) { return; } if (isEnabled) { // Confirm the current state for the side-effect of updating the time-in-state // counter for the current state. setState(currentState, timestamp); isEnabled = false; } else { // If the counter is being enabled with an out-of-order timestamp, just push back // the timestamp to avoid having the situation where // timeInStateSinceUpdate > timeSinceUpdate if (timestamp < lastUpdateTimestamp) { timestamp = lastUpdateTimestamp; } if (lastStateChangeTimestamp >= 0) { lastStateChangeTimestamp = timestamp; } isEnabled = true; } } template void MultiStateCounter::setState(state_t state, time_t timestamp) { if (isEnabled && lastStateChangeTimestamp >= 0 && lastUpdateTimestamp >= 0) { // If the update arrived out-of-order, just push back the timestamp to // avoid having the situation where timeInStateSinceUpdate > timeSinceUpdate if (timestamp < lastUpdateTimestamp) { timestamp = lastUpdateTimestamp; } 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 const T& MultiStateCounter::updateValue(const T& value, time_t timestamp) { T* returnValue = &emptyValue; // If the counter is disabled, we ignore the update, except when the counter got disabled after // the previous update, in which case we still need to pick up the residual delta. if (isEnabled || lastUpdateTimestamp < lastStateChangeTimestamp) { // If the update arrived out of order, just push back the timestamp to // avoid having the situation where timeInStateSinceUpdate > timeSinceUpdate if (timestamp < lastStateChangeTimestamp) { timestamp = lastStateChangeTimestamp; } // Confirm the current state for the side-effect of updating the time-in-state // counter for the current state. setState(currentState, timestamp); if (lastUpdateTimestamp >= 0) { if (timestamp > lastUpdateTimestamp) { if (delta(lastValue, value, &deltaValue)) { returnValue = &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()); for (int i = 0; i < stateCount; i++) { states[i].timeInStateSinceUpdate = 0; } } } else if (timestamp < lastUpdateTimestamp) { ALOGE("updateValue is called with an earlier timestamp: %lu, previous: %lu\n", (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp); for (int i = 0; i < stateCount; i++) { states[i].timeInStateSinceUpdate = 0; } } } } lastValue = value; lastUpdateTimestamp = timestamp; return *returnValue; } template void MultiStateCounter::incrementValue(const T& increment, time_t timestamp) { T newValue = lastValue; add(&newValue, increment, 1 /* numerator */, 1 /* denominator */); updateValue(newValue, timestamp); } template void MultiStateCounter::addValue(const T& value) { if (!isEnabled) { return; } add(&states[currentState].counter, value, 1 /* numerator */, 1 /* denominator */); } template void MultiStateCounter::reset() { lastStateChangeTimestamp = -1; lastUpdateTimestamp = -1; for (int i = 0; i < stateCount; i++) { states[i].timeInStateSinceUpdate = 0; states[i].counter = emptyValue; } } template uint16_t MultiStateCounter::getStateCount() { return stateCount; } template const T& MultiStateCounter::getCount(state_t state) { return states[state].counter; } template std::string MultiStateCounter::toString() { std::stringstream str; str << "["; for (int i = 0; i < stateCount; i++) { if (i != 0) { str << ", "; } 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"; } if (!isEnabled) { str << " disabled"; } return str.str(); } } // namespace battery } // namespace android