View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.web.health;
17  
18  import bitronix.tm.resource.jdbc.PoolingDataSource;
19  import com.codahale.metrics.Gauge;
20  import com.codahale.metrics.Metric;
21  import com.codahale.metrics.MetricSet;
22  import com.codahale.metrics.RatioGauge;
23  import org.apache.commons.dbcp.BasicDataSource;
24  import org.apache.log4j.Logger;
25  import org.enhydra.jdbc.pool.StandardXAPoolDataSource;
26  
27  import javax.sql.DataSource;
28  import java.sql.SQLException;
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  /**
33   * A set of metrics that indicate min, max, and active number of connections in the pool as well as the percentage of
34   * connection pool usage.
35   *
36   * Supports XAPool, Bitronix, and DBCP datasources. Will attempt to unwrap the underlying DataSource implementation if
37   * the given DataSource is a wrapper for one of these types. If the given DataSource is of an unknown type, this class
38   * will silently fail and invocations of {@link #getMetrics()} will return an empty map.
39   *
40   * @author Eric Westfall
41   */
42  public class DatabaseConnectionPoolMetricSet implements MetricSet {
43  
44      private static final Logger LOG = Logger.getLogger(DatabaseConnectionPoolMetricSet.class);
45  
46      public static final String ACTIVE = "pool.active";
47      public static final String MIN = "pool.min";
48      public static final String MAX = "pool.max";
49      public static final String USAGE = "pool.usage";
50  
51      private final String namePrefix;
52      private final DataSource dataSource;
53  
54      /**
55       * Construct a new database connection pool metric set.
56       * @param namePrefix the prefix to use for all metric names, will prepend this to all metric names in this set
57       * @param dataSource the DataSource from which to extract the metrics in this metric set
58       */
59      public DatabaseConnectionPoolMetricSet(String namePrefix, DataSource dataSource) {
60          this.namePrefix = namePrefix;
61          this.dataSource = dataSource;
62      }
63  
64      @Override
65      public Map<String, Metric> getMetrics() {
66          Map<String, Metric> metrics = new HashMap<>();
67          boolean success = tryXAPool(metrics) || tryBitronix(metrics) || tryDBCP(metrics);
68          if (!success) {
69              LOG.warn("Failed to identify the type of connection pool with namePrefix: " + namePrefix + " and dataSource class: " + dataSource.getClass());
70          }
71          return metrics;
72      }
73  
74      private boolean tryXAPool(Map<String, Metric> metrics) {
75          StandardXAPoolDataSource xaPoolDataSource = tryUnwrap(dataSource, StandardXAPoolDataSource.class);
76          if (xaPoolDataSource != null) {
77              installXAPoolMetrics(xaPoolDataSource, metrics);
78              return true;
79          }
80          return false;
81      }
82  
83      private void installXAPoolMetrics(final StandardXAPoolDataSource dataSource, Map<String, Metric> metrics) {
84          metrics.put(namePrefix + ACTIVE, new Gauge<Integer>() {
85              @Override
86              public Integer getValue() {
87                  return dataSource.getLockedObjectCount();
88              }
89          });
90          metrics.put(namePrefix + MIN, new Gauge<Integer>() {
91              @Override
92              public Integer getValue() {
93                  return dataSource.getMinSize();
94              }
95          });
96          metrics.put(namePrefix + MAX, new Gauge<Integer>() {
97              @Override
98              public Integer getValue() {
99                  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 }