Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rrd4j] Fix restore on startup #18308

Merged
merged 1 commit into from
Feb 22, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,14 @@
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;
Expand Down Expand Up @@ -539,6 +541,118 @@ public Iterable<HistoricItem> query(FilterCriteria filter, @Nullable String alia
}
}

/**
* 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();
Expand Down