From 19c09db5b3d0116521696e7502b291789de476d8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lievremont Date: Wed, 5 Feb 2014 17:37:06 +0100 Subject: [PATCH] SONAR-4925 Proxify JDBC components to allow profiling of SQL requests --- .../core/persistence/DefaultDatabase.java | 4 +- .../org/sonar/core/persistence/MyBatis.java | 8 +- .../profiling/PersistenceProfiling.java | 43 ++ .../profiling/ProfilingConnectionHandler.java | 54 +++ .../profiling/ProfilingDataSource.java | 402 ++++++++++++++++++ .../ProfilingPreparedStatementHandler.java | 51 +++ .../profiling/ProfilingStatementHandler.java | 48 +++ .../persistence/profiling/SqlProfiling.java | 44 ++ .../core/persistence/DefaultDatabaseTest.java | 1 - .../sonar/core/persistence/MyBatisTest.java | 13 - .../profiling/PersistenceProfilingTest.java | 93 ++++ .../org/sonar/server/platform/logback.xml | 2 +- 12 files changed, 739 insertions(+), 24 deletions(-) create mode 100644 sonar-core/src/main/java/org/sonar/core/persistence/profiling/PersistenceProfiling.java create mode 100644 sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingConnectionHandler.java create mode 100644 sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingDataSource.java create mode 100644 sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingPreparedStatementHandler.java create mode 100644 sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingStatementHandler.java create mode 100644 sonar-core/src/main/java/org/sonar/core/persistence/profiling/SqlProfiling.java create mode 100644 sonar-core/src/test/java/org/sonar/core/persistence/profiling/PersistenceProfilingTest.java diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java b/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java index d90aa399298..bec9bf8aafa 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java @@ -32,8 +32,7 @@ import org.sonar.api.database.DatabaseProperties; import org.sonar.core.persistence.dialect.Dialect; import org.sonar.core.persistence.dialect.DialectUtils; import org.sonar.core.persistence.dialect.H2; -import org.sonar.core.persistence.dialect.Oracle; -import org.sonar.core.persistence.dialect.PostgreSql; +import org.sonar.core.persistence.profiling.PersistenceProfiling; import org.sonar.jpa.session.CustomHibernateConnectionProvider; import javax.sql.DataSource; @@ -125,6 +124,7 @@ public class DefaultDatabase implements Database { datasource = (BasicDataSource) BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties)); datasource.setConnectionInitSqls(dialect.getConnectionInitStatements()); datasource.setValidationQuery(dialect.getValidationQuery()); + datasource = PersistenceProfiling.addProfilingIfNeeded(datasource, settings); } private void checkConnection() { diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java index ab2b3472488..e98bf8ff668 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java @@ -52,7 +52,6 @@ import org.sonar.core.measure.db.MeasureFilterMapper; import org.sonar.core.notification.db.NotificationQueueDto; import org.sonar.core.notification.db.NotificationQueueMapper; import org.sonar.core.permission.*; -import org.sonar.core.profiling.Profiling; import org.sonar.core.properties.PropertiesMapper; import org.sonar.core.properties.PropertyDto; import org.sonar.core.purge.PurgeMapper; @@ -202,13 +201,8 @@ public class MyBatis implements BatchComponent, ServerComponent { * See http://www.mybatis.org/core/logging.html : */ private void configureLogback(Class... mapperClasses) { - Level level = Level.INFO; - Profiling.Level profilingLevel = Profiling.Level.fromConfigString(settings.getString(Profiling.CONFIG_PROFILING_LEVEL)); - if (profilingLevel == Profiling.Level.FULL) { - level = Level.TRACE; - } for (Class mapperClass : mapperClasses) { - logback.setLoggerLevel(mapperClass.getName(), level); + logback.setLoggerLevel(mapperClass.getName(), Level.INFO); } } diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/PersistenceProfiling.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/PersistenceProfiling.java new file mode 100644 index 00000000000..335e7025ef4 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/PersistenceProfiling.java @@ -0,0 +1,43 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.persistence.profiling; + +import org.apache.commons.dbcp.BasicDataSource; +import org.sonar.api.config.Settings; +import org.sonar.core.profiling.Profiling; + +/** + * @since 4.2 + */ +public final class PersistenceProfiling { + + private PersistenceProfiling() { + // Static stuff only + } + + public static BasicDataSource addProfilingIfNeeded(BasicDataSource datasource, Settings settings) { + Profiling.Level profilingLevel = Profiling.Level.fromConfigString(settings.getString(Profiling.CONFIG_PROFILING_LEVEL)); + if (profilingLevel == Profiling.Level.FULL) { + return new ProfilingDataSource(datasource); + } else { + return datasource; + } + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingConnectionHandler.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingConnectionHandler.java new file mode 100644 index 00000000000..1a8c86eed33 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingConnectionHandler.java @@ -0,0 +1,54 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.persistence.profiling; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; + +class ProfilingConnectionHandler implements InvocationHandler { + + private final Connection connection; + + ProfilingConnectionHandler(Connection connection) { + this.connection = connection; + } + + @Override + public Object invoke(Object target, Method method, Object[] args) throws Throwable { + Object result = method.invoke(connection, args); + if ("prepareStatement".equals(method.getName())) { + PreparedStatement statement = (PreparedStatement) result; + String sql = (String) args[0]; + return Proxy.newProxyInstance(ProfilingConnectionHandler.class.getClassLoader(), new Class[] { PreparedStatement.class }, new ProfilingPreparedStatementHandler(statement, sql)); + + } else if ("createStatement".equals(method.getName())) { + Statement statement = (Statement) result; + return Proxy.newProxyInstance(ProfilingConnectionHandler.class.getClassLoader(), new Class[] { Statement.class }, new ProfilingStatementHandler(statement)); + + } else { + return result; + } + } + +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingDataSource.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingDataSource.java new file mode 100644 index 00000000000..90f0dd296b3 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingDataSource.java @@ -0,0 +1,402 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.persistence.profiling; + +import org.apache.commons.dbcp.BasicDataSource; + +import java.io.PrintWriter; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collection; + +class ProfilingDataSource extends BasicDataSource { + + private final BasicDataSource delegate; + + public ProfilingDataSource(BasicDataSource delegate) { + this.delegate = delegate; + } + + @Override + public boolean getDefaultAutoCommit() { + return delegate.getDefaultAutoCommit(); + } + + @Override + public void setDefaultAutoCommit(boolean defaultAutoCommit) { + delegate.setDefaultAutoCommit(defaultAutoCommit); + } + + @Override + public boolean getDefaultReadOnly() { + return delegate.getDefaultReadOnly(); + } + + @Override + public void setDefaultReadOnly(boolean defaultReadOnly) { + delegate.setDefaultReadOnly(defaultReadOnly); + } + + @Override + public int getDefaultTransactionIsolation() { + return delegate.getDefaultTransactionIsolation(); + } + + @Override + public void setDefaultTransactionIsolation(int defaultTransactionIsolation) { + delegate.setDefaultTransactionIsolation(defaultTransactionIsolation); + } + + @Override + public String getDefaultCatalog() { + return delegate.getDefaultCatalog(); + } + + @Override + public void setDefaultCatalog(String defaultCatalog) { + delegate.setDefaultCatalog(defaultCatalog); + } + + @Override + public synchronized String getDriverClassName() { + return delegate.getDriverClassName(); + } + + @Override + public synchronized void setDriverClassName(String driverClassName) { + delegate.setDriverClassName(driverClassName); + } + + @Override + public synchronized ClassLoader getDriverClassLoader() { + return delegate.getDriverClassLoader(); + } + + @Override + public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) { + delegate.setDriverClassLoader(driverClassLoader); + } + + @Override + public synchronized int getMaxActive() { + return delegate.getMaxActive(); + } + + @Override + public synchronized void setMaxActive(int maxActive) { + delegate.setMaxActive(maxActive); + } + + @Override + public synchronized int getMaxIdle() { + return delegate.getMaxIdle(); + } + + @Override + public synchronized void setMaxIdle(int maxIdle) { + delegate.setMaxIdle(maxIdle); + } + + @Override + public synchronized int getMinIdle() { + return delegate.getMinIdle(); + } + + @Override + public synchronized void setMinIdle(int minIdle) { + delegate.setMinIdle(minIdle); + } + + @Override + public synchronized int getInitialSize() { + return delegate.getInitialSize(); + } + + @Override + public synchronized void setInitialSize(int initialSize) { + delegate.setInitialSize(initialSize); + } + + @Override + public synchronized long getMaxWait() { + return delegate.getMaxWait(); + } + + @Override + public synchronized void setMaxWait(long maxWait) { + delegate.setMaxWait(maxWait); + } + + @Override + public synchronized boolean isPoolPreparedStatements() { + return delegate.isPoolPreparedStatements(); + } + + @Override + public synchronized void setPoolPreparedStatements(boolean poolingStatements) { + delegate.setPoolPreparedStatements(poolingStatements); + } + + @Override + public synchronized int getMaxOpenPreparedStatements() { + return delegate.getMaxOpenPreparedStatements(); + } + + @Override + public synchronized void setMaxOpenPreparedStatements(int maxOpenStatements) { + delegate.setMaxOpenPreparedStatements(maxOpenStatements); + } + + @Override + public synchronized boolean getTestOnBorrow() { + return delegate.getTestOnBorrow(); + } + + @Override + public synchronized void setTestOnBorrow(boolean testOnBorrow) { + delegate.setTestOnBorrow(testOnBorrow); + } + + @Override + public synchronized boolean getTestOnReturn() { + return delegate.getTestOnReturn(); + } + + @Override + public synchronized void setTestOnReturn(boolean testOnReturn) { + delegate.setTestOnReturn(testOnReturn); + } + + @Override + public synchronized long getTimeBetweenEvictionRunsMillis() { + return delegate.getTimeBetweenEvictionRunsMillis(); + } + + @Override + public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + delegate.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + } + + @Override + public synchronized int getNumTestsPerEvictionRun() { + return delegate.getNumTestsPerEvictionRun(); + } + + @Override + public synchronized void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { + delegate.setNumTestsPerEvictionRun(numTestsPerEvictionRun); + } + + @Override + public synchronized long getMinEvictableIdleTimeMillis() { + return delegate.getMinEvictableIdleTimeMillis(); + } + + @Override + public synchronized void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + delegate.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + } + + @Override + public synchronized boolean getTestWhileIdle() { + return delegate.getTestWhileIdle(); + } + + @Override + public synchronized void setTestWhileIdle(boolean testWhileIdle) { + delegate.setTestWhileIdle(testWhileIdle); + } + + @Override + public synchronized int getNumActive() { + return delegate.getNumActive(); + } + + @Override + public synchronized int getNumIdle() { + return delegate.getNumIdle(); + } + + @Override + public String getPassword() { + return delegate.getPassword(); + } + + @Override + public void setPassword(String password) { + delegate.setPassword(password); + } + + @Override + public synchronized String getUrl() { + return delegate.getUrl(); + } + + @Override + public synchronized void setUrl(String url) { + delegate.setUrl(url); + } + + @Override + public String getUsername() { + return delegate.getUsername(); + } + + @Override + public void setUsername(String username) { + delegate.setUsername(username); + } + + @Override + public String getValidationQuery() { + return delegate.getValidationQuery(); + } + + @Override + public void setValidationQuery(String validationQuery) { + delegate.setValidationQuery(validationQuery); + } + + @Override + public int getValidationQueryTimeout() { + return delegate.getValidationQueryTimeout(); + } + + @Override + public void setValidationQueryTimeout(int timeout) { + delegate.setValidationQueryTimeout(timeout); + } + + @Override + public Collection getConnectionInitSqls() { + return delegate.getConnectionInitSqls(); + } + + @Override + public void setConnectionInitSqls(Collection connectionInitSqls) { + delegate.setConnectionInitSqls(connectionInitSqls); + } + + @Override + public synchronized boolean isAccessToUnderlyingConnectionAllowed() { + return delegate.isAccessToUnderlyingConnectionAllowed(); + } + + @Override + public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) { + delegate.setAccessToUnderlyingConnectionAllowed(allow); + } + + @Override + public Connection getConnection() throws SQLException { + return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, new ProfilingConnectionHandler(delegate.getConnection())); + } + + @Override + public Connection getConnection(String user, String pass) throws SQLException { + return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, new ProfilingConnectionHandler(delegate.getConnection(user, pass))); + } + + @Override + public int getLoginTimeout() throws SQLException { + return delegate.getLoginTimeout(); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return delegate.getLogWriter(); + } + + @Override + public void setLoginTimeout(int loginTimeout) throws SQLException { + delegate.setLoginTimeout(loginTimeout); + } + + @Override + public void setLogWriter(PrintWriter logWriter) throws SQLException { + delegate.setLogWriter(logWriter); + } + + @Override + public boolean getRemoveAbandoned() { + return delegate.getRemoveAbandoned(); + } + + @Override + public void setRemoveAbandoned(boolean removeAbandoned) { + delegate.setRemoveAbandoned(removeAbandoned); + } + + @Override + public int getRemoveAbandonedTimeout() { + return delegate.getRemoveAbandonedTimeout(); + } + + @Override + public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) { + delegate.setRemoveAbandonedTimeout(removeAbandonedTimeout); + } + + @Override + public boolean getLogAbandoned() { + return delegate.getLogAbandoned(); + } + + @Override + public void setLogAbandoned(boolean logAbandoned) { + delegate.setLogAbandoned(logAbandoned); + } + + @Override + public void addConnectionProperty(String name, String value) { + delegate.addConnectionProperty(name, value); + } + + @Override + public void removeConnectionProperty(String name) { + delegate.removeConnectionProperty(name); + } + + @Override + public void setConnectionProperties(String connectionProperties) { + delegate.setConnectionProperties(connectionProperties); + } + + @Override + public synchronized void close() throws SQLException { + delegate.close(); + } + + @Override + public synchronized boolean isClosed() { + return delegate.isClosed(); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return delegate.isWrapperFor(iface); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return delegate.unwrap(iface); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingPreparedStatementHandler.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingPreparedStatementHandler.java new file mode 100644 index 00000000000..f773d8fb3c6 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingPreparedStatementHandler.java @@ -0,0 +1,51 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.persistence.profiling; + +import org.sonar.core.profiling.StopWatch; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.PreparedStatement; + +class ProfilingPreparedStatementHandler implements InvocationHandler { + + private static final SqlProfiling profiling = new SqlProfiling(); + private final PreparedStatement statement; + private final String sql; + + ProfilingPreparedStatementHandler(PreparedStatement statement, String sql) { + this.statement = statement; + this.sql = sql; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().startsWith("execute")) { + StopWatch watch = profiling.start(); + Object result = method.invoke(statement, args); + profiling.stop(watch, sql); + return result; + } else { + return method.invoke(statement, args); + } + } + +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingStatementHandler.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingStatementHandler.java new file mode 100644 index 00000000000..3180bf0ee1f --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingStatementHandler.java @@ -0,0 +1,48 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.persistence.profiling; + +import org.sonar.core.profiling.StopWatch; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.Statement; + +class ProfilingStatementHandler implements InvocationHandler { + + private static final SqlProfiling profiling = new SqlProfiling(); + private final Statement statement; + + ProfilingStatementHandler(Statement statement) { + this.statement = statement; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().startsWith("execute")) { + StopWatch watch = profiling.start(); + Object result = method.invoke(statement, args); + profiling.stop(watch, (String) args[0]); + return result; + } else { + return method.invoke(statement, args); + } + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/SqlProfiling.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/SqlProfiling.java new file mode 100644 index 00000000000..05bff18041e --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/SqlProfiling.java @@ -0,0 +1,44 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.persistence.profiling; + +import org.sonar.api.config.Settings; +import org.sonar.core.profiling.Profiling; +import org.sonar.core.profiling.Profiling.Level; +import org.sonar.core.profiling.StopWatch; + +class SqlProfiling { + + private final Profiling profiling; + + SqlProfiling() { + Settings settings = new Settings(); + settings.setProperty(Profiling.CONFIG_PROFILING_LEVEL, Profiling.Level.FULL.toString()); + profiling = new Profiling(settings); + } + + StopWatch start() { + return profiling.start("sql", Level.FULL); + } + + void stop(StopWatch watch, String sql) { + watch.stop(String.format("Executed SQL: %s", sql.replaceAll("\\s+", " "))); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/DefaultDatabaseTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/DefaultDatabaseTest.java index 4a3866264f5..827c4dc0b06 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/DefaultDatabaseTest.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/DefaultDatabaseTest.java @@ -22,7 +22,6 @@ package org.sonar.core.persistence; import org.apache.commons.dbcp.BasicDataSource; import org.junit.Test; import org.sonar.api.config.Settings; -import org.sonar.core.persistence.dialect.Oracle; import org.sonar.core.persistence.dialect.PostgreSql; import java.util.Properties; diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java index 5b02464e35c..44be4f26eb7 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java @@ -19,7 +19,6 @@ */ package org.sonar.core.persistence; -import ch.qos.logback.classic.Level; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import org.hamcrest.core.Is; @@ -33,7 +32,6 @@ import org.sonar.core.rule.RuleMapper; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; public class MyBatisTest { private static H2Database database; @@ -74,15 +72,4 @@ public class MyBatisTest { session.close(); } } - - @Test - public void log_sql_requests_and_responses() { - Settings settings = new Settings() - .setProperty("sonar.log.profilingLevel", "FULL"); - - MyBatis myBatis = new MyBatis(database, settings, logback); - myBatis.start(); - - verify(logback).setLoggerLevel("org.sonar.core.resource.ResourceIndexerMapper", Level.TRACE); - } } diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/profiling/PersistenceProfilingTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/profiling/PersistenceProfilingTest.java new file mode 100644 index 00000000000..b21a3a2a41b --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/persistence/profiling/PersistenceProfilingTest.java @@ -0,0 +1,93 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.persistence.profiling; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.ContextBase; +import ch.qos.logback.core.read.ListAppender; +import org.apache.commons.dbcp.BasicDataSource; +import org.junit.Test; +import org.slf4j.LoggerFactory; +import org.sonar.api.config.Settings; +import org.sonar.core.profiling.Profiling; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PersistenceProfilingTest { + + @Test + public void should_be_transparent_when_profiling_less_than_full() { + BasicDataSource datasource = mock(BasicDataSource.class); + assertThat(PersistenceProfiling.addProfilingIfNeeded(datasource , new Settings())).isEqualTo(datasource); + } + + @Test + public void should_enable_profiling_when_profiling_is_full() throws Exception { + final Logger sqlLogger = (Logger) LoggerFactory.getLogger("sql"); + ListAppender appender = new ListAppender(); + appender.setContext(new ContextBase()); + appender.start(); + sqlLogger.addAppender(appender); + + BasicDataSource originDataSource = mock(BasicDataSource.class); + + Connection connection = mock(Connection.class); + when(originDataSource.getConnection()).thenReturn(connection); + + String sql = "select 'polop' from dual;"; + + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(connection.prepareStatement(sql)).thenReturn(preparedStatement); + when(preparedStatement.execute()).thenReturn(true); + + Statement statement = mock(Statement.class); + when(connection.createStatement()).thenReturn(statement); + when(statement.execute(sql)).thenReturn(true); + + Settings settings = new Settings(); + settings.setProperty(Profiling.CONFIG_PROFILING_LEVEL, Profiling.Level.FULL.toString()); + + BasicDataSource resultDataSource = PersistenceProfiling.addProfilingIfNeeded(originDataSource , settings); + + assertThat(resultDataSource).isInstanceOf(ProfilingDataSource.class); + assertThat(resultDataSource.getUrl()).isNull(); + assertThat(resultDataSource.getConnection().getClientInfo()).isNull(); + PreparedStatement preparedStatementProxy = resultDataSource.getConnection().prepareStatement(sql); + assertThat(preparedStatementProxy.getConnection()).isNull(); + assertThat(preparedStatementProxy.execute()).isTrue(); + final Statement statementProxy = resultDataSource.getConnection().createStatement(); + assertThat(statementProxy.getConnection()).isNull(); + assertThat(statementProxy.execute(sql)).isTrue(); + + assertThat(appender.list).hasSize(2); + for (ILoggingEvent event: appender.list) { + assertThat(event.getLevel()).isEqualTo(Level.INFO); + assertThat(event.getFormattedMessage()).contains(sql); + } + } +} diff --git a/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml b/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml index 0fef27fea70..a4a640e59b1 100644 --- a/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml +++ b/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml @@ -42,7 +42,7 @@ - + -- 2.39.5