View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2007, 2014 Massimiliano Ziccardi
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *******************************************************************************/
16  package it.jnrpe.plugin;
17  
18  import it.jnrpe.ICommandLine;
19  import it.jnrpe.ReturnValue;
20  import it.jnrpe.ReturnValue.UnitOfMeasure;
21  import it.jnrpe.Status;
22  import it.jnrpe.plugin.utils.Utils;
23  import it.jnrpe.plugins.PluginBase;
24  import it.jnrpe.utils.BadThresholdException;
25  import it.jnrpe.utils.ThresholdUtil;
26  
27  import java.io.IOException;
28  import java.io.StringReader;
29  import java.io.UnsupportedEncodingException;
30  import java.net.URL;
31  import java.util.Properties;
32  
33  import javax.xml.parsers.DocumentBuilder;
34  import javax.xml.parsers.DocumentBuilderFactory;
35  import javax.xml.parsers.ParserConfigurationException;
36  import javax.xml.xpath.XPath;
37  import javax.xml.xpath.XPathConstants;
38  import javax.xml.xpath.XPathExpressionException;
39  import javax.xml.xpath.XPathFactory;
40  
41  import org.apache.commons.codec.binary.Base64;
42  import org.w3c.dom.Document;
43  import org.w3c.dom.Element;
44  import org.w3c.dom.NodeList;
45  import org.xml.sax.InputSource;
46  import org.xml.sax.SAXException;
47  
48  /**
49   * The CheckTomcat plugin. This plugin does a HTTP GET of the tomcat status
50   * page, by default: /manager/status?XML=true
51   * 
52   * @author Frederico Campos
53   * 
54   */
55  public class CheckTomcat extends PluginBase {
56  
57  	/**
58  	 * Default Tomcat http port.
59  	 */
60  	public static final String DEFAULT_PORT = "8080";
61  
62  	/**
63  	 * Default Tomcat manager URL.
64  	 */
65  	public static final String DEFAULT_URI = "/manager/status?XML=true";
66  
67  	/**
68  	 * Default timeout.
69  	 */
70  	public static final String DEFAULT_TIMEOUT = "10";
71  
72  	/**
73  	 * Executes the check.
74  	 * 
75  	 * @param cl
76  	 *            The command line
77  	 * @return the result of the check
78  	 * @throws BadThresholdException
79  	 *             if the threshold could not be parsed
80  	 */
81  	@Override
82  	public final ReturnValue execute(final ICommandLine cl)
83  			throws BadThresholdException {
84  		log.debug("check_tomcat");
85  		String username = cl.getOptionValue("username");
86  		String password = cl.getOptionValue("password");
87  		String hostname = cl.getOptionValue("hostname");
88  
89  		String port = cl.getOptionValue("port", DEFAULT_PORT);
90  		String uri = cl.getOptionValue("uri", DEFAULT_URI);
91  		String warning = cl.getOptionValue("warning");
92  		String critical = cl.getOptionValue("critical");
93  
94  		int timeout = Integer.parseInt(cl.getOptionValue("timeout",
95  				DEFAULT_TIMEOUT));
96  
97  		if (!uri.startsWith("/")) {
98  			uri = "/" + uri;
99  		}
100 
101 		String protocol;
102 		String credentials;
103 
104 		if (cl.hasOption("ssl")) {
105 			protocol = "https://";
106 		} else {
107 			protocol = "http://";
108 		}
109 
110 		if (password != null) {
111 			credentials = username + ":" + password;
112 		} else {
113 			credentials = username + ":";
114 		}
115 
116 		String url = protocol + credentials + "@" + hostname + ":" + port + uri;
117 
118 		String encoded = null;
119 		try {
120 			encoded = Base64.encodeBase64String((username + ":" + password)
121 					.getBytes("UTF-8"));
122 		} catch (UnsupportedEncodingException e) {
123 			throw new BadThresholdException("Error: " + e.getMessage(), e);
124 		}
125 		Properties props = new Properties();
126 		props.put("Authorization", "Basic " + encoded);
127 		String response = null;
128 		String errmsg = null;
129 		try {
130 			response = Utils.getUrl(new URL(url), props, timeout * 1000);
131 
132 		} catch (Exception e) {
133 			log.info("Plugin execution failed : " + e.getMessage(), e);
134 			errmsg = e.getMessage();
135 		}
136 
137 		if (response == null) {
138 			return new ReturnValue(Status.WARNING, errmsg);
139 		}
140 
141 		boolean checkThreads = cl.hasOption("threads");
142 		boolean checkMemory = cl.hasOption("memory");
143 
144 		// can only have one check at a time
145 		if (checkThreads && checkMemory) {
146 			throw new BadThresholdException(
147 					"Either --memory or --threads allowed in command.");
148 		}
149 		return analyseStatus(response, warning, critical, checkMemory,
150 				checkThreads);
151 	}
152 
153 	/**
154 	 * Parse xml data and return status.
155 	 * 
156 	 * @param xml
157 	 *            The XML to be analyzed
158 	 * @param warning
159 	 *            The warning range
160 	 * @param critical
161 	 *            The critical range
162 	 * @return ReturnValue The reesult
163 	 */
164 	private ReturnValue analyseStatus(final String xml, final String warning,
165 			final String critical, boolean checkMemory, boolean checkThreads)
166 			throws BadThresholdException {
167 		StringBuffer buff = new StringBuffer();
168 		log.debug("checkThreads " + checkThreads);
169 		log.debug("checkMemory " + checkMemory);
170 		log.debug("critical " + critical);
171 		log.debug("warning	 " + warning);
172 
173 		ReturnValue retVal = new ReturnValue(Status.OK, null);
174 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
175 		DocumentBuilder builder = null;
176 		try {
177 			builder = factory.newDocumentBuilder();
178 		} catch (ParserConfigurationException e) {
179 			// This should never happen...
180 			throw new IllegalStateException(e);
181 		}
182 		int freeMem = 0;
183 		int totalMem = 0;
184 		int availableMem = 0;
185 		int maxMem = 0;
186 		int memUse = 0;
187 		int maxMemMb = 0;
188 		int availableMemMb = 0;
189 		int currentThreadCount = 0;
190 		int currentThreadsBusy = 0;
191 		int threadsAvailable = 0;
192 		int maxThreads = 0;
193 
194 		InputSource is = new InputSource(new StringReader(xml));
195 		try {
196 			Document doc = builder.parse(is);
197 			XPathFactory xPathfactory = XPathFactory.newInstance();
198 			XPath xpath = xPathfactory.newXPath();
199 			Element root = (Element) xpath.compile("//status").evaluate(
200 					doc.getDocumentElement(), XPathConstants.NODE);
201 			Element memory = (Element) xpath.compile("//status/jvm/memory")
202 					.evaluate(doc.getDocumentElement(), XPathConstants.NODE);
203 
204 			// check memory
205 			freeMem = Integer.parseInt(memory.getAttribute("free"));
206 			totalMem = Integer.parseInt(memory.getAttribute("total"));
207 			maxMem = Integer.parseInt(memory.getAttribute("max"));
208 			availableMem = freeMem + maxMem - totalMem;
209 
210 			maxMemMb = maxMem / (1024 * 1024);
211 			availableMemMb = availableMem / (1024 * 1024);
212 			memUse = (maxMem - availableMem);
213 			buff.append("JVM memory use " + Utils.formatSize(memUse) + " ");
214 			buff.append("Free: " + Utils.formatSize(freeMem) + ", Total: "
215 					+ Utils.formatSize(totalMem) + ", Max: "
216 					+ Utils.formatSize(maxMem) + " ");
217 
218 			if (checkMemory) {
219 				String warn = warning != null ? getRangeValue(warning, maxMem,
220 						true) : null;
221 				String crit = critical != null ? getRangeValue(critical,
222 						maxMem, true) : null;
223 
224 				if (crit != null
225 						&& ThresholdUtil.isValueInRange(crit, availableMemMb)) {
226 					return new ReturnValue(Status.CRITICAL,
227 							"Free memory critical: " + availableMemMb
228 									+ " MB available").withPerformanceData(
229 							"memory", new Long(maxMemMb), !critical
230 									.contains("%") ? UnitOfMeasure.megabytes
231 									: UnitOfMeasure.percentage, warning,
232 							critical, 0L, new Long(maxMem));
233 				}
234 				if (warn != null
235 						&& ThresholdUtil.isValueInRange(warn, availableMemMb)) {
236 					return new ReturnValue(Status.WARNING, "Free memory low: "
237 							+ availableMem / (1024 * 1024) + " MB available / "
238 							+ buff.toString()).withPerformanceData("memory",
239 							new Long(maxMemMb),
240 							!warning.contains("%") ? UnitOfMeasure.megabytes
241 									: UnitOfMeasure.percentage, warning,
242 							critical, 0L, new Long(maxMem));
243 				}
244 			}
245 
246 			// check threads
247 			NodeList connectors = root.getElementsByTagName("connector");
248 			for (int i = 0; i < connectors.getLength(); i++) {
249 				Element connector = (Element) connectors.item(i);
250 				String connectorName = connector.getAttribute("name");
251 
252 				Element threadInfo = (Element) connector.getElementsByTagName(
253 						"threadInfo").item(0);
254 				maxThreads = Integer.parseInt(threadInfo
255 						.getAttribute("maxThreads"));
256 				currentThreadCount = Integer.parseInt(threadInfo
257 						.getAttribute("currentThreadCount"));
258 				currentThreadsBusy = Integer.parseInt(threadInfo
259 						.getAttribute("currentThreadsBusy"));
260 				threadsAvailable = maxThreads - currentThreadsBusy;
261 				log.debug("Connector " + connectorName + " maxThreads: "
262 						+ maxThreads + ", currentThreadCount:"
263 						+ currentThreadCount + ", currentThreadsBusy: "
264 						+ currentThreadsBusy);
265 
266 				String msg = connectorName + " - thread count: "
267 						+ currentThreadCount + ", current threads busy: "
268 						+ currentThreadsBusy + ", max threads: " + maxThreads;
269 
270 				if (checkThreads) {
271 					String warn = warning != null ? getRangeValue(warning,
272 							maxThreads, false) : null;
273 					String crit = critical != null ? getRangeValue(critical,
274 							maxThreads, false) : null;
275 
276 					if (critical != null
277 							&& ThresholdUtil.isValueInRange(crit,
278 									threadsAvailable)) {
279 						return new ReturnValue(Status.CRITICAL,
280 								"CRITICAL - Free " + connectorName
281 										+ " threads: " + threadsAvailable)
282 								.withMessage(msg)
283 								.withPerformanceData(
284 										connectorName + " threads",
285 										new Long(threadsAvailable),
286 										!critical.contains("%") ? UnitOfMeasure.counter
287 												: UnitOfMeasure.percentage,
288 										warning, critical, 0L,
289 										new Long(maxThreads));
290 					}
291 					if (warning != null
292 							&& ThresholdUtil.isValueInRange(warn,
293 									threadsAvailable)) {
294 						return new ReturnValue(Status.WARNING,
295 								"WARNING - Free " + connectorName
296 										+ " threads: " + threadsAvailable
297 										+ ", " + msg).withPerformanceData(
298 								connectorName + " threads", new Long(
299 										threadsAvailable), !warning
300 										.contains("%") ? UnitOfMeasure.counter
301 										: UnitOfMeasure.percentage, warning,
302 								critical, 0L, new Long(maxThreads));
303 					}
304 
305 				}
306 				buff.append(msg);
307 			}
308 
309 			retVal.withMessage(buff.toString());
310 		} catch (XPathExpressionException e) {
311 			e.printStackTrace();
312 
313 		} catch (SAXException e) {
314 			e.printStackTrace();
315 		} catch (IOException e) {
316 			e.printStackTrace();
317 		}
318 
319 		return retVal;
320 	}
321 
322 	/**
323 	 * Extract numeric value, even if it's a percentage sign.
324 	 * 
325 	 * @param value
326 	 *            The value
327 	 * @param factor
328 	 *            The factor
329 	 * @return int The numeric value
330 	 */
331 	private long getValue(String value, final int factor, boolean memory) {
332 		long val = 0;
333 		if (value != null) {
334 			if (value.contains("%")) {
335 				val = (long) ((factor * Double.parseDouble(value.replace(":",
336 						"").replace("%", ""))) / 100);
337 				if (memory) {
338 					val = val / (1024 * 1024); // MB
339 				}
340 			} else {
341 				val = Long.parseLong(value.replace(":", ""));
342 			}
343 		}
344 		return val;
345 
346 	}
347 
348 	private String getRangeValue(String value, int factor, boolean memory) {
349 		boolean hadRangeStart = false;
350 		boolean hadRangeEnd = false;
351 		if (value.endsWith(":")) {
352 			hadRangeEnd = true;
353 			value = value.substring(0, value.length() - 1);
354 		}
355 		if (value.startsWith(":")) {
356 			hadRangeStart = true;
357 			value = value.substring(1, value.length());
358 		}
359 		String val = "" + getValue(value, factor, memory);
360 
361 		if (hadRangeStart) {
362 			val = ":" + val;
363 
364 		}
365 		if (hadRangeEnd) {
366 			val += ":";
367 		}
368 
369 		return val;
370 	}
371 
372 	@Override
373 	protected String getPluginName() {
374 		return "CHECK_TOMCAT";
375 	}
376 }