[rrd4j] Improve performance (#8929)

* [rrd4j] Improve performance

* Cache item to improve performance
* Also return DecimalType for QuantityType values

Related to #8928
Reintroduces #8809

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born 2020-11-01 21:28:25 +01:00 committed by GitHub
parent b82d5f8063
commit 603ceedff9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 75 additions and 77 deletions

View File

@ -18,7 +18,6 @@ import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -154,7 +153,7 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
sample.setValue(DATASOURCE_STATE, lastValue); sample.setValue(DATASOURCE_STATE, lastValue);
sample.update(); sample.update();
logger.debug("Stored '{}' with state '{}' in rrd4j database (again)", name, logger.debug("Stored '{}' with state '{}' in rrd4j database (again)", name,
mapToState(lastValue, item.getName())); mapToState(lastValue, item));
} }
} }
} catch (IOException e) { } catch (IOException e) {
@ -199,7 +198,8 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
logger.debug("Stored '{}' with state '{}' in rrd4j database", name, value); logger.debug("Stored '{}' with state '{}' in rrd4j database", name, value);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
if (e.getMessage().contains("at least one second step is required")) { String message = e.getMessage();
if (message != null && message.contains("at least one second step is required")) {
// we try to store the value one second later // we try to store the value one second later
ScheduledFuture<?> job = scheduledJobs.get(name); ScheduledFuture<?> job = scheduledJobs.get(name);
if (job != null) { if (job != null) {
@ -230,73 +230,83 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
@Override @Override
public Iterable<HistoricItem> query(FilterCriteria filter) { public Iterable<HistoricItem> query(FilterCriteria filter) {
String itemName = filter.getItemName(); String itemName = filter.getItemName();
RrdDb db = getDB(itemName);
if (db != null) {
ConsolFun consolidationFunction = getConsolidationFunction(db);
long start = 0L;
long end = filter.getEndDate() == null ? System.currentTimeMillis() / 1000
: filter.getEndDate().toInstant().getEpochSecond();
try { RrdDb db = getDB(itemName);
if (filter.getBeginDate() == null) { if (db == null) {
// as rrd goes back for years and gets more and more logger.debug("Could not find item '{}' in rrd4j database", itemName);
// inaccurate, we only support descending order return List.of();
// and a single return value }
// if there is no begin date is given - this case is
// required specifically for the historicState() Item item = null;
// query, which we want to support try {
if (filter.getOrdering() == Ordering.DESCENDING && filter.getPageSize() == 1 item = itemRegistry.getItem(itemName);
&& filter.getPageNumber() == 0) { } catch (ItemNotFoundException e) {
if (filter.getEndDate() == null) { logger.debug("Could not find item '{}' in registry", itemName);
// we are asked only for the most recent value! }
double lastValue = db.getLastDatasourceValue(DATASOURCE_STATE);
if (!Double.isNaN(lastValue)) { long start = 0L;
HistoricItem rrd4jItem = new RRD4jItem(itemName, mapToState(lastValue, itemName), long end = filter.getEndDate() == null ? System.currentTimeMillis() / 1000
ZonedDateTime.ofInstant( : filter.getEndDate().toInstant().getEpochSecond();
Instant.ofEpochMilli(db.getLastArchiveUpdateTime() * 1000),
ZoneId.systemDefault())); try {
return Collections.singletonList(rrd4jItem); if (filter.getBeginDate() == null) {
} else { // as rrd goes back for years and gets more and more
return Collections.emptyList(); // inaccurate, we only support descending order
} // and a single return value
// if there is no begin date is given - this case is
// required specifically for the historicState()
// query, which we want to support
if (filter.getOrdering() == Ordering.DESCENDING && filter.getPageSize() == 1
&& filter.getPageNumber() == 0) {
if (filter.getEndDate() == null) {
// we are asked only for the most recent value!
double lastValue = db.getLastDatasourceValue(DATASOURCE_STATE);
if (!Double.isNaN(lastValue)) {
HistoricItem rrd4jItem = new RRD4jItem(itemName, mapToState(lastValue, item),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(db.getLastArchiveUpdateTime() * 1000),
ZoneId.systemDefault()));
return List.of(rrd4jItem);
} else { } else {
start = end; return List.of();
} }
} else { } else {
throw new UnsupportedOperationException("rrd4j does not allow querys without a begin date, " start = end;
+ "unless order is descending and a single value is requested");
} }
} else { } else {
start = filter.getBeginDate().toInstant().getEpochSecond(); throw new UnsupportedOperationException("rrd4j does not allow querys without a begin date, "
+ "unless order is descending and a single value is requested");
} }
FetchRequest request = db.createFetchRequest(consolidationFunction, start, end, 1); } else {
start = filter.getBeginDate().toInstant().getEpochSecond();
List<HistoricItem> items = new ArrayList<>();
FetchData result = request.fetchData();
long ts = result.getFirstTimestamp();
long step = result.getRowCount() > 1 ? result.getStep() : 0;
for (double value : result.getValues(DATASOURCE_STATE)) {
if (!Double.isNaN(value) && (((ts >= start) && (ts <= end)) || (start == end))) {
RRD4jItem rrd4jItem = new RRD4jItem(itemName, mapToState(value, itemName),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(ts * 1000), ZoneId.systemDefault()));
items.add(rrd4jItem);
}
ts += step;
}
return items;
} catch (IOException e) {
logger.warn("Could not query rrd4j database for item '{}': {}", itemName, e.getMessage());
} }
FetchRequest request = db.createFetchRequest(getConsolidationFunction(db), start, end, 1);
FetchData result = request.fetchData();
List<HistoricItem> items = new ArrayList<>();
long ts = result.getFirstTimestamp();
long step = result.getRowCount() > 1 ? result.getStep() : 0;
for (double value : result.getValues(DATASOURCE_STATE)) {
if (!Double.isNaN(value) && (((ts >= start) && (ts <= end)) || (start == end))) {
RRD4jItem rrd4jItem = new RRD4jItem(itemName, mapToState(value, item),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(ts * 1000), ZoneId.systemDefault()));
items.add(rrd4jItem);
}
ts += step;
}
return items;
} catch (IOException e) {
logger.warn("Could not query rrd4j database for item '{}': {}", itemName, e.getMessage());
return List.of();
} }
return Collections.emptyList();
} }
@Override @Override
public Set<PersistenceItemInfo> getItemInfo() { public Set<PersistenceItemInfo> getItemInfo() {
return Collections.emptySet(); return Set.of();
} }
protected @Nullable synchronized RrdDb getDB(String alias) { protected synchronized @Nullable RrdDb getDB(String alias) {
RrdDb db = null; RrdDb db = null;
File file = new File(DB_FOLDER + File.separator + alias + ".rrd"); File file = new File(DB_FOLDER + File.separator + alias + ".rrd");
try { try {
@ -383,29 +393,17 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
} }
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) private State mapToState(double value, @Nullable Item item) {
private State mapToState(double value, String itemName) { if (item instanceof SwitchItem && !(item instanceof DimmerItem)) {
try { return value == 0.0d ? OnOffType.OFF : OnOffType.ON;
Item item = itemRegistry.getItem(itemName); } else if (item instanceof ContactItem) {
if (item instanceof SwitchItem && !(item instanceof DimmerItem)) { return value == 0.0d ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
return value == 0.0d ? OnOffType.OFF : OnOffType.ON; } else if (item instanceof DimmerItem || item instanceof RollershutterItem) {
} else if (item instanceof ContactItem) { // make sure Items that need PercentTypes instead of DecimalTypes do receive the right information
return value == 0.0d ? OpenClosedType.CLOSED : OpenClosedType.OPEN; return new PercentType((int) Math.round(value * 100));
} else if (item instanceof DimmerItem || item instanceof RollershutterItem) {
// make sure Items that need PercentTypes instead of DecimalTypes do receive the right information
return new PercentType((int) Math.round(value * 100));
} else if (item instanceof NumberItem) {
Unit<? extends Quantity<?>> unit = ((NumberItem) item).getUnit();
if (unit != null) {
return new QuantityType(value, unit);
} else {
return new DecimalType(value);
}
}
} catch (ItemNotFoundException e) {
logger.debug("Could not find item '{}' in registry", itemName);
} }
// just return a DecimalType as a fallback // return a DecimalType as a fallback and for QuantityType values to prevent performance issues
// see: https://github.com/openhab/openhab-addons/issues/8928
return new DecimalType(value); return new DecimalType(value);
} }