/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.plugin.mysql; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.sql.SQLException; import java.util.HashMap; import java.util.Properties; import org.hyperic.hq.product.JDBCMeasurementPlugin; import org.hyperic.hq.product.Metric; import org.hyperic.hq.product.MetricInvalidException; import org.hyperic.hq.product.MetricNotFoundException; import org.hyperic.hq.product.MetricUnreachableException; import org.hyperic.hq.product.MetricValue; import org.hyperic.hq.product.PluginException; import org.hyperic.hq.product.TypeInfo; import org.hyperic.util.StringUtil; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.config.ConfigSchema; import org.hyperic.util.jdbc.DBUtil; public class MySQLMeasurementPlugin extends JDBCMeasurementPlugin { // Driver defaults static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; // Measurement Queries private static final String STATUSQUERY = "SHOW /*!50002 GLOBAL */ STATUS LIKE "; private static final String TABLEQUERY = "SHOW TABLE STATUS LIKE %table%"; private static final String INDEXQUERY = "SHOW INDEX FROM %table%"; private static final String DBQUERY = "SHOW TABLE STATUS"; private static final String NUMDATABASES = "SHOW DATABASES"; private static final String CONNECT_TIMEOUT_KEY = "connectTimeout"; private static final String SOCKET_TIMEOUT_KEY = "socketTimeout"; private static final int TIMEOUT_VALUE = 60000; private static HashMap columnMap = null; protected void getDriver() throws ClassNotFoundException { Class.forName(JDBC_DRIVER); } protected Connection getConnection(String url, String user, String password) throws SQLException { return DriverManager.getConnection(getJdbcUrl(url), user, password); } // convenience method used to set timeout properties for JDBC connection private String getJdbcUrl(String url) { if (url == null) { return url; } else if (url.indexOf('?') > 0) { return url + "&socketTimeout=" + TIMEOUT_VALUE + "&connectTimeout=" + TIMEOUT_VALUE; } else { return url + "?socketTimeout=" + TIMEOUT_VALUE + "&connectTimeout=" + TIMEOUT_VALUE; } } protected String getDefaultURL() { // defined in hq-plugin.xml return getPluginProperty("DEFAULT_URL"); } protected void initQueries() { if (columnMap != null) return; columnMap = new HashMap(); // Table metrics columnMap.put("availability", new Integer(4)); columnMap.put("Rows", new Integer(4)); columnMap.put("Avg_row_length", new Integer(5)); columnMap.put("Data_length", new Integer(6)); columnMap.put("Max_data_length", new Integer(7)); columnMap.put("Index_length", new Integer(8)); columnMap.put("Data_free", new Integer(9)); } /** * Mysql results are in different columns depending on what metric * is being collected. Server metrics are always in the second column, * table metrics come from a lookup map. */ protected int getColumn(Metric metric) { String queryVal = metric.getAttributeName(); Integer index = (Integer)columnMap.get(queryVal); if (index == null) return 2; return COL_INVALID; // Force parent to call getColumnName() } protected String getColumnName(Metric metric) { return metric.getAttributeName(); } private boolean isCumulativeMetric(String attr) { return attr.equals("Data_length") || attr.equals("Rows") || attr.equals("Index_length"); } private boolean isIndexMetric(String attr) { return attr.equals("NumIndicies") || attr.equals("AvgCardinality"); } public MetricValue getValue(Metric metric) throws PluginException, MetricUnreachableException, MetricInvalidException, MetricNotFoundException { String alias = metric.getAttributeName(); boolean isAvail = alias.equalsIgnoreCase(AVAIL_ATTR); if (-1 == alias.toLowerCase().indexOf("numberofdatabases") && !isAvail) { return super.getValue(metric); } Statement stmt = null; ResultSet rs = null; try { Connection conn = getCachedConnection(metric); stmt = conn.createStatement(); rs = stmt.executeQuery(NUMDATABASES); long now = System.currentTimeMillis(); if (isAvail) { return new MetricValue(Metric.AVAIL_UP, now); } int value = 0; while (rs.next()) { value++; } return new MetricValue(value, System.currentTimeMillis()); } catch (SQLException e) { long now = System.currentTimeMillis(); if (isAvail) { return new MetricValue(Metric.AVAIL_DOWN, now); } else { String msg = "Query failed for " + alias + ": " + e.getMessage() + " (hint: database may be down or " + " the user is not setup correctly)"; throw new MetricUnreachableException(msg, e); } } finally { DBUtil.closeJDBCObjects(getLog(), null, stmt, rs); } } protected String getQuery(Metric metric) { Properties objectProps = metric.getObjectProperties(); Properties props = metric.getProperties(); String queryVal = metric.getAttributeName(); if (isIndexMetric(queryVal)) { String table = objectProps.getProperty(PROP_TABLE); if (table == null) { // Backward compat table = props.getProperty(PROP_TABLE); } return StringUtil.replace(INDEXQUERY, "%table%", table); } if (objectProps.getProperty("Type"). equals(MySQLServerDetector.TABLE)) { String table = objectProps.getProperty(PROP_TABLE); if (table == null) { // Backwards compat table = props.getProperty(PROP_TABLE); } return StringUtil.replace(TABLEQUERY, "%table%", "'" + table + "'"); } // Cumulative metrics if (isCumulativeMetric(queryVal)) { return DBQUERY; } return STATUSQUERY + "'" + queryVal + "'"; } public ConfigSchema getConfigSchema(TypeInfo info, ConfigResponse config) { // Override JDBCMeasurementPlugin. return new ConfigSchema(); } /** * Since there is no way to retrieve index metric information on a * per-index basis we need to duplicate getQueryValue from the JDBC * measurement plugin so that we can iterate the results looking for * the index in question. */ protected double getQueryValue(Metric jdsn) throws MetricNotFoundException, PluginException, MetricUnreachableException { initQueries(); String query = getQuery(jdsn); String attr = jdsn.getAttributeName(); boolean isIndex = isIndexMetric(attr); boolean isCumulative = isCumulativeMetric(attr); // Cumulative and index metrics need to be computed by hand by // iterating over the results given by the 'SHOW xxx STATUS command. if (!(isIndex || isCumulative)) { return super.getQueryValue(jdsn); } if (query == null) { //plugin bug or hq-plugin.xml typo bug String msg = "No SQL query mapped to: " + attr; throw new PluginException(msg); } Properties props = jdsn.getProperties(); String url = props.getProperty(PROP_URL), user = props.getProperty(PROP_USER), pass = props.getProperty(PROP_PASSWORD); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; double numIndicies = 0; double totCardinality = 0; double dataLength = 0; double indexLength = 0; double totalRows = 0; try { conn = getCachedConnection(url, user, pass); //XXX cache prepared statements ps = conn.prepareStatement(query); rs = ps.executeQuery(); while (rs != null && rs.next()) { if (isIndex) { double cardinality = rs.getDouble(7); numIndicies++; totCardinality += cardinality; } else if (isCumulative) { dataLength += rs.getDouble(attr); indexLength += rs.getDouble(attr); totalRows += rs.getDouble(attr); } } if (attr.equals("NumIndicies")) { return numIndicies; } else if (attr.equals("AvgCardinality")) { return totCardinality/numIndicies; } else if (attr.equals("Data_length")) { return dataLength; } else if (attr.equals("Index_length")) { return indexLength; } else if (attr.equals("Rows")) { return totalRows; } else { //Shouldn't happen throw new MetricNotFoundException(attr); } } catch (SQLException e) { // Remove this connection from the cache. removeCachedConnection(url, user, pass); String msg = "Query failed for " + attr + ": " + e.getMessage(); throw new MetricNotFoundException(msg, e); } finally { DBUtil.closeJDBCObjects(getLog(), null, ps, rs); } } }