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