2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.rrd4j.internal.charts;
15 import java.awt.Color;
17 import java.awt.image.BufferedImage;
19 import java.io.IOException;
20 import java.util.Date;
21 import java.util.HashMap;
22 import java.util.Hashtable;
24 import java.util.Objects;
26 import javax.imageio.ImageIO;
27 import javax.servlet.Servlet;
28 import javax.servlet.ServletConfig;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletRequest;
31 import javax.servlet.ServletResponse;
33 import org.openhab.core.items.GroupItem;
34 import org.openhab.core.items.Item;
35 import org.openhab.core.items.ItemNotFoundException;
36 import org.openhab.core.library.items.NumberItem;
37 import org.openhab.core.ui.chart.ChartProvider;
38 import org.openhab.core.ui.items.ItemUIRegistry;
39 import org.openhab.persistence.rrd4j.internal.RRD4jPersistenceService;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Deactivate;
43 import org.osgi.service.component.annotations.Reference;
44 import org.osgi.service.http.HttpService;
45 import org.osgi.service.http.NamespaceException;
46 import org.rrd4j.ConsolFun;
47 import org.rrd4j.core.RrdDb;
48 import org.rrd4j.graph.RrdGraph;
49 import org.rrd4j.graph.RrdGraphDef;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * This servlet generates time-series charts for a given set of items.
55 * It accepts the following HTTP parameters:
57 * <li>w: width in pixels of image to generate</li>
58 * <li>h: height in pixels of image to generate</li>
59 * <li>period: the time span for the x-axis. Value can be h,4h,8h,12h,D,3D,W,2W,M,2M,4M,Y</li>
60 * <li>items: A comma separated list of item names to display
61 * <li>groups: A comma separated list of group names, whose members should be displayed
64 * @author Kai Kreuzer - Initial contribution
65 * @author Chris Jackson - a few improvements
66 * @author Jan N. Klug - a few improvements
69 @Component(service = ChartProvider.class)
70 public class RRD4jChartServlet implements Servlet, ChartProvider {
72 private final Logger logger = LoggerFactory.getLogger(RRD4jChartServlet.class);
74 /** the URI of this servlet */
75 public static final String SERVLET_NAME = "/rrdchart.png";
77 protected static final Color[] LINECOLORS = new Color[] { Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA,
78 Color.ORANGE, Color.CYAN, Color.PINK, Color.DARK_GRAY, Color.YELLOW };
79 protected static final Color[] AREACOLORS = new Color[] { new Color(255, 0, 0, 30), new Color(0, 255, 0, 30),
80 new Color(0, 0, 255, 30), new Color(255, 0, 255, 30), new Color(255, 128, 0, 30),
81 new Color(0, 255, 255, 30), new Color(255, 0, 128, 30), new Color(255, 128, 128, 30),
82 new Color(255, 255, 0, 30) };
84 protected static final Map<String, Long> PERIODS = new HashMap<>();
87 PERIODS.put("h", -3600000L);
88 PERIODS.put("4h", -14400000L);
89 PERIODS.put("8h", -28800000L);
90 PERIODS.put("12h", -43200000L);
91 PERIODS.put("D", -86400000L);
92 PERIODS.put("3D", -259200000L);
93 PERIODS.put("W", -604800000L);
94 PERIODS.put("2W", -1209600000L);
95 PERIODS.put("M", -2592000000L);
96 PERIODS.put("2M", -5184000000L);
97 PERIODS.put("4M", -10368000000L);
98 PERIODS.put("Y", -31536000000L);
102 protected HttpService httpService;
105 protected ItemUIRegistry itemUIRegistry;
108 protected void activate() {
110 logger.debug("Starting up rrd chart servlet at {}", SERVLET_NAME);
111 httpService.registerServlet(SERVLET_NAME, this, new Hashtable<>(), httpService.createDefaultHttpContext());
112 } catch (NamespaceException e) {
113 logger.error("Error during servlet startup", e);
114 } catch (ServletException e) {
115 logger.error("Error during servlet startup", e);
120 protected void deactivate() {
121 httpService.unregister(SERVLET_NAME);
125 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
126 logger.debug("RRD4J received incoming chart request: {}", req);
130 width = Integer.parseInt(Objects.requireNonNull(req.getParameter("w")));
131 } catch (Exception e) {
135 height = Integer.parseInt(Objects.requireNonNull(req.getParameter("h")));
136 } catch (Exception e) {
138 Long period = PERIODS.get(req.getParameter("period"));
139 if (period == null) {
140 // use a day as the default period
141 period = PERIODS.get("D");
143 // Create the start and stop time
144 Date timeEnd = new Date();
145 Date timeBegin = new Date(timeEnd.getTime() + period);
147 // Set the content type to that provided by the chart provider
148 res.setContentType("image/" + getChartType());
150 BufferedImage chart = createChart(null, null, timeBegin, timeEnd, height, width, req.getParameter("items"),
151 req.getParameter("groups"), null, null);
152 ImageIO.write(chart, getChartType().toString(), res.getOutputStream());
153 } catch (ItemNotFoundException e) {
154 logger.debug("Item not found error while generating chart.");
155 } catch (IllegalArgumentException e) {
156 logger.debug("Illegal argument in chart", e);
161 * Adds a line for the item to the graph definition.
162 * The color of the line is determined by the counter, it simply picks the according index from LINECOLORS (and
163 * rolls over if necessary).
165 * @param graphDef the graph definition to fill
166 * @param item the item to add a line for
167 * @param counter defines the number of the datasource and is used to determine the line color
169 protected void addLine(RrdGraphDef graphDef, Item item, int counter) {
170 Color color = LINECOLORS[counter % LINECOLORS.length];
171 String label = itemUIRegistry.getLabel(item.getName());
172 String rrdName = RRD4jPersistenceService.DB_FOLDER + File.separator + item.getName() + ".rrd";
174 if (label != null && label.contains("[") && label.contains("]")) {
175 label = label.substring(0, label.indexOf('['));
178 RrdDb db = new RrdDb(rrdName);
179 consolFun = db.getRrdDef().getArcDefs()[0].getConsolFun();
181 } catch (IOException e) {
182 consolFun = ConsolFun.MAX;
184 if (item instanceof NumberItem) {
185 // we only draw a line
186 graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
187 graphDef.line(Integer.toString(counter), color, label, 2);
189 // we draw a line and fill the area beneath it with a transparent color
190 graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
191 Color areaColor = AREACOLORS[counter % LINECOLORS.length];
193 graphDef.area(Integer.toString(counter), areaColor);
194 graphDef.line(Integer.toString(counter), color, label, 2);
199 public void init(ServletConfig config) throws ServletException {
203 public ServletConfig getServletConfig() {
208 public String getServletInfo() {
213 public void destroy() {
216 // ----------------------------------------------------------
217 // The following methods implement the ChartServlet interface
220 public String getName() {
225 public BufferedImage createChart(String service, String theme, Date startTime, Date endTime, int height, int width,
226 String items, String groups, Integer dpi, Boolean legend) throws ItemNotFoundException {
227 RrdGraphDef graphDef = new RrdGraphDef();
229 long period = (startTime.getTime() - endTime.getTime()) / 1000;
231 graphDef.setWidth(width);
232 graphDef.setHeight(height);
233 graphDef.setAntiAliasing(true);
234 graphDef.setImageFormat("PNG");
235 graphDef.setStartTime(period);
236 graphDef.setTextAntiAliasing(true);
237 graphDef.setLargeFont(new Font("SansSerif", Font.PLAIN, 15));
238 graphDef.setSmallFont(new Font("SansSerif", Font.PLAIN, 11));
240 int seriesCounter = 0;
242 // Loop through all the items
244 String[] itemNames = items.split(",");
245 for (String itemName : itemNames) {
246 Item item = itemUIRegistry.getItem(itemName);
247 addLine(graphDef, item, seriesCounter++);
251 // Loop through all the groups and add each item from each group
252 if (groups != null) {
253 String[] groupNames = groups.split(",");
254 for (String groupName : groupNames) {
255 Item item = itemUIRegistry.getItem(groupName);
256 if (item instanceof GroupItem) {
257 GroupItem groupItem = (GroupItem) item;
258 for (Item member : groupItem.getMembers()) {
259 addLine(graphDef, member, seriesCounter++);
262 throw new ItemNotFoundException("Item '" + item.getName() + "' defined in groups is not a group.");
267 // Write the chart as a PNG image
270 graph = new RrdGraph(graphDef);
271 BufferedImage bi = new BufferedImage(graph.getRrdGraphInfo().getWidth(),
272 graph.getRrdGraphInfo().getHeight(), BufferedImage.TYPE_INT_RGB);
273 graph.render(bi.getGraphics());
276 } catch (IOException e) {
277 logger.error("Error generating graph.", e);
284 public ImageType getChartType() {
285 return ImageType.png;