]> git.basschouten.com Git - openhab-addons.git/blob
58bbf2ada6685526fd5a34f41529da281d8f99b2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.rrd4j.internal.charts;
14
15 import java.awt.Color;
16 import java.awt.Font;
17 import java.awt.image.BufferedImage;
18 import java.io.File;
19 import java.io.IOException;
20 import java.util.*;
21
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;
28
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;
48
49 /**
50  * This servlet generates time-series charts for a given set of items.
51  * It accepts the following HTTP parameters:
52  * <ul>
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
58  * </ul>
59  *
60  * @author Kai Kreuzer - Initial contribution
61  * @author Chris Jackson - a few improvements
62  * @author Jan N. Klug - a few improvements
63  *
64  */
65 @Component(service = ChartProvider.class)
66 public class RRD4jChartServlet implements Servlet, ChartProvider {
67
68     private final Logger logger = LoggerFactory.getLogger(RRD4jChartServlet.class);
69
70     /** the URI of this servlet */
71     public static final String SERVLET_NAME = "/rrdchart.png";
72
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) };
79
80     protected static final Map<String, Long> PERIODS = new HashMap<>();
81
82     static {
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);
95     }
96
97     @Reference
98     protected HttpService httpService;
99
100     @Reference
101     protected ItemUIRegistry itemUIRegistry;
102
103     @Activate
104     protected void activate() {
105         try {
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);
112         }
113     }
114
115     @Deactivate
116     protected void deactivate() {
117         httpService.unregister(SERVLET_NAME);
118     }
119
120     @Override
121     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
122         logger.debug("RRD4J received incoming chart request: {}", req);
123
124         int width = 480;
125         try {
126             width = Integer.parseInt(Objects.requireNonNull(req.getParameter("w")));
127         } catch (Exception e) {
128         }
129         int height = 240;
130         try {
131             height = Integer.parseInt(Objects.requireNonNull(req.getParameter("h")));
132         } catch (Exception e) {
133         }
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");
138         }
139         // Create the start and stop time
140         Date timeEnd = new Date();
141         Date timeBegin = new Date(timeEnd.getTime() + period);
142
143         // Set the content type to that provided by the chart provider
144         res.setContentType("image/" + getChartType());
145         try {
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);
153         }
154     }
155
156     /**
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).
160      *
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
164      */
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";
169         ConsolFun consolFun;
170         if (label != null && label.contains("[") && label.contains("]")) {
171             label = label.substring(0, label.indexOf('['));
172         }
173         try {
174             RrdDb db = new RrdDb(rrdName);
175             consolFun = db.getRrdDef().getArcDefs()[0].getConsolFun();
176             db.close();
177         } catch (IOException e) {
178             consolFun = ConsolFun.MAX;
179         }
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);
184         } else {
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];
188
189             graphDef.area(Integer.toString(counter), areaColor);
190             graphDef.line(Integer.toString(counter), color, label, 2);
191         }
192     }
193
194     @Override
195     public void init(ServletConfig config) throws ServletException {
196     }
197
198     @Override
199     public ServletConfig getServletConfig() {
200         return null;
201     }
202
203     @Override
204     public String getServletInfo() {
205         return null;
206     }
207
208     @Override
209     public void destroy() {
210     }
211
212     // ----------------------------------------------------------
213     // The following methods implement the ChartServlet interface
214
215     @Override
216     public String getName() {
217         return "rrd4j";
218     }
219
220     @Override
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();
224
225         long period = (startTime.getTime() - endTime.getTime()) / 1000;
226
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));
235
236         int seriesCounter = 0;
237
238         // Loop through all the items
239         if (items != null) {
240             String[] itemNames = items.split(",");
241             for (String itemName : itemNames) {
242                 Item item = itemUIRegistry.getItem(itemName);
243                 addLine(graphDef, item, seriesCounter++);
244             }
245         }
246
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++);
256                     }
257                 } else {
258                     throw new ItemNotFoundException("Item '" + item.getName() + "' defined in groups is not a group.");
259                 }
260             }
261         }
262
263         // Write the chart as a PNG image
264         RrdGraph graph;
265         try {
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());
270
271             return bi;
272         } catch (IOException e) {
273             logger.error("Error generating graph.", e);
274         }
275
276         return null;
277     }
278
279     @Override
280     public ImageType getChartType() {
281         return ImageType.png;
282     }
283 }