001package nl.nlighten.prometheus.tomcat;
002
003import io.prometheus.client.Collector;
004import io.prometheus.client.CounterMetricFamily;
005import io.prometheus.client.GaugeMetricFamily;
006import org.apache.catalina.util.ServerInfo;
007import org.apache.juli.logging.Log;
008import org.apache.juli.logging.LogFactory;
009
010import javax.management.*;
011import java.lang.management.ManagementFactory;
012import java.util.*;
013
014/**
015 * Exports Tomcat metrics applicable to most most applications:
016 *
017 * - http session metrics
018 * - request processor metrics
019 * - thread pool metrics
020 *
021 * <p>
022 * Example usage:
023 * <pre>
024 * {@code
025 *   new TomcatGenericExports(false).register();
026 * }
027 * </pre>
028 * Example metrics being exported:
029 * <pre>
030 *     tomcat_info{version="7.0.61.0",build="Apr 29 2015 14:58:03 UTC",} 1.0
031 *     tomcat_session_active_total{context="/foo",host="default",} 877.0
032 *     tomcat_session_rejected_total{context="/foo",host="default",} 0.0
033 *     tomcat_session_created_total{context="/foo",host="default",} 24428.0
034 *     tomcat_session_expired_total{context="/foo",host="default",} 23832.0
035 *     tomcat_session_alivetime_seconds_avg{context="/foo",host="default",} 633.0
036 *     tomcat_session_alivetime_seconds_max{context="/foo",host="default",} 9883.0
037 *     tomcat_requestprocessor_received_bytes{name="http-bio-0.0.0.0-8080",} 0.0
038 *     tomcat_requestprocessor_sent_bytes{name="http-bio-0.0.0.0-8080",} 5056098.0
039 *     tomcat_requestprocessor_time_seconds{name="http-bio-0.0.0.0-8080",} 127386.0
040 *     tomcat_requestprocessor_error_count{name="http-bio-0.0.0.0-8080",} 0.0
041 *     tomcat_requestprocessor_request_count{name="http-bio-0.0.0.0-8080",} 33709.0
042 *     tomcat_threads_total{pool="http-bio-0.0.0.0-8080",} 10.0
043 *     tomcat_threads_active_total{pool="http-bio-0.0.0.0-8080",} 2.0
044 *     tomcat_threads_active_max{pool="http-bio-0.0.0.0-8080",} 200.0
045 *  </pre>
046 */
047
048public class TomcatGenericExports extends Collector {
049
050    private static final Log log = LogFactory.getLog(TomcatGenericExports.class);
051    private String jmxDomain = "Catalina";
052
053    public TomcatGenericExports(boolean embedded) {
054        if (embedded) {
055            jmxDomain = "Tomcat";
056        }
057    }
058    private void addRequestProcessorMetrics(List<MetricFamilySamples> mfs) {
059        try {
060            final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
061            ObjectName filterName = new ObjectName(jmxDomain + ":type=GlobalRequestProcessor,name=*");
062            Set<ObjectInstance> mBeans = server.queryMBeans(filterName, null);
063
064            if (mBeans.size() > 0) {
065                List<String> labelNameList = Collections.singletonList("name");
066
067                GaugeMetricFamily requestProcessorBytesReceivedGauge = new GaugeMetricFamily(
068                        "tomcat_requestprocessor_received_bytes",
069                        "Number of bytes received by this request processor",
070                        labelNameList);
071
072                GaugeMetricFamily requestProcessorBytesSentGauge = new GaugeMetricFamily(
073                        "tomcat_requestprocessor_sent_bytes",
074                        "Number of bytes sent by this request processor",
075                        labelNameList);
076
077                GaugeMetricFamily requestProcessorProcessingTimeGauge = new GaugeMetricFamily(
078                        "tomcat_requestprocessor_time_seconds",
079                        "The total time spend by this request processor",
080                        labelNameList);
081
082                CounterMetricFamily requestProcessorErrorCounter = new CounterMetricFamily(
083                        "tomcat_requestprocessor_error_count",
084                        "The number of error request served by this request processor",
085                        labelNameList);
086
087                CounterMetricFamily requestProcessorRequestCounter = new CounterMetricFamily(
088                        "tomcat_requestprocessor_request_count",
089                        "The number of request served by this request processor",
090                        labelNameList);
091
092                for (final ObjectInstance mBean : mBeans) {
093                    List<String> labelValueList = Collections.singletonList(mBean.getObjectName().getKeyProperty("name").replaceAll("[\"\\\\]", ""));
094
095                    requestProcessorBytesReceivedGauge.addMetric(
096                            labelValueList,
097                            ((Long) server.getAttribute(mBean.getObjectName(), "bytesReceived")).doubleValue());
098
099                    requestProcessorBytesSentGauge.addMetric(
100                            labelValueList,
101                            ((Long) server.getAttribute(mBean.getObjectName(), "bytesSent")).doubleValue());
102
103                    requestProcessorProcessingTimeGauge.addMetric(
104                            labelValueList,
105                            ((Long) server.getAttribute(mBean.getObjectName(), "processingTime")).doubleValue() / 1000.0);
106
107                    requestProcessorErrorCounter.addMetric(
108                            labelValueList,
109                            ((Integer) server.getAttribute(mBean.getObjectName(), "errorCount")).doubleValue());
110
111                    requestProcessorRequestCounter.addMetric(
112                            labelValueList,
113                            ((Integer) server.getAttribute(mBean.getObjectName(), "requestCount")).doubleValue());
114                }
115
116                mfs.add(requestProcessorBytesReceivedGauge);
117                mfs.add(requestProcessorBytesSentGauge);
118                mfs.add(requestProcessorProcessingTimeGauge);
119                mfs.add(requestProcessorRequestCounter);
120                mfs.add(requestProcessorErrorCounter);
121            }
122        } catch (Exception e) {
123            log.error("Error retrieving metric.", e);
124        }
125    }
126
127
128    private void addSessionMetrics(List<MetricFamilySamples> mfs) {
129        try {
130            final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
131            ObjectName filterName = new ObjectName(jmxDomain + ":type=Manager,context=*,host=*");
132            Set<ObjectInstance> mBeans = server.queryMBeans(filterName, null);
133
134            if (mBeans.size() > 0) {
135                List<String> labelNameList = Arrays.asList("host", "context");
136
137                GaugeMetricFamily activeSessionCountGauge = new GaugeMetricFamily(
138                        "tomcat_session_active_total",
139                        "Number of active sessions",
140                        labelNameList);
141
142                GaugeMetricFamily rejectedSessionCountGauge = new GaugeMetricFamily(
143                        "tomcat_session_rejected_total",
144                        "Number of sessions rejected due to maxActive being reached",
145                        labelNameList);
146
147                GaugeMetricFamily createdSessionCountGauge = new GaugeMetricFamily(
148                        "tomcat_session_created_total",
149                        "Number of sessions created",
150                        labelNameList);
151
152                GaugeMetricFamily expiredSessionCountGauge = new GaugeMetricFamily(
153                        "tomcat_session_expired_total",
154                        "Number of sessions that expired",
155                        labelNameList);
156
157                GaugeMetricFamily sessionAvgAliveTimeGauge = new GaugeMetricFamily(
158                        "tomcat_session_alivetime_seconds_avg",
159                        "Average time an expired session had been alive",
160                        labelNameList);
161
162                GaugeMetricFamily sessionMaxAliveTimeGauge = new GaugeMetricFamily(
163                        "tomcat_session_alivetime_seconds_max",
164                        "Maximum time an expired session had been alive",
165                        labelNameList);
166
167                GaugeMetricFamily contextStateGauge = new GaugeMetricFamily(
168                        "tomcat_context_state_started",
169                        "Indication if the lifecycle state of this context is STARTED",
170                        labelNameList);
171
172                for (final ObjectInstance mBean : mBeans) {
173                    List<String> labelValueList = Arrays.asList(mBean.getObjectName().getKeyProperty("host"), mBean.getObjectName().getKeyProperty("context"));
174
175                    activeSessionCountGauge.addMetric(
176                            labelValueList,
177                            ((Integer) server.getAttribute(mBean.getObjectName(), "activeSessions")).doubleValue());
178
179                    rejectedSessionCountGauge.addMetric(
180                            labelValueList,
181                            ((Integer) server.getAttribute(mBean.getObjectName(), "rejectedSessions")).doubleValue());
182
183                    createdSessionCountGauge.addMetric(
184                            labelValueList,
185                            ((Long) server.getAttribute(mBean.getObjectName(), "sessionCounter")).doubleValue());
186
187                    expiredSessionCountGauge.addMetric(
188                            labelValueList,
189                            ((Long) server.getAttribute(mBean.getObjectName(), "expiredSessions")).doubleValue());
190
191                    sessionAvgAliveTimeGauge.addMetric(
192                            labelValueList,
193                            ((Integer) server.getAttribute(mBean.getObjectName(), "sessionAverageAliveTime")).doubleValue());
194
195                    sessionMaxAliveTimeGauge.addMetric(
196                            labelValueList,
197                            ((Integer) server.getAttribute(mBean.getObjectName(), "sessionMaxAliveTime")).doubleValue());
198
199                    if (server.getAttribute(mBean.getObjectName(), "stateName").equals("STARTED")) {
200                        contextStateGauge.addMetric(labelValueList, 1.0);
201                    } else {
202                        contextStateGauge.addMetric(labelValueList, 0.0);
203                    }
204                }
205
206                mfs.add(activeSessionCountGauge);
207                mfs.add(rejectedSessionCountGauge);
208                mfs.add(createdSessionCountGauge);
209                mfs.add(expiredSessionCountGauge);
210                mfs.add(sessionAvgAliveTimeGauge);
211                mfs.add(sessionMaxAliveTimeGauge);
212                mfs.add(contextStateGauge);
213            }
214        } catch (Exception e) {
215            log.error("Error retrieving metric.", e);
216        }
217    }
218
219
220    private void addThreadPoolMetrics(List<MetricFamilySamples> mfs) {
221        try {
222            final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
223            ObjectName filterName = new ObjectName(jmxDomain + ":type=ThreadPool,name=*");
224            Set<ObjectInstance> mBeans = server.queryMBeans(filterName, null);
225
226            if (mBeans.size() > 0) {
227                List<String> labelList = Collections.singletonList("name");
228
229                GaugeMetricFamily threadPoolCurrentCountGauge = new GaugeMetricFamily(
230                        "tomcat_threads_total",
231                        "Number threads in this pool.",
232                        labelList);
233
234                GaugeMetricFamily threadPoolActiveCountGauge = new GaugeMetricFamily(
235                        "tomcat_threads_active_total",
236                        "Number of active threads in this pool.",
237                        labelList);
238
239                GaugeMetricFamily threadPoolMaxThreadsGauge = new GaugeMetricFamily(
240                        "tomcat_threads_max",
241                        "Maximum number of threads allowed in this pool.",
242                        labelList);
243
244                GaugeMetricFamily threadPoolConnectionCountGauge = new GaugeMetricFamily(
245                        "tomcat_connections_active_total",
246                        "Number of connections served by this pool.",
247                        labelList);
248
249                GaugeMetricFamily threadPoolMaxConnectionGauge = new GaugeMetricFamily(
250                        "tomcat_connections_active_max",
251                        "Maximum number of concurrent connections served by this pool.",
252                        labelList);
253
254                String[] genericAttributes = new String[]{"currentThreadCount", "currentThreadsBusy", "maxThreads", "connectionCount", "maxConnections"};
255
256                for (final ObjectInstance mBean : mBeans) {
257                    List<String> labelValueList = Collections.singletonList(mBean.getObjectName().getKeyProperty("name").replaceAll("[\"\\\\]", ""));
258                    AttributeList attributeList = server.getAttributes(mBean.getObjectName(), genericAttributes);
259                    for (Attribute attribute : attributeList.asList()) {
260                        switch (attribute.getName()) {
261                            case "currentThreadCount":
262                                threadPoolCurrentCountGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
263                                break;
264                            case "currentThreadsBusy":
265                                threadPoolActiveCountGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
266                                break;
267                            case "maxThreads":
268                                threadPoolMaxThreadsGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
269                                break;
270                            case "connectionCount":
271                                threadPoolConnectionCountGauge.addMetric(labelValueList, ((Long) attribute.getValue()).doubleValue());
272                                break;
273                            case "maxConnections":
274                                threadPoolMaxConnectionGauge.addMetric(labelValueList, ((Integer) attribute.getValue()).doubleValue());
275
276                        }
277                    }
278                }
279
280                addNonEmptyMetricFamily(mfs, threadPoolCurrentCountGauge);
281                addNonEmptyMetricFamily(mfs, threadPoolActiveCountGauge);
282                addNonEmptyMetricFamily(mfs, threadPoolMaxThreadsGauge);
283                addNonEmptyMetricFamily(mfs, threadPoolConnectionCountGauge);
284                addNonEmptyMetricFamily(mfs, threadPoolMaxConnectionGauge);
285            }
286        } catch (Exception e) {
287            log.error("Error retrieving metric:" + e.getMessage());
288        }
289    }
290
291
292    private void addVersionInfo(List<MetricFamilySamples> mfs) {
293        GaugeMetricFamily tomcatInfo = new GaugeMetricFamily(
294                "tomcat_info",
295                "tomcat version info",
296                Arrays.asList("version", "build"));
297        tomcatInfo.addMetric(Arrays.asList(ServerInfo.getServerNumber(), ServerInfo.getServerBuilt()), 1);
298        mfs.add(tomcatInfo);
299    }
300
301
302    private void addNonEmptyMetricFamily(List<MetricFamilySamples> mfs, GaugeMetricFamily metricFamily) {
303        if (metricFamily.samples.size() > 0) {
304            mfs.add(metricFamily);
305        }
306    }
307
308
309    public List<MetricFamilySamples> collect() {
310        List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
311        addSessionMetrics(mfs);
312        addThreadPoolMetrics(mfs);
313        addRequestProcessorMetrics(mfs);
314        addVersionInfo(mfs);
315        return mfs;
316
317    }
318}