/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tomcat.jdbc.test; import java.lang.management.ManagementFactory; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; import java.sql.DriverPropertyInfo; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import javax.management.AttributeChangeNotification; import javax.management.Notification; import javax.management.NotificationListener; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.apache.tomcat.jdbc.pool.ConnectionPool; import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport; import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx; public class TestSlowQueryReport extends DefaultTestCase { public static final String superSlowSql = "select count(1) from test where val1 like 'ewq%eq' and val2 = 'ew%rre' and val3 = 'sda%da' and val4 = 'dad%ada'"; public static final String failedSql = "select 1 from non_existent"; @Before public void setUp() throws SQLException { DriverManager.registerDriver(new MockDriver()); // use our mock driver this.datasource.setDriverClassName(MockDriver.class.getName()); this.datasource.setUrl(MockDriver.url); // Required to trigger validation query's execution this.datasource.setInitialSize(1); this.datasource.setTestOnBorrow(true); this.datasource.setValidationInterval(-1); this.datasource.setValidationQuery("SELECT 1"); this.datasource.setMaxActive(1); this.datasource.setJdbcInterceptors(SlowQueryReportJmx.class.getName()+"(threshold=50,notifyPool=false)"); } @Test public void testSlowSql() throws Exception { int count = 3; this.datasource.setMaxActive(1); this.datasource.setJdbcInterceptors(SlowQueryReport.class.getName()+"(threshold=50)"); Connection con = this.datasource.getConnection(); for (int i=0; i<count; i++) { Statement st = con.createStatement(); ResultSet rs = st.executeQuery(superSlowSql); rs.close(); st.close(); } Map<String,SlowQueryReport.QueryStats> map = SlowQueryReport.getPoolStats(datasource.getPool().getName()); Assert.assertNotNull(map); Assert.assertEquals(1,map.size()); String key = map.keySet().iterator().next(); SlowQueryReport.QueryStats stats = map.get(key); System.out.println("Stats:"+stats); for (int i=0; i<count; i++) { PreparedStatement st = con.prepareStatement(superSlowSql); ResultSet rs = st.executeQuery(); rs.close(); st.close(); } System.out.println("Stats:"+stats); for (int i=0; i<count; i++) { CallableStatement st = con.prepareCall(superSlowSql); ResultSet rs = st.executeQuery(); rs.close(); st.close(); } System.out.println("Stats:"+stats); ConnectionPool pool = datasource.getPool(); con.close(); tearDown(); //make sure we actually did clean up when the pool closed Assert.assertNull(SlowQueryReport.getPoolStats(pool.getName())); } @Test public void testSlowSqlJmx() throws Exception { int count = 1; Connection con = this.datasource.getConnection(); for (int i=0; i<count; i++) { Statement st = con.createStatement(); ResultSet rs = st.executeQuery(superSlowSql); rs.close(); st.close(); } Map<String,SlowQueryReport.QueryStats> map = SlowQueryReport.getPoolStats(datasource.getPool().getName()); Assert.assertNotNull(map); Assert.assertEquals(1,map.size()); String key = map.keySet().iterator().next(); SlowQueryReport.QueryStats stats = map.get(key); System.out.println("Stats:"+stats); ClientListener listener = new ClientListener(); ConnectionPool pool = datasource.getPool(); ManagementFactory.getPlatformMBeanServer().addNotificationListener( new SlowQueryReportJmx().getObjectName(SlowQueryReportJmx.class, pool.getName()), listener, null, null); for (int i=0; i<count; i++) { PreparedStatement st = con.prepareStatement(superSlowSql); ResultSet rs = st.executeQuery(); rs.close(); st.close(); } System.out.println("Stats:"+stats); for (int i=0; i<count; i++) { CallableStatement st = con.prepareCall(superSlowSql); ResultSet rs = st.executeQuery(); rs.close(); st.close(); } System.out.println("Stats:"+stats); Assert.assertEquals("Expecting to have received "+(2*count)+" notifications.",2*count, listener.notificationCount.get()); con.close(); tearDown(); //make sure we actually did clean up when the pool closed Assert.assertNull(SlowQueryReport.getPoolStats(pool.getName())); } @Test public void testFastSql() throws Exception { int count = 3; Connection con = this.datasource.getConnection(); String fastSql = this.datasource.getValidationQuery(); for (int i=0; i<count; i++) { Statement st = con.createStatement(); ResultSet rs = st.executeQuery(fastSql); rs.close(); st.close(); } Map<String,SlowQueryReport.QueryStats> map = SlowQueryReport.getPoolStats(datasource.getPool().getName()); Assert.assertNotNull(map); Assert.assertEquals(1,map.size()); ConnectionPool pool = datasource.getPool(); con.close(); tearDown(); Assert.assertNull(SlowQueryReport.getPoolStats(pool.getName())); } @Test public void testFailedSql() throws Exception { int count = 3; Connection con = this.datasource.getConnection(); for (int i=0; i<count; i++) { Statement st = con.createStatement(); try { ResultSet rs = st.executeQuery(failedSql); rs.close(); }catch (Exception x) { // NO-OP } st.close(); } Map<String,SlowQueryReport.QueryStats> map = SlowQueryReport.getPoolStats(datasource.getPool().getName()); Assert.assertNotNull(map); Assert.assertEquals(1,map.size()); ConnectionPool pool = datasource.getPool(); String key = map.keySet().iterator().next(); SlowQueryReport.QueryStats stats = map.get(key); System.out.println("Stats:"+stats); con.close(); tearDown(); Assert.assertNull(SlowQueryReport.getPoolStats(pool.getName())); } public class ClientListener implements NotificationListener { AtomicInteger notificationCount = new AtomicInteger(0); @Override public void handleNotification(Notification notification, Object handback) { notificationCount.incrementAndGet(); System.out.println("\nReceived notification:"); System.out.println("\tClassName: " + notification.getClass().getName()); System.out.println("\tSource: " + notification.getSource()); System.out.println("\tType: " + notification.getType()); System.out.println("\tMessage: " + notification.getMessage()); if (notification instanceof AttributeChangeNotification) { AttributeChangeNotification acn = (AttributeChangeNotification) notification; System.out.println("\tAttributeName: " + acn.getAttributeName()); System.out.println("\tAttributeType: " + acn.getAttributeType()); System.out.println("\tNewValue: " + acn.getNewValue()); System.out.println("\tOldValue: " + acn.getOldValue()); } } } /** * Mock Driver, Connection and Statement implementations use to verify setQueryTimeout was called. */ public static class MockDriver implements java.sql.Driver { public static final String url = "jdbc:tomcat:mock"; public MockDriver() { } @Override public boolean acceptsURL(String url) throws SQLException { return url!=null && url.equals(MockDriver.url); } @Override public Connection connect(String url, Properties info) throws SQLException { return new MockConnection(info); } @Override public int getMajorVersion() { return 0; } @Override public int getMinorVersion() { return 0; } @Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { return null; } @Override public boolean jdbcCompliant() { return false; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } } public static class MockConnection extends org.apache.tomcat.jdbc.test.driver.Connection { public MockConnection(Properties info) { super(info); } @Override public Statement createStatement() throws SQLException { return new MockStatement(false); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return new MockStatement(sql.equals(superSlowSql)); } @Override public CallableStatement prepareCall(String sql) throws SQLException { return new MockStatement(sql.equals(superSlowSql)); } } public static class MockStatement extends org.apache.tomcat.jdbc.test.driver.Statement { boolean slow = false; public MockStatement(boolean slow) { this.slow = slow; } @Override public boolean execute(String sql) throws SQLException { if (failedSql.equals(sql)) { throw new SQLException("Invalid SQL:"+sql); } if (slow || superSlowSql.equals(sql)) { try { Thread.sleep(200); }catch (Exception x) { } } return super.execute(sql); } @Override public ResultSet executeQuery(String sql) throws SQLException { if (failedSql.equals(sql)) { throw new SQLException("Invalid SQL:"+sql); } if (slow || superSlowSql.equals(sql)) { try { Thread.sleep(200); }catch (Exception x) { } } return super.executeQuery(sql); } @Override public ResultSet executeQuery() throws SQLException { if (slow) { try { Thread.sleep(200); }catch (Exception x) { } } return super.executeQuery(); } } }