317 lines
10 KiB
C++
317 lines
10 KiB
C++
/*
|
|
* 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;
|
|
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 <class T>
|
|
MultiStateCounter<T>::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 <class T>
|
|
MultiStateCounter<T>::~MultiStateCounter() {
|
|
delete[] states;
|
|
};
|
|
|
|
template <class T>
|
|
void MultiStateCounter<T>::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 <class T>
|
|
void MultiStateCounter<T>::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 <class T>
|
|
void MultiStateCounter<T>::setValue(state_t state, const T& value) {
|
|
states[state].counter = value;
|
|
}
|
|
|
|
template <class T>
|
|
const T& MultiStateCounter<T>::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 <class T>
|
|
void MultiStateCounter<T>::incrementValue(const T& increment, time_t timestamp) {
|
|
T newValue = lastValue;
|
|
add(&newValue, increment, 1 /* numerator */, 1 /* denominator */);
|
|
updateValue(newValue, timestamp);
|
|
}
|
|
|
|
template <class T>
|
|
void MultiStateCounter<T>::addValue(const T& value) {
|
|
if (!isEnabled) {
|
|
return;
|
|
}
|
|
add(&states[currentState].counter, value, 1 /* numerator */, 1 /* denominator */);
|
|
}
|
|
|
|
template <class T>
|
|
void MultiStateCounter<T>::reset() {
|
|
lastStateChangeTimestamp = -1;
|
|
lastUpdateTimestamp = -1;
|
|
for (int i = 0; i < stateCount; i++) {
|
|
states[i].timeInStateSinceUpdate = 0;
|
|
states[i].counter = emptyValue;
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
uint16_t MultiStateCounter<T>::getStateCount() {
|
|
return stateCount;
|
|
}
|
|
|
|
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 << "[";
|
|
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
|