Console: Update rrd4j to 3.9.1 preview

from rrd4j github head.
Includes our ImageWorker contribution which should speed up rendering.
This commit is contained in:
zzz
2024-05-12 09:58:42 -04:00
parent 84dbf53f95
commit 177fbad865
7 changed files with 308 additions and 159 deletions

View File

@ -267,7 +267,7 @@ Applications:
See licenses/LICENSE-Apache2.0.txt
See licenses/LICENSE-ECLIPSE-1.0.html
RRD4J 3.9 (jrobin.jar):
RRD4J 3.9.1 (jrobin.jar):
Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor.
Copyright (c) 2011 The OpenNMS Group, Inc.
Copyright 2011 The RRD4J Authors.

View File

@ -66,7 +66,7 @@
<property name="workspace.changes.tr" value="" />
<jar destfile="./build/jrobin.jar" basedir="./build/obj" includes="**/*.class">
<manifest>
<attribute name="Implementation-Version" value="3.8" />
<attribute name="Implementation-Version" value="3.9.1" />
<attribute name="Built-By" value="${build.built-by}" />
<attribute name="Build-Date" value="${build.timestamp}" />
<attribute name="Base-Revision" value="${workspace.version}" />

View File

@ -310,10 +310,7 @@ public abstract class RrdBackendFactory implements Closeable {
}
@Override
public void clear() {
try {
backend.close();
} catch (IOException e) {
}
// backend doesn't need to be closed here as it already happens in RrdBackend.rrdClose()
backend = null;
super.clear();
}

View File

