/* This file is part of VoltDB. * Copyright (C) 2008-2010 VoltDB L.L.C. * * VoltDB 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. * * VoltDB 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 VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.client; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import org.apache.log4j.Logger; import org.voltdb.VoltTable; import edu.brown.catalog.CatalogUtil; /** * Polls a Distributer instance for IO and procedure invocation information and ELTs the results * to a database via JDBC. * */ public class ClientStatsSqlLoader implements ClientStatsLoader { private static final Logger LOG = Logger.getLogger(ClientStatsSqlLoader.class); private final StatsUploaderSettings m_settings; private final Connection m_conn; private final String m_applicationName; private final String m_subApplicationName; private final int m_pollInterval; private final Distributer m_distributer; private int m_instanceId = -1; private final Thread m_loadThread = new Thread(new Loader(), "Client stats loader"); private static final String tablePrefix = ""; // "ma_"; private static final String instancesTable = tablePrefix + "clientInstances"; private static final String connectionStatsTable = tablePrefix + "clientConnectionStats"; private static final String procedureStatsTable = tablePrefix + "clientProcedureStats"; private static final String createInstanceStatement = "insert into " + instancesTable + " ( clusterStartTime, clusterLeaderAddress, applicationName, subApplicationName, " + " numHosts, numSites, numPartitions) " + "values ( ?, ?, ?, ?, ?, ?, ? );"; private static final String insertConnectionStatsStatement = "insert into " + connectionStatsTable + " ( instanceId, tsEvent, hostname, connectionId, serverHostId, serverHostname, " + " serverConnectionId, numInvocations, numAborts, numFailures, numThrottled, numBytesRead, " + " numMessagesRead, numBytesWritten, numMessagesWritten) " + "values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );"; private static final String insertProcedureStatsStatement = "insert into " + procedureStatsTable + " ( instanceId, tsEvent, hostname, connectionId, serverHostId, serverHostname, " + " serverConnectionId, procedureName, roundtripAvg, roundtripMin, roundtripMax, " + " clusterRoundtripAvg, clusterRoundtripMin, clusterRoundtripMax, " + " numInvocations, numAborts, numFailures, numRestarts) " + "values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );"; private PreparedStatement insertConnectionStatsStmt; private PreparedStatement insertProcedureStatsStmt; public ClientStatsSqlLoader( StatsUploaderSettings settings, Distributer distributer) { if (LOG.isDebugEnabled()) LOG.debug("Creating new connection to stats database [" + settings.databaseURL + "]"); try { if (settings.databaseJDBC != null && settings.databaseJDBC.isEmpty() == false) { Class.forName(settings.databaseJDBC); } m_conn = DriverManager.getConnection(settings.databaseURL, settings.databaseUser, settings.databasePass); m_conn.setAutoCommit(false); m_conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); } catch (Exception e) { String msg = "Failed to connect to SQL reporting server with message:\n "; msg += e.getMessage(); throw new RuntimeException(msg); } m_settings = settings; m_applicationName = settings.applicationName; m_subApplicationName = settings.subApplicationName; m_pollInterval = settings.pollInterval; m_distributer = distributer; } public void start(long startTime, int leaderAddress) throws SQLException { Timestamp timestamp = new Timestamp(startTime); if (LOG.isDebugEnabled()) LOG.debug(String.format("Cluster Start Time: %s [%d]", timestamp, startTime)); PreparedStatement instanceStmt = m_conn.prepareStatement( createInstanceStatement, PreparedStatement.RETURN_GENERATED_KEYS); insertConnectionStatsStmt = m_conn.prepareStatement(insertConnectionStatsStatement); insertProcedureStatsStmt = m_conn.prepareStatement(insertProcedureStatsStatement); instanceStmt.setTimestamp( 1, timestamp); instanceStmt.setInt( 2, leaderAddress); instanceStmt.setString( 3, m_applicationName); if (m_subApplicationName != null) { instanceStmt.setString( 4, m_subApplicationName); } else { instanceStmt.setNull( 4, Types.VARCHAR); } instanceStmt.setInt(5, CatalogUtil.getNumberOfHosts(m_settings.getCatalog())); instanceStmt.setInt(6, CatalogUtil.getNumberOfSites(m_settings.getCatalog())); instanceStmt.setInt(7, CatalogUtil.getNumberOfPartitions(m_settings.getCatalog())); instanceStmt.execute(); ResultSet results = instanceStmt.getGeneratedKeys(); while (results.next()) { m_instanceId = results.getInt( 1 ); } results.close(); instanceStmt.close(); if (m_instanceId < 0) { throw new SQLException("Unable to generate an instance id to identify this client"); } insertConnectionStatsStmt.setInt( 1, m_instanceId); insertProcedureStatsStmt.setInt( 1, m_instanceId); m_conn.commit(); m_loadThread.setDaemon(true); m_loadThread.start(); if (LOG.isDebugEnabled()) LOG.debug("ClientStatsLoader has been started"); } public synchronized void stop() throws InterruptedException { m_shouldStop = true; notifyAll(); while (!m_stopped) { wait(); } } private boolean m_shouldStop = false; private boolean m_stopped = false; private class Loader implements Runnable { @Override public void run() { long sleepLess = 0; synchronized (ClientStatsSqlLoader.this) { try { while (true) { if (m_shouldStop) { break; } try { if (m_pollInterval - sleepLess > 0) { ClientStatsSqlLoader.this.wait(m_pollInterval - sleepLess); } } catch (InterruptedException e) { return; } final long startTime = System.currentTimeMillis(); final VoltTable ioStats = m_distributer .getConnectionStats(true); final VoltTable procedureStats = m_distributer .getProcedureStats(true); try { while (ioStats.advanceRow()) { int index = 1; insertConnectionStatsStmt.setInt(index++, m_instanceId); insertConnectionStatsStmt.setTimestamp(index++, new Timestamp(ioStats.getLong("TIMESTAMP"))); insertConnectionStatsStmt.setString(index++, ioStats.getString("HOSTNAME")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("CONNECTION_ID")); insertConnectionStatsStmt .setInt(index++, (int) ioStats .getLong("SERVER_HOST_ID")); insertConnectionStatsStmt.setString(index++, ioStats.getString("SERVER_HOSTNAME")); insertConnectionStatsStmt .setLong( index++, ioStats .getLong("SERVER_CONNECTION_ID")); insertConnectionStatsStmt .setLong( index++, ioStats .getLong("INVOCATIONS_COMPLETED")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("INVOCATIONS_ABORTED")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("INVOCATIONS_FAILED")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("INVOCATIONS_THROTTLED")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("BYTES_READ")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("MESSAGES_READ")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("BYTES_WRITTEN")); insertConnectionStatsStmt.setLong(index++, ioStats.getLong("MESSAGES_WRITTEN")); insertConnectionStatsStmt.addBatch(); } insertConnectionStatsStmt.executeBatch(); } catch (SQLException e) { if (e.getCause() instanceof InterruptedException) { return; } e.printStackTrace(); } try { while (procedureStats.advanceRow()) { int index = 1; insertProcedureStatsStmt.setInt(index++, m_instanceId); insertProcedureStatsStmt.setTimestamp(index++, new Timestamp(procedureStats.getLong("TIMESTAMP"))); insertProcedureStatsStmt.setString(index++, procedureStats.getString("HOSTNAME")); insertProcedureStatsStmt .setLong(index++, procedureStats .getLong("CONNECTION_ID")); insertProcedureStatsStmt.setInt(index++, (int) procedureStats .getLong("SERVER_HOST_ID")); insertProcedureStatsStmt.setString(index++, procedureStats .getString("SERVER_HOSTNAME")); insertProcedureStatsStmt .setLong( index++, procedureStats .getLong("SERVER_CONNECTION_ID")); insertProcedureStatsStmt.setString(index++, procedureStats .getString("PROCEDURE_NAME")); insertProcedureStatsStmt.setInt(index++, (int) procedureStats .getLong("ROUNDTRIPTIME_AVG")); insertProcedureStatsStmt.setInt(index++, (int) procedureStats .getLong("ROUNDTRIPTIME_MIN")); insertProcedureStatsStmt.setInt(index++, (int) procedureStats .getLong("ROUNDTRIPTIME_MAX")); insertProcedureStatsStmt .setInt( index++, (int) procedureStats .getLong("CLUSTER_ROUNDTRIPTIME_AVG")); insertProcedureStatsStmt .setInt( index++, (int) procedureStats .getLong("CLUSTER_ROUNDTRIPTIME_MIN")); insertProcedureStatsStmt .setInt( index++, (int) procedureStats .getLong("CLUSTER_ROUNDTRIPTIME_MAX")); insertProcedureStatsStmt .setLong( index++, procedureStats .getLong("INVOCATIONS_COMPLETED")); insertProcedureStatsStmt .setLong(index++, procedureStats .getLong("INVOCATIONS_ABORTED")); insertProcedureStatsStmt.setLong(index++, procedureStats .getLong("INVOCATIONS_FAILED")); insertProcedureStatsStmt.setLong(index++, procedureStats .getLong("TIMES_RESTARTED")); insertProcedureStatsStmt.addBatch(); } insertProcedureStatsStmt.executeBatch(); } catch (SQLException e) { if (e.getCause() instanceof InterruptedException) { return; } e.printStackTrace(); } try { m_conn.commit(); } catch (SQLException e) { e.printStackTrace(); } final long endTime = System.currentTimeMillis(); sleepLess = endTime - startTime; } } finally { m_stopped = true; ClientStatsSqlLoader.this.notifyAll(); } } } } }