Basic JMX management/reporting – counters for commands and records v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Thu, 25 Sep 2014 17:50:40 +0200
branchv_0
changeset 179 236332caeb29
parent 178 5a5fc66f11b1
child 180 74a6d55da11c
Basic JMX management/reporting – counters for commands and records
distributions/debian/build.sh
java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java
java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagement.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ManagementUtils.java
--- a/distributions/debian/build.sh	Wed Sep 24 22:53:30 2014 +0200
+++ b/distributions/debian/build.sh	Thu Sep 25 17:50:40 2014 +0200
@@ -41,7 +41,7 @@
 CONTROL_FILE="equivs-control" &&
 COPYRIGHT_FILE="copyright" &&
 URL="https://sql-dk.globalcode.info/" &&
-VERSION="0.9" &&
+VERSION="0.10" &&
 
 echo "Section: database
 Priority: optional
--- a/java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java	Wed Sep 24 22:53:30 2014 +0200
+++ b/java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java	Thu Sep 25 17:50:40 2014 +0200
@@ -31,6 +31,8 @@
 import info.globalcode.sql.dk.formatting.Formatter;
 import info.globalcode.sql.dk.formatting.FormatterContext;
 import info.globalcode.sql.dk.formatting.FormatterException;
+import info.globalcode.sql.dk.jmx.ConnectionManagement;
+import info.globalcode.sql.dk.jmx.ManagementUtils;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -149,7 +151,9 @@
 	private void processQueryNow() throws ConfigurationException, SQLException, FormatterException {
 		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
 		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
-		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties())) {
+		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
+
+		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
 			log.log(Level.FINE, "Database connected");
 			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
 				c.executeQuery(options.getSQLCommand(), f);
@@ -169,7 +173,9 @@
 
 		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
 		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
-		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties())) {
+		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
+
+		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
 			log.log(Level.FINE, "Database connected");
 			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
 				c.executeBatch(b, f);
--- a/java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java	Wed Sep 24 22:53:30 2014 +0200
+++ b/java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java	Thu Sep 25 17:50:40 2014 +0200
@@ -17,6 +17,8 @@
  */
 package info.globalcode.sql.dk;
 
+import static info.globalcode.sql.dk.jmx.ConnectionManagement.incrementCounter;
+import static info.globalcode.sql.dk.jmx.ConnectionManagement.resetCounter;
 import info.globalcode.sql.dk.batch.Batch;
 import info.globalcode.sql.dk.batch.BatchException;
 import info.globalcode.sql.dk.configuration.DatabaseDefinition;
@@ -24,6 +26,8 @@
 import info.globalcode.sql.dk.configuration.Property;
 import info.globalcode.sql.dk.formatting.ColumnsHeader;
 import info.globalcode.sql.dk.formatting.Formatter;
+import info.globalcode.sql.dk.jmx.ConnectionManagement;
+import info.globalcode.sql.dk.jmx.ConnectionManagement.COUNTER;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
@@ -50,10 +54,23 @@
 	private final DatabaseDefinition databaseDefinition;
 	private final Connection connection;
 	private final Properties properties;
+	/**
+	 * Could be null = JMX is disabled → must check, see functions in
+	 * {@linkplain ConnectionManagement}
+	 */
+	private final ConnectionManagement connectionMBean;
 
-	public DatabaseConnection(DatabaseDefinition databaseDefinition, Properties properties) throws SQLException {
+	/**
+	 *
+	 * @param databaseDefinition DB url, name, password etc.
+	 * @param properties additional properties from CLI
+	 * @param connectionMBean JMX management bean | null = disabled JMX reporting
+	 * @throws SQLException
+	 */
+	public DatabaseConnection(DatabaseDefinition databaseDefinition, Properties properties, ConnectionManagement connectionMBean) throws SQLException {
 		this.databaseDefinition = databaseDefinition;
 		this.properties = properties;
+		this.connectionMBean = connectionMBean;
 
 		if (properties.hasProperty(JDBC_PROPERTY_PASSWORD)) {
 			log.log(Level.WARNING, "Passing DB password as CLI parameter is insecure!");
@@ -97,6 +114,9 @@
 	}
 
 	private void processCommand(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
+		incrementCounter(connectionMBean, COUNTER.COMMAND);
+		resetCounter(connectionMBean, COUNTER.RECORD_CURRENT);
+		
 		try (PreparedStatement ps = sqlCommand.prepareStatement(connection)) {
 			log.log(Level.FINE, "Statement prepared");
 			sqlCommand.parametrize(ps);
@@ -135,6 +155,9 @@
 		int columnCount = rs.getMetaData().getColumnCount();
 
 		while (rs.next()) {
+			incrementCounter(connectionMBean, COUNTER.RECORD_CURRENT);
+			incrementCounter(connectionMBean, COUNTER.RECORD_TOTAL);
+			
 			formatter.writeStartRow();
 
 			for (int i = 1; i <= columnCount; i++) {
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java	Wed Sep 24 22:53:30 2014 +0200
+++ b/java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java	Thu Sep 25 17:50:40 2014 +0200
@@ -19,7 +19,14 @@
 
 import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
 import info.globalcode.sql.dk.DatabaseConnection;
+import info.globalcode.sql.dk.jmx.ConnectionManagement;
+import java.lang.management.ManagementFactory;
 import java.sql.SQLException;
+import java.util.Hashtable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
 import javax.xml.bind.annotation.XmlElement;
 
 /**
@@ -29,6 +36,7 @@
  */
 public class DatabaseDefinition implements NameIdentified {
 
+	private static final Logger log = Logger.getLogger(DatabaseDefinition.class.getName());
 	private String name;
 	private String url;
 	private String userName;
@@ -83,8 +91,17 @@
 
 	/**
 	 * @param properties ad-hoc properties from CLI options (for the JDBC driver)
+	 * @param jmxBean JMX management bean for progress reporting | null = disable JMX
+	 */
+	public DatabaseConnection connect(Properties properties, ConnectionManagement jmxBean) throws SQLException {
+		return new DatabaseConnection(this, properties, jmxBean);
+	}
+
+	/**
+	 * @see #connect(info.globalcode.sql.dk.configuration.Properties, java.lang.String)
+	 * With disabled JMX reporting.
 	 */
 	public DatabaseConnection connect(Properties properties) throws SQLException {
-		return new DatabaseConnection(this, properties);
+		return new DatabaseConnection(this, properties, null);
 	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagement.java	Thu Sep 25 17:50:40 2014 +0200
@@ -0,0 +1,97 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.jmx;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * JMX management bean for progress reporting.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ConnectionManagement implements ConnectionManagementMBean {
+
+	private final String databaseName;
+	private final Map<COUNTER, Integer> counters = new EnumMap(COUNTER.class);
+
+	public ConnectionManagement(String databaseName) {
+		this.databaseName = databaseName;
+		for (COUNTER c : COUNTER.values()) {
+			counters.put(c, 0);
+		}
+	}
+
+	public enum COUNTER {
+
+		COMMAND,
+		RECORD_CURRENT,
+		RECORD_TOTAL
+	};
+
+	public void incrementCounter(COUNTER counter) {
+		synchronized (counters) {
+			int old = counters.get(counter);
+			counters.put(counter, old + 1);
+		}
+	}
+
+	public void resetCounter(COUNTER counter) {
+		synchronized (counters) {
+			counters.put(counter, 0);
+		}
+	}
+
+	public static void incrementCounter(ConnectionManagement mbean, COUNTER counter) {
+		if (mbean != null) {
+			mbean.incrementCounter(counter);
+		}
+	}
+
+	public static void resetCounter(ConnectionManagement mbean, COUNTER counter) {
+		if (mbean != null) {
+			mbean.resetCounter(counter);
+		}
+	}
+
+	@Override
+	public String getDatabaseName() {
+		return databaseName;
+	}
+
+	@Override
+	public int getCommandCount() {
+		synchronized (counters) {
+			return counters.get(COUNTER.COMMAND);
+		}
+	}
+
+	@Override
+	public int getCurrentRecordCount() {
+		synchronized (counters) {
+			return counters.get(COUNTER.RECORD_CURRENT);
+		}
+	}
+	
+	@Override
+	public int getTotalRecordCount() {
+		synchronized (counters) {
+			return counters.get(COUNTER.RECORD_TOTAL);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java	Thu Sep 25 17:50:40 2014 +0200
@@ -0,0 +1,34 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.jmx;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public interface ConnectionManagementMBean {
+
+	public String getDatabaseName();
+
+	public int getCommandCount();
+
+	public int getCurrentRecordCount();
+
+	public int getTotalRecordCount();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/info/globalcode/sql/dk/jmx/ManagementUtils.java	Thu Sep 25 17:50:40 2014 +0200
@@ -0,0 +1,68 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.jmx;
+
+import java.lang.management.ManagementFactory;
+import java.util.Hashtable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ManagementUtils {
+
+	private static final Logger log = Logger.getLogger(ManagementUtils.class.getName());
+	public static final String DEFAULT_CONNECTION_JMX_NAME = "main";
+
+	/**
+	 * @see #registerMBean(java.lang.String, java.lang.String) with default JMX name
+	 */
+	public static ConnectionManagement registerMBean(String dbName) {
+		return registerMBean(dbName, DEFAULT_CONNECTION_JMX_NAME);
+	}
+
+	/**
+	 *
+	 * @param dbName database name
+	 * @param jmxName name of JMX bean
+	 * @return registered JMX bean | or null if registration fails (should not)
+	 */
+	public static ConnectionManagement registerMBean(String dbName, String jmxName) {
+		try {
+			ConnectionManagement mbean = new ConnectionManagement(dbName);
+			MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+			Hashtable<String, String> objectProperties = new Hashtable<>();
+			objectProperties.put("type", "Connection");
+			objectProperties.put("name", jmxName);
+			ObjectName objectName = new ObjectName("info.globalcode.sql.dk", objectProperties);
+			mbs.registerMBean(mbean, objectName);
+			log.log(Level.FINE, "JMX MBean was registered as: {0}", objectName);
+			return mbean;
+		} catch (Exception e) {
+			log.log(Level.WARNING, "Unable to register JMX MBean", e);
+			return null;
+		}
+	}
+
+	private ManagementUtils() {
+	}
+}