@ -300,6 +300,9 @@ public class RrdDbPool {
RrdEntry ref;
try {
ref = getEntry(dburi, false);
} catch (IllegalStateException e) {
// This means that corresponding database has been already closed before, so returning immediately.
return;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Release interrupted for " + rrdDb.getPath(), e);

View File

@ -0,0 +1,161 @@
package org.rrd4j.graph;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
class BufferedImageWorker extends ImageWorker {
static class Builder {
private int width = 1;
private int height = 1;
private RrdGraphDef gdef;
private ImageWriter writer;
private ImageWriteParam imageWriteParam;
BufferedImageWorker build() {
return new BufferedImageWorker(this);
}
private ImageWriteParam getImageParams() {
ImageWriteParam iwp = writer.getDefaultWriteParam();
ImageWriterSpi imgProvider = writer.getOriginatingProvider();
//If lossy compression, use the quality
if (! imgProvider.isFormatLossless()) {
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(gdef.imageQuality);
}
if (iwp.canWriteProgressive()) {
iwp.setProgressiveMode(gdef.interlaced ? ImageWriteParam.MODE_DEFAULT:ImageWriteParam.MODE_DISABLED);
}
return iwp;
}
public Builder setWidth(int width) {
this.width = width;
return this;
}
public Builder setHeight(int height) {
this.height = height;
return this;
}
public Builder setGdef(RrdGraphDef gdef) {
this.gdef = gdef;
if (this.writer == null) {
this.writer = ImageIO.getImageWritersByFormatName(gdef.imageFormat).next();
}
if (this.imageWriteParam == null) {
this.imageWriteParam = getImageParams();
}
return this;
}
public Builder setWriter(ImageWriter writer) {
this.writer = writer;
if (this.imageWriteParam == null) {
this.imageWriteParam = getImageParams();
}
return this;
}
public Builder setImageWriteParam(ImageWriteParam imageWriteParam) {
this.imageWriteParam = imageWriteParam;
return this;
}
}
public static Builder getBuilder() {
return new Builder();
}
private BufferedImage img;
private int imgWidth;
private int imgHeight;
private AffineTransform initialAffineTransform;
private final ImageWriter writer;
private final ImageWriteParam iwp;
private BufferedImageWorker(Builder builder) {
this.imgHeight = builder.height;
this.imgWidth = builder.width;
this.writer = builder.writer;
this.iwp = builder.imageWriteParam;
resize(imgWidth, imgHeight);
}
/*
* Not for use, only for tests
*/
BufferedImageWorker(int width, int height) {
this.imgHeight = height;
this.imgWidth = width;
this.writer = ImageIO.getImageWritersByFormatName("png").next();
this.iwp = writer.getDefaultWriteParam();
resize(imgWidth, imgHeight);
}
protected void resize(int width, int height) {
imgWidth = width;
imgHeight = height;
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
setG2d(g2d);
initialAffineTransform = g2d.getTransform();
setAntiAliasing(false);
setTextAntiAliasing(false);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
protected void reset(Graphics2D g2d) {
g2d.setTransform(initialAffineTransform);
g2d.setClip(0, 0, imgWidth, imgHeight);
}
protected void makeImage(OutputStream stream) throws IOException {
BufferedImage outputImage = img;
ImageWriterSpi imgProvider = writer.getOriginatingProvider();
img.coerceData(false);
// Some format can't manage 16M colors images
// JPEG don't like transparency
if (! imgProvider.canEncodeImage(outputImage) || "image/jpeg".equalsIgnoreCase(imgProvider.getMIMETypes()[0])) {
int w = img.getWidth();
int h = img.getHeight();
outputImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
outputImage.getGraphics().drawImage(img, 0, 0, w, h, null);
if (! imgProvider.canEncodeImage(outputImage)) {
throw new IllegalArgumentException("Invalid image type");
}
}
if (! imgProvider.canEncodeImage(outputImage)) {
throw new IllegalArgumentException("Invalid image type");
}
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(stream)) {
writer.setOutput(imageStream);
writer.write(null, new IIOImage(outputImage, null, null), iwp);
imageStream.flush();
} catch (IOException e) {
writer.abort();
throw e;
} finally {
writer.dispose();
}
}
}

View File

@ -1,223 +1,211 @@
package org.rrd4j.graph;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.*;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
/**
* An abstract class, that allows to use custom {@link Graphics2D}. To use it the construct should build it and call
* {@link #setG2d(Graphics2D)} when finished.
*/
public abstract class ImageWorker {
class ImageWorker {
private static final String DUMMY_TEXT = "Dummy";
private static final int IMG_BUFFER_CAPACITY = 10000; // bytes
private BufferedImage img;
private Graphics2D g2d;
private int imgWidth, imgHeight;
private AffineTransform initialAffineTransform;
ImageWorker(int width, int height) {
resize(width, height);
}
void resize(int width, int height) {
protected void setG2d(Graphics2D g2d) {
if (g2d != null) {
dispose();
}
imgWidth = width;
imgHeight = height;
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
g2d = img.createGraphics();
initialAffineTransform = g2d.getTransform();
setAntiAliasing(false);
setTextAntiAliasing(false);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
this.g2d = g2d;
}
void clip(int x, int y, int width, int height) {
protected abstract void resize(int width, int height);
protected void clip(int x, int y, int width, int height) {
g2d.setClip(x, y, width, height);
}
void transform(int x, int y, double angle) {
protected void transform(int x, int y, double angle) {
g2d.translate(x, y);
g2d.rotate(angle);
}
void reset() {
g2d.setTransform(initialAffineTransform);
g2d.setClip(0, 0, imgWidth, imgHeight);
protected void reset() {
reset(g2d);
}
void fillRect(int x, int y, int width, int height, Paint paint) {
/**
* reset the dimensions of the {@link Graphics2D}
*/
protected abstract void reset(Graphics2D g2d);
protected void fillRect(int x, int y, int width, int height, Paint paint) {
g2d.setPaint(paint);
g2d.fillRect(x, y, width, height);
}
void fillPolygon(int[] x, int[] y, Paint paint) {
g2d.setPaint(paint);
g2d.fillPolygon(x, y, x.length);
}
void fillPolygon(double[] x, double yBottom, double[] yTop, Paint paint) {
protected void fillPolygon(double[] x, double yBottom, double[] yTop, Paint paint) {
g2d.setPaint(paint);
PathIterator path = new PathIterator(yTop);
for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) {
int start = pos[0], end = pos[1], n = end - start;
int[] xDev = new int[n + 2], yDev = new int[n + 2];
int c = 0;
for (int i = start; i < end; i++) {
xDev[i - start] = (int) x[i];
yDev[i - start] = (int) yTop[i];
int cx = (int) x[i];
int cy = (int) yTop[i];
if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
// collapse horizontal lines
xDev[c - 1] = cx;
} else {
xDev[c] = cx;
yDev[c++] = cy;
}
}
}
xDev[n] = xDev[n - 1];
xDev[n + 1] = xDev[0];
yDev[n] = yDev[n + 1] = (int) yBottom;
g2d.fillPolygon(xDev, yDev, xDev.length);
g2d.drawPolygon(xDev, yDev, xDev.length);
xDev[c] = xDev[c - 1];
xDev[c + 1] = xDev[0];
yDev[c] = yDev[c + 1] = (int) yBottom;
g2d.fillPolygon(xDev, yDev, c + 2);
}
}
void fillPolygon(double[] x, double[] yBottom, double[] yTop, Paint paint) {
protected void fillPolygon(double[] x, double[] yBottom, double[] yTop, Paint paint) {
g2d.setPaint(paint);
PathIterator path = new PathIterator(yTop);
for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) {
int start = pos[0], end = pos[1], n = end - start;
int[] xDev = new int[n * 2], yDev = new int[n * 2];
int c = 0;
for (int i = start; i < end; i++) {
int ix1 = i - start, ix2 = n * 2 - 1 - i + start;
xDev[ix1] = xDev[ix2] = (int) x[i];
yDev[ix1] = (int) yTop[i];
yDev[ix2] = (int) yBottom[i];
int cx = (int) x[i];
int cy = (int) yTop[i];
if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
// collapse horizontal lines
xDev[c - 1] = cx;
} else {
xDev[c] = cx;
yDev[c++] = cy;
}
}
}
g2d.fillPolygon(xDev, yDev, xDev.length);
for (int i = end - 1; i >= start; i--) {
int cx = (int) x[i];
int cy = (int) yBottom[i];
if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
// collapse horizontal lines
xDev[c - 1] = cx;
} else {
xDev[c] = cx;
yDev[c++] = cy;
}
}
}
g2d.fillPolygon(xDev, yDev, c);
}
}
void drawLine(int x1, int y1, int x2, int y2, Paint paint, Stroke stroke) {
protected void drawLine(int x1, int y1, int x2, int y2, Paint paint, Stroke stroke) {
g2d.setStroke(stroke);
g2d.setPaint(paint);
g2d.drawLine(x1, y1, x2, y2);
}
void drawPolyline(int[] x, int[] y, Paint paint, Stroke stroke) {
g2d.setStroke(stroke);
g2d.setPaint(paint);
g2d.drawPolyline(x, y, x.length);
}
void drawPolyline(double[] x, double[] y, Paint paint, Stroke stroke) {
protected void drawPolyline(double[] x, double[] y, Paint paint, Stroke stroke) {
g2d.setPaint(paint);
g2d.setStroke(stroke);
PathIterator path = new PathIterator(y);
for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) {
int start = pos[0], end = pos[1];
int[] xDev = new int[end - start], yDev = new int[end - start];
int c = 0;
for (int i = start; i < end; i++) {
xDev[i - start] = (int) x[i];
yDev[i - start] = (int) y[i];
int cx = (int) x[i];
int cy = (int) y[i];
if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
// collapse horizontal lines
xDev[c - 1] = cx;
} else {
xDev[c] = cx;
yDev[c++] = cy;
}
}
}
g2d.drawPolyline(xDev, yDev, xDev.length);
g2d.drawPolyline(xDev, yDev, c);
}
}
void drawString(String text, int x, int y, Font font, Paint paint) {
protected void drawString(String text, int x, int y, Font font, Paint paint) {
g2d.setFont(font);
g2d.setPaint(paint);
g2d.drawString(text, x, y);
}
double getFontAscent(Font font) {
protected double getFontAscent(Font font) {
LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, g2d.getFontRenderContext());
return lm.getAscent();
}
double getFontHeight(Font font) {
protected double getFontHeight(Font font) {
LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, g2d.getFontRenderContext());
return lm.getAscent() + lm.getDescent();
}
double getStringWidth(String text, Font font) {
protected double getStringWidth(String text, Font font) {
return font.getStringBounds(text, 0, text.length(), g2d.getFontRenderContext()).getBounds().getWidth();
}
void setAntiAliasing(boolean enable) {
protected void setAntiAliasing(boolean enable) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
enable ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
}
void setTextAntiAliasing(boolean enable) {
protected void setTextAntiAliasing(boolean enable) {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
enable ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
void dispose() {
g2d.dispose();
}
void makeImage(Object stream, ImageWriter writer, ImageWriteParam iwp) throws IOException {
BufferedImage outputImage = img;
ImageWriterSpi imgProvider = writer.getOriginatingProvider();
img.coerceData(false);
// Some format can't manage 16M colors images
// JPEG don't like transparency
if (! imgProvider.canEncodeImage(outputImage) || "image/jpeg".equalsIgnoreCase(imgProvider.getMIMETypes()[0])) {
int w = img.getWidth();
int h = img.getHeight();
outputImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
outputImage.getGraphics().drawImage(img, 0, 0, w, h, null);
if (! imgProvider.canEncodeImage(outputImage)) {
throw new RuntimeException("Invalid image type");
}
}
if (! imgProvider.canEncodeImage(outputImage)) {
throw new RuntimeException("Invalid image type");
}
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(stream)) {
writer.setOutput(imageStream);
writer.write(null, new IIOImage(outputImage, null, null), iwp);
imageStream.flush();
} catch (IOException e) {
writer.abort();
throw e;
} finally {
writer.dispose();
}
}
void saveImage(String path, ImageWriter writer, ImageWriteParam iwp) throws IOException {
makeImage(Paths.get(path).toFile(), writer, iwp);
}
byte[] getImageBytes(ImageWriter writer, ImageWriteParam iwp) throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream(IMG_BUFFER_CAPACITY)){
makeImage(stream, writer, iwp);
return stream.toByteArray();
}
}
void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException {
protected void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException {
BufferedImage wpImage = imageSource.apply(w, h).getSubimage(0, 0, w, h);
g2d.drawImage(wpImage, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
}
protected void dispose() {
if (g2d != null) {
g2d.dispose();
}
}
protected void makeImage(Path path) throws IOException {
try (OutputStream os = Files.newOutputStream(path)) {
makeImage(os);
}
}
protected abstract void makeImage(OutputStream os) throws IOException ;
protected void saveImage(String path) throws IOException {
makeImage(Paths.get(path));
}
protected byte[] getImageBytes() throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream(IMG_BUFFER_CAPACITY)){
makeImage(stream);
return stream.toByteArray();
}
}
}

View File

@ -12,10 +12,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageWriterSpi;
import javax.swing.ImageIcon;
import org.rrd4j.core.Util;
@ -47,8 +45,6 @@ public class RrdGraph implements RrdGraphConstants {
Mapper mapper;
private final RrdGraphInfo info = new RrdGraphInfo();
private final String signature;
private final ImageWriter writer;
private final ImageWriteParam param;
/**
* Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object.
@ -60,9 +56,8 @@ public class RrdGraph implements RrdGraphConstants {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
worker = new ImageWorker(1, 1); // Dummy worker, just to start with something
writer = ImageIO.getImageWritersByFormatName(gdef.imageFormat).next();
param = getImageParams();
worker = BufferedImageWorker.getBuilder().setGdef(gdef).build();
try {
createGraph();
}
@ -73,6 +68,28 @@ public class RrdGraph implements RrdGraphConstants {
}
}
/**
* Create graph from a custom image worker
* @param gdef
* @param worker
* @throws IOException
*/
public RrdGraph(RrdGraphDef gdef, ImageWorker worker) throws IOException {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
this.worker = worker;
try {
createGraph();
}
finally {
worker.dispose();
this.worker = null;
dproc = null;
}
}
/**
* <p>Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object.</p>
* <p>The graph will be created using customs {@link javax.imageio.ImageWriter} and {@link javax.imageio.ImageWriteParam} given.</p>
@ -88,9 +105,7 @@ public class RrdGraph implements RrdGraphConstants {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
worker = new ImageWorker(1, 1); // Dummy worker, just to start with something
this.writer = writer;
this.param = param;
worker = BufferedImageWorker.getBuilder().setGdef(gdef).setWriter(writer).setImageWriteParam(param).build();
try {
createGraph();
}
@ -110,21 +125,6 @@ public class RrdGraph implements RrdGraphConstants {
return info;
}
private ImageWriteParam getImageParams() {
ImageWriteParam iwp = writer.getDefaultWriteParam();
ImageWriterSpi imgProvider = writer.getOriginatingProvider();
//If lossy compression, use the quality
if (! imgProvider.isFormatLossless()) {
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(gdef.imageQuality);
}
if (iwp.canWriteProgressive()) {
iwp.setProgressiveMode(gdef.interlaced ? ImageWriteParam.MODE_DEFAULT:ImageWriteParam.MODE_DISABLED);
}
return iwp;
}
private void createGraph() throws IOException {
boolean lazy = lazyCheck();
if (!lazy || gdef.printStatementCount() != 0) {
@ -177,7 +177,7 @@ public class RrdGraph implements RrdGraphConstants {
private void saveImage() throws IOException {
if (! RrdGraphConstants.IN_MEMORY_IMAGE.equals(gdef.filename)) {
Path imgpath = Paths.get(gdef.filename);
worker.saveImage(gdef.filename, writer, param);
worker.saveImage(gdef.filename);
info.bytesSource = () -> {
try {
return Files.readAllBytes(imgpath);
@ -194,7 +194,7 @@ public class RrdGraph implements RrdGraphConstants {
};
}
else {
byte[] content = worker.getImageBytes(writer, param);
byte[] content = worker.getImageBytes();
info.bytesSource = () -> Arrays.copyOf(content, content.length);
info.bytesCount = () -> content.length;
}