fix rrd4j restore on startup (#18308)

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
pull/18013/merge
Mark Herwege 2025-02-22 11:21:51 +01:00 committed by GitHub
parent fa22df38ef
commit 1cefae42fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 114 additions and 0 deletions

View File

@ -64,12 +64,14 @@ import org.openhab.core.library.types.QuantityType;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.FilterCriteria.Ordering;
import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.persistence.PersistedItem;
import org.openhab.core.persistence.PersistenceItemInfo;
import org.openhab.core.persistence.PersistenceService;
import org.openhab.core.persistence.QueryablePersistenceService;
import org.openhab.core.persistence.strategy.PersistenceCronStrategy;
import org.openhab.core.persistence.strategy.PersistenceStrategy;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
@ -539,6 +541,118 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
}
}
/**
* Returns a {@link PersistedItem} representing the persisted state, last update and change timestamps and previous
* persisted state. This can be used to restore the full state of an item.
* The default implementation queries the service and iterates backward to find the last change and previous
* persisted state. Persistence services can override this default implementation with a more specific or efficient
* algorithm.
*
* This method overrides the default implementation in the interface as queries without a begin date are not allowed
* in the rrd4j database. If the last change cannot be found in half the length of the first archive, a null value
* for the last change and previous persisted state will be returned with {@link PersistedItem}.
*
* @param itemName name of item
* @param alias alias of item
*
* @return a {@link PersistedItem} or null if the item has not been persisted
*/
@Override
public @Nullable PersistedItem persistedItem(String itemName, @Nullable String alias) {
State currentState = UnDefType.NULL;
State previousState = null;
ZonedDateTime lastUpdate = null;
ZonedDateTime lastChange = null;
// Avoid query with open begin date. Don't look further back than half of the first archive.
// Only half of the archive is considered to avoid accidently querying the next archive with lower granularity.
String localAlias = alias != null ? alias : itemName;
RrdDefConfig rrdDefConfig = getRrdDefConfig(localAlias);
if (rrdDefConfig == null) {
logger.warn("No rrd4j database definition found for {}", itemName);
return null;
}
List<RrdArchiveDef> rrdArchiveDefs = rrdDefConfig.archives;
if (rrdArchiveDefs.isEmpty()) {
logger.warn("No rrd4j archive definition found for {}", itemName);
return null;
}
RrdArchiveDef rrdArchiveDef = rrdArchiveDefs.get(0);
long archiveLength = (rrdArchiveDef.rows * rrdArchiveDef.steps) / 2;
ZonedDateTime endDate = ZonedDateTime.now();
ZonedDateTime beginDate = endDate.minusSeconds(archiveLength);
int pageNumber = 0;
FilterCriteria filter = new FilterCriteria().setItemName(itemName).setBeginDate(beginDate).setEndDate(endDate)
.setOrdering(Ordering.DESCENDING).setPageSize(1000).setPageNumber(pageNumber);
Iterable<HistoricItem> items = query(filter, alias);
while (items != null) {
Iterator<HistoricItem> it = items.iterator();
int itemCount = 0;
if (UnDefType.NULL.equals(currentState) && it.hasNext()) {
HistoricItem historicItem = it.next();
itemCount++;
currentState = historicItem.getState();
lastUpdate = historicItem.getTimestamp();
lastChange = lastUpdate;
}
while (it.hasNext()) {
HistoricItem historicItem = it.next();
itemCount++;
if (!historicItem.getState().equals(currentState)) {
previousState = historicItem.getState();
items = null;
break;
}
lastChange = historicItem.getTimestamp();
}
if (itemCount == filter.getPageSize()) {
filter.setPageNumber(++pageNumber);
items = query(filter);
} else {
items = null;
}
}
if (UnDefType.NULL.equals(currentState) || lastUpdate == null) {
return null;
}
final State state = currentState;
final ZonedDateTime lastStateUpdate = lastUpdate;
final State lastState = previousState;
// if we don't find a previous state in persistence, we also don't know when it last changed
final ZonedDateTime lastStateChange = previousState != null ? lastChange : null;
return new PersistedItem() {
@Override
public ZonedDateTime getTimestamp() {
return lastStateUpdate;
}
@Override
public State getState() {
return state;
}
@Override
public String getName() {
return itemName;
}
@Override
public @Nullable ZonedDateTime getLastStateChange() {
return lastStateChange;
}
@Override
public @Nullable State getLastState() {
return lastState;
}
};
}
@Override
public Set<PersistenceItemInfo> getItemInfo() {
return Set.of();