001package nl.nlighten.prometheus.tomcat;
002
003import io.prometheus.client.Collector;
004import io.prometheus.client.GaugeMetricFamily;
005import org.apache.juli.logging.Log;
006import org.apache.juli.logging.LogFactory;
007
008import javax.management.*;
009import java.lang.management.ManagementFactory;
010import java.util.*;
011
012/**
013 * Exports Tomcat <a href="http://tomcat.apache.org/tomcat-8.5-doc/jdbc-pool.html">jdbc-pool</a> metrics.
014 * <p>
015 * Example usage:
016 * <pre>
017 * {@code
018 *   new TomcatJdbcPoolExports().register();
019 * }
020 * </pre>
021 * Example metrics being exported:
022 * <pre>
023 *    tomcat_jdbc_connections_max{context="/foo",pool="jdbc/mypool"} 20.0
024 *    tomcat_jdbc_connections_active_total{context="/foo",pool="jdbc/mypool"} 2.0
025 *    tomcat_jdbc_connections_idle_total{context="/foo",pool="jdbc/mypool"} 6.0
026 *    tomcat_jdbc_connections_total{context="/foo",pool="jdbc/mypool"} 8.0
027 *    tomcat_jdbc_connections_threadswaiting_total{context="/foo",pool="jdbc/mypool"} 0.0
028 * </pre> * </pre>
029 */
030
031public class TomcatJdbcPoolExports extends Collector {
032
033    private static final Log log = LogFactory.getLog(TomcatJdbcPoolExports.class);
034
035    public List<MetricFamilySamples> collect() {
036        List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
037        try {
038            final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
039            ObjectName filterName = new ObjectName("tomcat.jdbc:class=org.apache.tomcat.jdbc.pool.DataSource,type=ConnectionPool,*");
040            Set<ObjectInstance> mBeans = server.queryMBeans(filterName, null);
041
042            if (mBeans.size() > 0) {
043                List<String> labelList = Arrays.asList("pool", "context");
044
045                GaugeMetricFamily maxActiveConnectionsGauge = new GaugeMetricFamily(
046                        "tomcat_jdbc_connections_max",
047                        "Maximum number of active connections that can be allocated from this pool at the same time",
048                        labelList);
049
050                GaugeMetricFamily activeConnectionsGauge = new GaugeMetricFamily(
051                        "tomcat_jdbc_connections_active_total",
052                        "Number of active connections allocated from this pool",
053                        labelList);
054
055                GaugeMetricFamily idleConnectionsGauge = new GaugeMetricFamily(
056                        "tomcat_jdbc_connections_idle_total",
057                        "Number of idle connections in this pool",
058                        labelList);
059
060                GaugeMetricFamily totalConnectionsGauge = new GaugeMetricFamily(
061                        "tomcat_jdbc_connections_total",
062                        "Total number of connections in this pool",
063                        labelList);
064
065                GaugeMetricFamily waitingThreadsCountGauge = new GaugeMetricFamily(
066                        "tomcat_jdbc_waitingthreads_total",
067                        "Number of threads waiting for connections from this pool",
068                        labelList);
069
070                GaugeMetricFamily borrowedConnectionsGauge = new GaugeMetricFamily(
071                        "tomcat_jdbc_connections_borrowed_total",
072                        "Number of connections borrowed from this pool",
073                        labelList);
074
075                GaugeMetricFamily returnedConnectionsGauge = new GaugeMetricFamily(
076                        "tomcat_jdbc_connections_returned_total",
077                        "Number of connections returned to this pool",
078                        labelList);
079
080                GaugeMetricFamily createdConnectionsGauge = new GaugeMetricFamily(
081                        "tomcat_jdbc_connections_created_total",
082                        "Number of connections created by this pool",
083                        labelList);
084                GaugeMetricFamily releasedConnectionsGauge = new GaugeMetricFamily(
085                        "tomcat_jdbc_connections_released_total",
086                        "Number of connections released by this pool",
087                        labelList);
088
089                GaugeMetricFamily reconnectedConnectionsGauge = new GaugeMetricFamily(
090                        "tomcat_jdbc_connections_reconnected_total",
091                        "Number of reconnected connections by this pool",
092                        labelList);
093
094                GaugeMetricFamily removeAbandonedConnectionsGauge = new GaugeMetricFamily(
095                        "tomcat_jdbc_connections_removeabandoned_total",
096                        "Number of abandoned connections that have been removed",
097                        labelList);
098
099                GaugeMetricFamily releasedIdleConnectionsGauge = new GaugeMetricFamily(
100                        "tomcat_jdbc_connections_releasedidle_total",
101                        "Number of idle connections that have been released",
102                        labelList);
103
104                String[] poolAttributes = new String[]{"MaxActive", "Active", "Idle", "Size", "WaitCount", "BorrowedCount", "ReturnedCount", "CreatedCount", "ReleasedCount", "ReconnectedCount", "RemoveAbandonedCount", "ReleasedIdleCount"};
105
106                for (final ObjectInstance mBean : mBeans) {
107                    List<String> labelValueList = Arrays.asList(mBean.getObjectName().getKeyProperty("name").replaceAll("[\"\\\\]", ""), Optional.ofNullable(mBean.getObjectName().getKeyProperty("context")).orElse("global"));
108                    if (mBean.getObjectName().getKeyProperty("connections") == null) {  // Tomcat 8.5.33 ignore PooledConnections
109                        AttributeList attributeList = server.getAttributes(mBean.getObjectName(), poolAttributes);
110
111                        for (Attribute attribute : attributeList.asList()) {
112                            switch (attribute.getName()) {
113                                case "MaxActive":
114                                    maxActiveConnectionsGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
115                                    break;
116                                case "Active":
117                                    activeConnectionsGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
118                                    break;
119                                case "Idle":
120                                    idleConnectionsGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
121                                    break;
122                                case "Size":
123                                    totalConnectionsGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
124                                    break;
125                                case "WaitCount":
126                                    waitingThreadsCountGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
127                                    break;
128                                case "BorrowedCount":
129                                    borrowedConnectionsGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
130                                    break;
131                                case "ReturnedCount":
132                                    returnedConnectionsGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
133                                    break;
134                                case "CreatedCount":
135                                    createdConnectionsGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
136                                    break;
137                                case "ReleasedCount":
138                                    releasedConnectionsGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
139                                    break;
140                                case "ReconnectedCount":
141                                    reconnectedConnectionsGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
142                                    break;
143                                case "RemoveAbandonedCount":
144                                    removeAbandonedConnectionsGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
145                                    break;
146                                case "ReleasedIdleCount":
147                                    releasedIdleConnectionsGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
148                            }
149                        }
150                    }
151                }
152                mfs.add(maxActiveConnectionsGauge);
153                mfs.add(activeConnectionsGauge);
154                mfs.add(idleConnectionsGauge);
155                mfs.add(totalConnectionsGauge);
156                mfs.add(waitingThreadsCountGauge);
157                mfs.add(borrowedConnectionsGauge);
158                mfs.add(returnedConnectionsGauge);
159                mfs.add(createdConnectionsGauge);
160                mfs.add(releasedConnectionsGauge);
161                mfs.add(reconnectedConnectionsGauge);
162                mfs.add(removeAbandonedConnectionsGauge);
163                mfs.add(releasedIdleConnectionsGauge);
164            }
165        } catch (Exception e) {
166            log.error("Error retrieving metric:" + e.getMessage());
167        }
168        return mfs;
169    }
170
171    public static boolean isTomcatJdbcUsed() {
172        try {
173            final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
174            ObjectName filterName = new ObjectName("tomcat.jdbc:class=org.apache.tomcat.jdbc.pool.DataSource,type=ConnectionPool,*");
175            Set<ObjectInstance> mBeans = server.queryMBeans(filterName, null);
176            return !mBeans.isEmpty();
177        } catch (Exception e) {
178            e.printStackTrace();
179        }
180        return false;
181    }
182
183}