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;
22 import javax.imageio.ImageIO;
23 import javax.servlet.Servlet;
24 import javax.servlet.ServletConfig;
25 import javax.servlet.ServletException;
26 import javax.servlet.ServletRequest;
27 import javax.servlet.ServletResponse;
29 import org.openhab.core.items.GroupItem;
30 import org.openhab.core.items.Item;
31 import org.openhab.core.items.ItemNotFoundException;
32 import org.openhab.core.library.items.NumberItem;
33 import org.openhab.core.ui.chart.ChartProvider;
34 import org.openhab.core.ui.items.ItemUIRegistry;
35 import org.openhab.persistence.rrd4j.internal.RRD4jPersistenceService;
36 import org.osgi.service.component.annotations.Activate;
37 import org.osgi.service.component.annotations.Component;
38 import org.osgi.service.component.annotations.Deactivate;
39 import org.osgi.service.component.annotations.Reference;
40 import org.osgi.service.http.HttpService;
41 import org.osgi.service.http.NamespaceException;
42 import org.rrd4j.ConsolFun;
43 import org.rrd4j.core.RrdDb;
44 import org.rrd4j.graph.RrdGraph;
45 import org.rrd4j.graph.RrdGraphDef;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * This servlet generates time-series charts for a given set of items.
51 * It accepts the following HTTP parameters:
53 * <li>w: width in pixels of image to generate</li>
54 * <li>h: height in pixels of image to generate</li>
55 * <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>
56 * <li>items: A comma separated list of item names to display
57 * <li>groups: A comma separated list of group names, whose members should be displayed
60 * @author Kai Kreuzer - Initial contribution
61 * @author Chris Jackson - a few improvements
62 * @author Jan N. Klug - a few improvements
65 @Component(service = ChartProvider.class)
66 public class RRD4jChartServlet implements Servlet, ChartProvider {
68 private final Logger logger = LoggerFactory.getLogger(RRD4jChartServlet.class);
70 /** the URI of this servlet */
71 public static final String SERVLET_NAME = "/rrdchart.png";
73 protected static final Color[] LINECOLORS = new Color[] { Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA,
74 Color.ORANGE, Color.CYAN, Color.PINK, Color.DARK_GRAY, Color.YELLOW };
75 protected static final Color[] AREACOLORS = new Color[] { new Color(255, 0, 0, 30), new Color(0, 255, 0, 30),
76 new Color(0, 0, 255, 30), new Color(255, 0, 255, 30), new Color(255, 128, 0, 30),
77 new Color(0, 255, 255, 30), new Color(255, 0, 128, 30), new Color(255, 128, 128, 30),
78 new Color(255, 255, 0, 30) };
80 protected static final Map<String, Long> PERIODS = new HashMap<>();
83 PERIODS.put("h", -3600000L);
84 PERIODS.put("4h", -14400000L);
85 PERIODS.put("8h", -28800000L);
86 PERIODS.put("12h", -43200000L);
87 PERIODS.put("D", -86400000L);
88 PERIODS.put("3D", -259200000L);
89 PERIODS.put("W", -604800000L);
90 PERIODS.put("2W", -1209600000L);
91 PERIODS.put("M", -2592000000L);
92 PERIODS.put("2M", -5184000000L);
93 PERIODS.put("4M", -10368000000L);
94 PERIODS.put("Y", -31536000000L);
98 protected HttpService httpService;
101 protected ItemUIRegistry itemUIRegistry;
104 protected void activate() {
106 logger.debug("Starting up rrd chart servlet at {}", SERVLET_NAME);
107 httpService.registerServlet(SERVLET_NAME, this, new Hashtable<>(), httpService.createDefaultHttpContext());
108 } catch (NamespaceException e) {
109 logger.error("Error during servlet startup", e);
110 } catch (ServletException e) {
111 logger.error("Error during servlet startup", e);
116 protected void deactivate() {
117 httpService.unregister(SERVLET_NAME);
121 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
122 logger.debug("RRD4J received incoming chart request: {}", req);
126 width = Integer.parseInt(Objects.requireNonNull(req.getParameter("w")));
127 } catch (Exception e) {
131 height = Integer.parseInt(Objects.requireNonNull(req.getParameter("h")));
132 } catch (Exception e) {
134 Long period = PERIODS.get(req.getParameter("period"));
135 if (period == null) {
136 // use a day as the default period
137 period = PERIODS.get("D");
139 // Create the start and stop time
140 Date timeEnd = new Date();
141 Date timeBegin = new Date(timeEnd.getTime() + period);
143 // Set the content type to that provided by the chart provider
144 res.setContentType("image/" + getChartType());
146 BufferedImage chart = createChart(null, null, timeBegin, timeEnd, height, width, req.getParameter("items"),
147 req.getParameter("groups"), null, null);
148 ImageIO.write(chart, getChartType().toString(), res.getOutputStream());
149 } catch (ItemNotFoundException e) {
150 logger.debug("Item not found error while generating chart.");
151 } catch (IllegalArgumentException e) {
152 logger.debug("Illegal argument in chart", e);
157 * Adds a line for the item to the graph definition.
158 * The color of the line is determined by the counter, it simply picks the according index from LINECOLORS (and
159 * rolls over if necessary).
161 * @param graphDef the graph definition to fill
162 * @param item the item to add a line for
163 * @param counter defines the number of the datasource and is used to determine the line color
165 protected void addLine(RrdGraphDef graphDef, Item item, int counter) {
166 Color color = LINECOLORS[counter % LINECOLORS.length];
167 String label = itemUIRegistry.getLabel(item.getName());
168 String rrdName = RRD4jPersistenceService.DB_FOLDER + File.separator + item.getName() + ".rrd";
170 if (label != null && label.contains("[") && label.contains("]")) {
171 label = label.substring(0, label.indexOf('['));
174 RrdDb db = new RrdDb(rrdName);
175 consolFun = db.getRrdDef().getArcDefs()[0].getConsolFun();
177 } catch (IOException e) {
178 consolFun = ConsolFun.MAX;
180 if (item instanceof NumberItem) {
181 // we only draw a line
182 graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
183 graphDef.line(Integer.toString(counter), color, label, 2);
185 // we draw a line and fill the area beneath it with a transparent color
186 graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
187 Color areaColor = AREACOLORS[counter % LINECOLORS.length];
189 graphDef.area(Integer.toString(counter), areaColor);
190 graphDef.line(Integer.toString(counter), color, label, 2);
195 public void init(ServletConfig config) throws ServletException {
199 public ServletConfig getServletConfig() {
204 public String getServletInfo() {
209 public void destroy() {
212 // ----------------------------------------------------------
213 // The following methods implement the ChartServlet interface
216 public String getName() {
221 public BufferedImage createChart(String service, String theme, Date startTime, Date endTime, int height, int width,
222 String items, String groups, Integer dpi, Boolean legend) throws ItemNotFoundException {
223 RrdGraphDef graphDef = new RrdGraphDef();
225 long period = (startTime.getTime() - endTime.getTime()) / 1000;
227 graphDef.setWidth(width);
228 graphDef.setHeight(height);
229 graphDef.setAntiAliasing(true);
230 graphDef.setImageFormat("PNG");
231 graphDef.setStartTime(period);
232 graphDef.setTextAntiAliasing(true);
233 graphDef.setLargeFont(new Font("SansSerif", Font.PLAIN, 15));
234 graphDef.setSmallFont(new Font("SansSerif", Font.PLAIN, 11));
236 int seriesCounter = 0;
238 // Loop through all the items
240 String[] itemNames = items.split(",");
241 for (String itemName : itemNames) {
242 Item item = itemUIRegistry.getItem(itemName);
243 addLine(graphDef, item, seriesCounter++);
247 // Loop through all the groups and add each item from each group
248 if (groups != null) {
249 String[] groupNames = groups.split(",");
250 for (String groupName : groupNames) {
251 Item item = itemUIRegistry.getItem(groupName);
252 if (item instanceof GroupItem) {
253 GroupItem groupItem = (GroupItem) item;
254 for (Item member : groupItem.getMembers()) {
255 addLine(graphDef, member, seriesCounter++);
258 throw new ItemNotFoundException("Item '" + item.getName() + "' defined in groups is not a group.");
263 // Write the chart as a PNG image
266 graph = new RrdGraph(graphDef);
267 BufferedImage bi = new BufferedImage(graph.getRrdGraphInfo().getWidth(),
268 graph.getRrdGraphInfo().getHeight(), BufferedImage.TYPE_INT_RGB);
269 graph.render(bi.getGraphics());
272 } catch (IOException e) {
273 logger.error("Error generating graph.", e);
280 public ImageType getChartType() {
281 return ImageType.png;