001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.web.health; 017 018import bitronix.tm.resource.jdbc.PoolingDataSource; 019import com.codahale.metrics.Gauge; 020import com.codahale.metrics.Metric; 021import com.codahale.metrics.MetricSet; 022import com.codahale.metrics.RatioGauge; 023import org.apache.commons.dbcp.BasicDataSource; 024import org.apache.log4j.Logger; 025import org.enhydra.jdbc.pool.StandardXAPoolDataSource; 026 027import javax.sql.DataSource; 028import java.sql.SQLException; 029import java.util.HashMap; 030import java.util.Map; 031 032/** 033 * A set of metrics that indicate min, max, and active number of connections in the pool as well as the percentage of 034 * connection pool usage. 035 * 036 * Supports XAPool, Bitronix, and DBCP datasources. Will attempt to unwrap the underlying DataSource implementation if 037 * the given DataSource is a wrapper for one of these types. If the given DataSource is of an unknown type, this class 038 * will silently fail and invocations of {@link #getMetrics()} will return an empty map. 039 * 040 * @author Eric Westfall 041 */ 042public class DatabaseConnectionPoolMetricSet implements MetricSet { 043 044 private static final Logger LOG = Logger.getLogger(DatabaseConnectionPoolMetricSet.class); 045 046 public static final String ACTIVE = "pool.active"; 047 public static final String MIN = "pool.min"; 048 public static final String MAX = "pool.max"; 049 public static final String USAGE = "pool.usage"; 050 051 private final String namePrefix; 052 private final DataSource dataSource; 053 054 /** 055 * Construct a new database connection pool metric set. 056 * @param namePrefix the prefix to use for all metric names, will prepend this to all metric names in this set 057 * @param dataSource the DataSource from which to extract the metrics in this metric set 058 */ 059 public DatabaseConnectionPoolMetricSet(String namePrefix, DataSource dataSource) { 060 this.namePrefix = namePrefix; 061 this.dataSource = dataSource; 062 } 063 064 @Override 065 public Map<String, Metric> getMetrics() { 066 Map<String, Metric> metrics = new HashMap<>(); 067 boolean success = tryXAPool(metrics) || tryBitronix(metrics) || tryDBCP(metrics); 068 if (!success) { 069 LOG.warn("Failed to identify the type of connection pool with namePrefix: " + namePrefix + " and dataSource class: " + dataSource.getClass()); 070 } 071 return metrics; 072 } 073 074 private boolean tryXAPool(Map<String, Metric> metrics) { 075 StandardXAPoolDataSource xaPoolDataSource = tryUnwrap(dataSource, StandardXAPoolDataSource.class); 076 if (xaPoolDataSource != null) { 077 installXAPoolMetrics(xaPoolDataSource, metrics); 078 return true; 079 } 080 return false; 081 } 082 083 private void installXAPoolMetrics(final StandardXAPoolDataSource dataSource, Map<String, Metric> metrics) { 084 metrics.put(namePrefix + ACTIVE, new Gauge<Integer>() { 085 @Override 086 public Integer getValue() { 087 return dataSource.getLockedObjectCount(); 088 } 089 }); 090 metrics.put(namePrefix + MIN, new Gauge<Integer>() { 091 @Override 092 public Integer getValue() { 093 return dataSource.getMinSize(); 094 } 095 }); 096 metrics.put(namePrefix + MAX, new Gauge<Integer>() { 097 @Override 098 public Integer getValue() { 099 return dataSource.getMaxSize(); 100 } 101 }); 102 metrics.put(namePrefix + USAGE, new RatioGauge() { 103 @Override 104 protected Ratio getRatio() { 105 return Ratio.of(dataSource.getLockedObjectCount(), dataSource.getMaxSize()); 106 } 107 }); 108 } 109 110 private boolean tryBitronix(Map<String, Metric> metrics) { 111 PoolingDataSource poolingDataSource = tryUnwrap(dataSource, PoolingDataSource.class); 112 if (poolingDataSource != null) { 113 installBitronixMetrics(poolingDataSource, metrics); 114 return true; 115 } 116 return false; 117 } 118 119 private void installBitronixMetrics(final PoolingDataSource dataSource, Map<String, Metric> metrics) { 120 metrics.put(namePrefix + ACTIVE, new Gauge<Integer>() { 121 @Override 122 public Integer getValue() { 123 return (int)dataSource.getTotalPoolSize() - (int)dataSource.getInPoolSize(); 124 } 125 }); 126 metrics.put(namePrefix + MIN, new Gauge<Integer>() { 127 @Override 128 public Integer getValue() { 129 return dataSource.getMinPoolSize(); 130 } 131 }); 132 metrics.put(namePrefix + MAX, new Gauge<Integer>() { 133 @Override 134 public Integer getValue() { 135 return dataSource.getMaxPoolSize(); 136 } 137 }); 138 metrics.put(namePrefix + USAGE, new RatioGauge() { 139 @Override 140 protected Ratio getRatio() { 141 return Ratio.of(dataSource.getTotalPoolSize() - dataSource.getInPoolSize(), dataSource.getMaxPoolSize()); 142 } 143 }); 144 } 145 146 private boolean tryDBCP(Map<String, Metric> metrics) { 147 BasicDataSource basicDataSource = tryUnwrap(dataSource, BasicDataSource.class); 148 if (basicDataSource != null) { 149 installDBCPMetrics(basicDataSource, metrics); 150 return true; 151 } 152 return false; 153 } 154 155 private void installDBCPMetrics(final BasicDataSource dataSource, Map<String, Metric> metrics) { 156 metrics.put(namePrefix + ACTIVE, new Gauge<Integer>() { 157 @Override 158 public Integer getValue() { 159 return dataSource.getNumActive(); 160 } 161 }); 162 metrics.put(namePrefix + MIN, new Gauge<Integer>() { 163 @Override 164 public Integer getValue() { 165 return dataSource.getMinIdle(); 166 } 167 }); 168 metrics.put(namePrefix + MAX, new Gauge<Integer>() { 169 @Override 170 public Integer getValue() { 171 return dataSource.getMaxActive(); 172 } 173 }); 174 metrics.put(namePrefix + USAGE, new RatioGauge() { 175 @Override 176 protected Ratio getRatio() { 177 return Ratio.of(dataSource.getNumActive(), dataSource.getMaxActive()); 178 } 179 }); 180 } 181 182 private <T> T tryUnwrap(DataSource dataSource, Class<T> targetType) { 183 if (targetType.isInstance(dataSource)) { 184 return targetType.cast(dataSource); 185 } 186 try { 187 if (dataSource.isWrapperFor(targetType)) { 188 return dataSource.unwrap(targetType); 189 } 190 } catch (SQLException e) { 191 LOG.warn("Exception when trying to unwrap datasource as " + targetType, e); 192 } 193 return null; 194 } 195 196}