2 * Copyright (c) 2010-2020 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;
25 import javax.imageio.ImageIO;
26 import javax.servlet.Servlet;
27 import javax.servlet.ServletConfig;
28 import javax.servlet.ServletException;
29 import javax.servlet.ServletRequest;
30 import javax.servlet.ServletResponse;
32 import org.openhab.core.items.GroupItem;
33 import org.openhab.core.items.Item;
34 import org.openhab.core.items.ItemNotFoundException;
35 import org.openhab.core.library.items.NumberItem;
36 import org.openhab.core.ui.chart.ChartProvider;
37 import org.openhab.core.ui.items.ItemUIRegistry;
38 import org.openhab.persistence.rrd4j.internal.RRD4jPersistenceService;
39 import org.osgi.service.component.annotations.Activate;
40 import org.osgi.service.component.annotations.Component;
41 import org.osgi.service.component.annotations.Deactivate;
42 import org.osgi.service.component.annotations.Reference;
43 import org.osgi.service.http.HttpService;
44 import org.osgi.service.http.NamespaceException;
45 import org.rrd4j.ConsolFun;
46 import org.rrd4j.core.RrdDb;
47 import org.rrd4j.graph.RrdGraph;
48 import org.rrd4j.graph.RrdGraphDef;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
53 * This servlet generates time-series charts for a given set of items.
54 * It accepts the following HTTP parameters:
56 * <li>w: width in pixels of image to generate</li>
57 * <li>h: height in pixels of image to generate</li>
58 * <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>
59 * <li>items: A comma separated list of item names to display
60 * <li>groups: A comma separated list of group names, whose members should be displayed
63 * @author Kai Kreuzer - Initial contribution
64 * @author Chris Jackson - a few improvements
65 * @author Jan N. Klug - a few improvements
68 @Component(service = ChartProvider.class)
69 public class RRD4jChartServlet implements Servlet, ChartProvider {
71 private final Logger logger = LoggerFactory.getLogger(RRD4jChartServlet.class);
73 /** the URI of this servlet */
74 public static final String SERVLET_NAME = "/rrdchart.png";
76 protected static final Color[] LINECOLORS = new Color[] { Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA,
77 Color.ORANGE, Color.CYAN, Color.PINK, Color.DARK_GRAY, Color.YELLOW };
78 protected static final Color[] AREACOLORS = new Color[] { new Color(255, 0, 0, 30), new Color(0, 255, 0, 30),
79 new Color(0, 0, 255, 30), new Color(255, 0, 255, 30), new Color(255, 128, 0, 30),
80 new Color(0, 255, 255, 30), new Color(255, 0, 128, 30), new Color(255, 128, 128, 30),
81 new Color(255, 255, 0, 30) };
83 protected static final Map<String, Long> PERIODS = new HashMap<>();
86 PERIODS.put("h", -3600000L);
87 PERIODS.put("4h", -14400000L);
88 PERIODS.put("8h", -28800000L);
89 PERIODS.put("12h", -43200000L);
90 PERIODS.put("D", -86400000L);
91 PERIODS.put("3D", -259200000L);
92 PERIODS.put("W", -604800000L);
93 PERIODS.put("2W", -1209600000L);
94 PERIODS.put("M", -2592000000L);
95 PERIODS.put("2M", -5184000000L);
96 PERIODS.put("4M", -10368000000L);
97 PERIODS.put("Y", -31536000000L);
101 protected HttpService httpService;
104 protected ItemUIRegistry itemUIRegistry;
107 protected void activate() {
109 logger.debug("Starting up rrd chart servlet at {}", SERVLET_NAME);
110 httpService.registerServlet(SERVLET_NAME, this, new Hashtable<>(), httpService.createDefaultHttpContext());
111 } catch (NamespaceException e) {
112 logger.error("Error during servlet startup", e);
113 } catch (ServletException e) {
114 logger.error("Error during servlet startup", e);
119 protected void deactivate() {
120 httpService.unregister(SERVLET_NAME);
124 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
125 logger.debug("RRD4J received incoming chart request: {}", req);
129 width = Integer.parseInt(req.getParameter("w"));
130 } catch (Exception e) {
134 height = Integer.parseInt(req.getParameter("h"));
135 } catch (Exception e) {
137 Long period = PERIODS.get(req.getParameter("period"));
138 if (period == null) {
139 // use a day as the default period
140 period = PERIODS.get("D");
142 // Create the start and stop time
143 Date timeEnd = new Date();
144 Date timeBegin = new Date(timeEnd.getTime() + period);
146 // Set the content type to that provided by the chart provider
147 res.setContentType("image/" + getChartType());
149 BufferedImage chart = createChart(null, null, timeBegin, timeEnd, height, width, req.getParameter("items"),
150 req.getParameter("groups"), null, null);
151 ImageIO.write(chart, getChartType().toString(), res.getOutputStream());
152 } catch (ItemNotFoundException e) {
153 logger.debug("Item not found error while generating chart.");
154 } catch (IllegalArgumentException e) {
155 logger.debug("Illegal argument in chart", e);
160 * Adds a line for the item to the graph definition.
161 * The color of the line is determined by the counter, it simply picks the according index from LINECOLORS (and
162 * rolls over if necessary).
164 * @param graphDef the graph definition to fill
165 * @param item the item to add a line for
166 * @param counter defines the number of the datasource and is used to determine the line color
168 protected void addLine(RrdGraphDef graphDef, Item item, int counter) {
169 Color color = LINECOLORS[counter % LINECOLORS.length];
170 String label = itemUIRegistry.getLabel(item.getName());
171 String rrdName = RRD4jPersistenceService.DB_FOLDER + File.separator + item.getName() + ".rrd";
173 if (label != null && label.contains("[") && label.contains("]")) {
174 label = label.substring(0, label.indexOf('['));
177 RrdDb db = new RrdDb(rrdName);
178 consolFun = db.getRrdDef().getArcDefs()[0].getConsolFun();
180 } catch (IOException e) {
181 consolFun = ConsolFun.MAX;
183 if (item instanceof NumberItem) {
184 // we only draw a line
185 graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
186 graphDef.line(Integer.toString(counter), color, label, 2);
188 // we draw a line and fill the area beneath it with a transparent color
189 graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
190 Color areaColor = AREACOLORS[counter % LINECOLORS.length];
192 graphDef.area(Integer.toString(counter), areaColor);
193 graphDef.line(Integer.toString(counter), color, label, 2);
198 public void init(ServletConfig config) throws ServletException {
202 public ServletConfig getServletConfig() {
207 public String getServletInfo() {
212 public void destroy() {
215 // ----------------------------------------------------------
216 // The following methods implement the ChartServlet interface
219 public String getName() {
224 public BufferedImage createChart(String service, String theme, Date startTime, Date endTime, int height, int width,
225 String items, String groups, Integer dpi, Boolean legend) throws ItemNotFoundException {
226 RrdGraphDef graphDef = new RrdGraphDef();
228 long period = (startTime.getTime() - endTime.getTime()) / 1000;
230 graphDef.setWidth(width);
231 graphDef.setHeight(height);
232 graphDef.setAntiAliasing(true);
233 graphDef.setImageFormat("PNG");
234 graphDef.setStartTime(period);
235 graphDef.setTextAntiAliasing(true);
236 graphDef.setLargeFont(new Font("SansSerif", Font.PLAIN, 15));
237 graphDef.setSmallFont(new Font("SansSerif", Font.PLAIN, 11));
239 int seriesCounter = 0;
241 // Loop through all the items
243 String[] itemNames = items.split(",");
244 for (String itemName : itemNames) {
245 Item item = itemUIRegistry.getItem(itemName);
246 addLine(graphDef, item, seriesCounter++);
250 // Loop through all the groups and add each item from each group
251 if (groups != null) {
252 String[] groupNames = groups.split(",");
253 for (String groupName : groupNames) {
254 Item item = itemUIRegistry.getItem(groupName);
255 if (item instanceof GroupItem) {
256 GroupItem groupItem = (GroupItem) item;
257 for (Item member : groupItem.getMembers()) {
258 addLine(graphDef, member, seriesCounter++);
261 throw new ItemNotFoundException("Item '" + item.getName() + "' defined in groups is not a group.");
266 // Write the chart as a PNG image
269 graph = new RrdGraph(graphDef);
270 BufferedImage bi = new BufferedImage(graph.getRrdGraphInfo().getWidth(),
271 graph.getRrdGraphInfo().getHeight(), BufferedImage.TYPE_INT_RGB);
272 graph.render(bi.getGraphics());
275 } catch (IOException e) {
276 logger.error("Error generating graph.", e);
283 public ImageType getChartType() {
284 return ImageType.png;