[rrd4j] Upgrade rrd4j, improve exception handling (#11355)

* Upgrades rrd4j to 3.8
* Fixes deprecations
* Updates RRD4jChartServlet for ChartProvider API changes
* Improves RRD4jChartServlet exception handling

For the rrd4j changelog see:

https://raw.githubusercontent.com/rrd4j/rrd4j/master/changelog.txt

Related to openhab/openhab-core#2502

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born 2021-10-09 21:56:09 +02:00 committed by GitHub
parent dd432270e8
commit 2b5431df00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 81 additions and 75 deletions

View File

@ -23,7 +23,7 @@
<dependency> <dependency>
<groupId>org.rrd4j</groupId> <groupId>org.rrd4j</groupId>
<artifactId>rrd4j</artifactId> <artifactId>rrd4j</artifactId>
<version>3.3.1</version> <version>3.8</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -296,7 +296,7 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
for (double value : result.getValues(DATASOURCE_STATE)) { for (double value : result.getValues(DATASOURCE_STATE)) {
if (!Double.isNaN(value) && (((ts >= start) && (ts <= end)) || (start == end))) { if (!Double.isNaN(value) && (((ts >= start) && (ts <= end)) || (start == end))) {
RRD4jItem rrd4jItem = new RRD4jItem(itemName, mapToState(value, item, unit), RRD4jItem rrd4jItem = new RRD4jItem(itemName, mapToState(value, item, unit),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(ts * 1000), ZoneId.systemDefault())); ZonedDateTime.ofInstant(Instant.ofEpochSecond(ts), ZoneId.systemDefault()));
items.add(rrd4jItem); items.add(rrd4jItem);
} }
ts += step; ts += step;
@ -319,7 +319,7 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
try { try {
if (file.exists()) { if (file.exists()) {
// recreate the RrdDb instance from the file // recreate the RrdDb instance from the file
db = new RrdDb(file.getAbsolutePath()); db = RrdDb.of(file.getAbsolutePath());
} else { } else {
File folder = new File(DB_FOLDER); File folder = new File(DB_FOLDER);
if (!folder.exists()) { if (!folder.exists()) {
@ -328,7 +328,7 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
RrdDef rrdDef = getRrdDef(alias, file); RrdDef rrdDef = getRrdDef(alias, file);
if (rrdDef != null) { if (rrdDef != null) {
// create a new database file // create a new database file
db = new RrdDb(rrdDef); db = RrdDb.of(rrdDef);
} else { } else {
logger.debug( logger.debug(
"Did not create rrd4j database for item '{}' since no rrd definition could be determined. This is likely due to an unsupported item type.", "Did not create rrd4j database for item '{}' since no rrd definition could be determined. This is likely due to an unsupported item type.",
@ -349,7 +349,7 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
for (Map.Entry<String, RrdDefConfig> e : rrdDefs.entrySet()) { for (Map.Entry<String, RrdDefConfig> e : rrdDefs.entrySet()) {
// try to find special config // try to find special config
RrdDefConfig rdc = e.getValue(); RrdDefConfig rdc = e.getValue();
if (rdc != null && rdc.appliesTo(itemName)) { if (rdc.appliesTo(itemName)) {
useRdc = rdc; useRdc = rdc;
break; break;
} }
@ -543,13 +543,11 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
} }
for (RrdDefConfig rrdDef : rrdDefs.values()) { for (RrdDefConfig rrdDef : rrdDefs.values()) {
if (rrdDef != null) { if (rrdDef.isValid()) {
if (rrdDef.isValid()) { logger.debug("Created {}", rrdDef);
logger.debug("Created {}", rrdDef); } else {
} else { logger.info("Removing invalid definition {}", rrdDef);
logger.info("Removing invalid definition {}", rrdDef); rrdDefs.remove(rrdDef.name);
rrdDefs.remove(rrdDef.name);
}
} }
} }
} }

View File

@ -12,16 +12,18 @@
*/ */
package org.openhab.persistence.rrd4j.internal.charts; package org.openhab.persistence.rrd4j.internal.charts;
import static java.util.Map.entry;
import java.awt.Color; import java.awt.Color;
import java.awt.Font; import java.awt.Font;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.io.UncheckedIOException;
import java.util.HashMap; import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.servlet.Servlet; import javax.servlet.Servlet;
@ -30,6 +32,9 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.items.GroupItem; import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item; import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemNotFoundException;
@ -46,6 +51,7 @@ import org.osgi.service.http.NamespaceException;
import org.rrd4j.ConsolFun; import org.rrd4j.ConsolFun;
import org.rrd4j.core.RrdDb; import org.rrd4j.core.RrdDb;
import org.rrd4j.graph.RrdGraph; import org.rrd4j.graph.RrdGraph;
import org.rrd4j.graph.RrdGraphConstants.FontTag;
import org.rrd4j.graph.RrdGraphDef; import org.rrd4j.graph.RrdGraphDef;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -66,11 +72,15 @@ import org.slf4j.LoggerFactory;
* @author Jan N. Klug - a few improvements * @author Jan N. Klug - a few improvements
* *
*/ */
@NonNullByDefault
@Component(service = ChartProvider.class) @Component(service = ChartProvider.class)
public class RRD4jChartServlet implements Servlet, ChartProvider { public class RRD4jChartServlet implements Servlet, ChartProvider {
private final Logger logger = LoggerFactory.getLogger(RRD4jChartServlet.class); private final Logger logger = LoggerFactory.getLogger(RRD4jChartServlet.class);
private static final int DEFAULT_HEIGHT = 240;
private static final int DEFAULT_WIDTH = 480;
/** the URI of this servlet */ /** the URI of this servlet */
public static final String SERVLET_NAME = "/rrdchart.png"; public static final String SERVLET_NAME = "/rrdchart.png";
@ -81,29 +91,30 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
new Color(0, 255, 255, 30), new Color(255, 0, 128, 30), new Color(255, 128, 128, 30), new Color(0, 255, 255, 30), new Color(255, 0, 128, 30), new Color(255, 128, 128, 30),
new Color(255, 255, 0, 30) }; new Color(255, 255, 0, 30) };
protected static final Map<String, Long> PERIODS = new HashMap<>(); private static final Duration DEFAULT_PERIOD = Duration.ofDays(1);
static { private static final Map<String, Duration> PERIODS = Map.ofEntries( //
PERIODS.put("h", -3600000L); entry("h", Duration.ofHours(1)), entry("4h", Duration.ofHours(4)), //
PERIODS.put("4h", -14400000L); entry("8h", Duration.ofHours(8)), entry("12h", Duration.ofHours(12)), //
PERIODS.put("8h", -28800000L); entry("D", Duration.ofDays(1)), entry("2D", Duration.ofDays(2)), //
PERIODS.put("12h", -43200000L); entry("3D", Duration.ofDays(3)), entry("W", Duration.ofDays(7)), //
PERIODS.put("D", -86400000L); entry("2W", Duration.ofDays(14)), entry("M", Duration.ofDays(30)), //
PERIODS.put("3D", -259200000L); entry("2M", Duration.ofDays(60)), entry("4M", Duration.ofDays(120)), //
PERIODS.put("W", -604800000L); entry("Y", Duration.ofDays(365))//
PERIODS.put("2W", -1209600000L); );
PERIODS.put("M", -2592000000L);
PERIODS.put("2M", -5184000000L); private final HttpService httpService;
PERIODS.put("4M", -10368000000L); private final ItemUIRegistry itemUIRegistry;
PERIODS.put("Y", -31536000000L); private final TimeZoneProvider timeZoneProvider;
@Activate
public RRD4jChartServlet(final @Reference HttpService httpService, final @Reference ItemUIRegistry itemUIRegistry,
final @Reference TimeZoneProvider timeZoneProvider) {
this.httpService = httpService;
this.itemUIRegistry = itemUIRegistry;
this.timeZoneProvider = timeZoneProvider;
} }
@Reference
protected HttpService httpService;
@Reference
protected ItemUIRegistry itemUIRegistry;
@Activate @Activate
protected void activate() { protected void activate() {
try { try {
@ -125,35 +136,39 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
logger.debug("RRD4J received incoming chart request: {}", req); logger.debug("RRD4J received incoming chart request: {}", req);
int width = 480; int width = parseInt(req.getParameter("w"), DEFAULT_WIDTH);
try { int height = parseInt(req.getParameter("h"), DEFAULT_HEIGHT);
width = Integer.parseInt(Objects.requireNonNull(req.getParameter("w"))); String periodParam = req.getParameter("period");
} catch (Exception e) { Duration period = periodParam == null ? DEFAULT_PERIOD : PERIODS.getOrDefault(periodParam, DEFAULT_PERIOD);
}
int height = 240; // Create the start and stop time
try { ZonedDateTime timeEnd = ZonedDateTime.now(timeZoneProvider.getTimeZone());
height = Integer.parseInt(Objects.requireNonNull(req.getParameter("h"))); ZonedDateTime timeBegin = timeEnd.minus(period);
} catch (Exception e) {
}
Long period = PERIODS.get(req.getParameter("period"));
if (period == null) {
// use a day as the default period
period = PERIODS.get("D");
}
// Create the start and stop time
Date timeEnd = new Date();
Date timeBegin = new Date(timeEnd.getTime() + period);
// Set the content type to that provided by the chart provider
res.setContentType("image/" + getChartType());
try { try {
BufferedImage chart = createChart(null, null, timeBegin, timeEnd, height, width, req.getParameter("items"), BufferedImage chart = createChart(null, null, timeBegin, timeEnd, height, width, req.getParameter("items"),
req.getParameter("groups"), null, null); req.getParameter("groups"), null, null);
// Set the content type to that provided by the chart provider
res.setContentType("image/" + getChartType());
ImageIO.write(chart, getChartType().toString(), res.getOutputStream()); ImageIO.write(chart, getChartType().toString(), res.getOutputStream());
} catch (ItemNotFoundException e) { } catch (ItemNotFoundException e) {
logger.debug("Item not found error while generating chart."); logger.debug("Item not found error while generating chart", e);
throw new ServletException("Item not found error while generating chart: " + e.getMessage());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.debug("Illegal argument in chart", e); logger.debug("Illegal argument in chart", e);
throw new ServletException("Illegal argument in chart: " + e.getMessage());
}
}
private int parseInt(@Nullable String s, int defaultValue) {
if (s == null) {
return defaultValue;
}
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
logger.debug("'{}' is not an integer, using default: {}", s, defaultValue);
return defaultValue;
} }
} }
@ -175,7 +190,7 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
label = label.substring(0, label.indexOf('[')); label = label.substring(0, label.indexOf('['));
} }
try { try {
RrdDb db = new RrdDb(rrdName); RrdDb db = RrdDb.of(rrdName);
consolFun = db.getRrdDef().getArcDefs()[0].getConsolFun(); consolFun = db.getRrdDef().getArcDefs()[0].getConsolFun();
db.close(); db.close();
} catch (IOException e) { } catch (IOException e) {
@ -196,16 +211,16 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
} }
@Override @Override
public void init(ServletConfig config) throws ServletException { public void init(@Nullable ServletConfig config) throws ServletException {
} }
@Override @Override
public ServletConfig getServletConfig() { public @Nullable ServletConfig getServletConfig() {
return null; return null;
} }
@Override @Override
public String getServletInfo() { public @Nullable String getServletInfo() {
return null; return null;
} }
@ -222,20 +237,17 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
} }
@Override @Override
public BufferedImage createChart(String service, String theme, Date startTime, Date endTime, int height, int width, public BufferedImage createChart(@Nullable String service, @Nullable String theme, ZonedDateTime startTime,
String items, String groups, Integer dpi, Boolean legend) throws ItemNotFoundException { ZonedDateTime endTime, int height, int width, @Nullable String items, @Nullable String groups,
RrdGraphDef graphDef = new RrdGraphDef(); @Nullable Integer dpi, @Nullable Boolean legend) throws ItemNotFoundException {
RrdGraphDef graphDef = new RrdGraphDef(startTime.toEpochSecond(), endTime.toEpochSecond());
long period = (startTime.getTime() - endTime.getTime()) / 1000;
graphDef.setWidth(width); graphDef.setWidth(width);
graphDef.setHeight(height); graphDef.setHeight(height);
graphDef.setAntiAliasing(true); graphDef.setAntiAliasing(true);
graphDef.setImageFormat("PNG"); graphDef.setImageFormat("PNG");
graphDef.setStartTime(period);
graphDef.setTextAntiAliasing(true); graphDef.setTextAntiAliasing(true);
graphDef.setLargeFont(new Font("SansSerif", Font.PLAIN, 15)); graphDef.setFont(FontTag.TITLE, new Font("SansSerif", Font.PLAIN, 15));
graphDef.setSmallFont(new Font("SansSerif", Font.PLAIN, 11)); graphDef.setFont(FontTag.DEFAULT, new Font("SansSerif", Font.PLAIN, 11));
int seriesCounter = 0; int seriesCounter = 0;
@ -265,19 +277,15 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
} }
// Write the chart as a PNG image // Write the chart as a PNG image
RrdGraph graph;
try { try {
graph = new RrdGraph(graphDef); RrdGraph graph = new RrdGraph(graphDef);
BufferedImage bi = new BufferedImage(graph.getRrdGraphInfo().getWidth(), BufferedImage bi = new BufferedImage(graph.getRrdGraphInfo().getWidth(),
graph.getRrdGraphInfo().getHeight(), BufferedImage.TYPE_INT_RGB); graph.getRrdGraphInfo().getHeight(), BufferedImage.TYPE_INT_RGB);
graph.render(bi.getGraphics()); graph.render(bi.getGraphics());
return bi; return bi;
} catch (IOException e) { } catch (IOException e) {
logger.error("Error generating graph.", e); throw new UncheckedIOException("Error generating RrdGraph", e);
} }
return null;
} }
@Override @Override