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}