001/* 002 * (C) Copyright 2013-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Delbosc Benoit 018 */ 019package org.nuxeo.runtime.metrics; 020 021import static org.apache.logging.log4j.Level.INFO; 022import static org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME; 023import static org.nuxeo.runtime.model.Descriptor.UNIQUE_DESCRIPTOR_ID; 024 025import java.lang.management.ManagementFactory; 026import java.util.List; 027import java.util.stream.Collectors; 028 029import javax.management.MalformedObjectNameException; 030import javax.management.ObjectName; 031 032import org.apache.logging.log4j.LogManager; 033import org.apache.logging.log4j.Logger; 034import org.apache.logging.log4j.core.LoggerContext; 035import org.apache.logging.log4j.core.config.Configuration; 036import org.nuxeo.runtime.api.Framework; 037import org.nuxeo.runtime.model.ComponentContext; 038import org.nuxeo.runtime.model.DefaultComponent; 039 040import io.dropwizard.metrics5.Counter; 041import io.dropwizard.metrics5.MetricRegistry; 042import io.dropwizard.metrics5.SharedMetricRegistries; 043import io.dropwizard.metrics5.jvm.BufferPoolMetricSet; 044import io.dropwizard.metrics5.jvm.FileDescriptorRatioGauge; 045import io.dropwizard.metrics5.jvm.GarbageCollectorMetricSet; 046import io.dropwizard.metrics5.jvm.JmxAttributeGauge; 047import io.dropwizard.metrics5.jvm.MemoryUsageGaugeSet; 048import io.dropwizard.metrics5.jvm.ThreadStatesGaugeSet; 049import io.dropwizard.metrics5.log4j2.InstrumentedAppender; 050 051public class MetricsServiceImpl extends DefaultComponent implements MetricsService { 052 053 private static final Logger log = LogManager.getLogger(MetricsServiceImpl.class); 054 055 protected static final String CONFIGURATION_EP = "configuration"; 056 057 protected static final String REPORTER_EP = "reporter"; 058 059 protected MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName()); 060 061 protected final Counter instanceUp = registry.counter(MetricRegistry.name("nuxeo", "instance-up")); 062 063 protected MetricsConfigurationDescriptor config; 064 065 protected List<MetricsReporterDescriptor> reporterConfigs; 066 067 protected List<MetricsReporter> reporters; 068 069 protected InstrumentedAppender appender; 070 071 @Override 072 public void activate(ComponentContext context) { 073 super.activate(context); 074 log.debug("Activating component"); 075 SharedMetricRegistries.getOrCreate(MetricsService.class.getName()); 076 } 077 078 @Override 079 public void deactivate(ComponentContext context) { 080 log.debug("Deactivating component"); 081 SharedMetricRegistries.remove(MetricsService.class.getName()); 082 super.deactivate(context); 083 } 084 085 @Override 086 public void start(ComponentContext context) { 087 super.start(context); 088 log.debug("Starting component"); 089 instanceUp.inc(); 090 config = getDescriptor(CONFIGURATION_EP, UNIQUE_DESCRIPTOR_ID); 091 startReporters(); 092 } 093 094 @Override 095 public void stop(ComponentContext context) { 096 log.debug("Stopping component"); 097 stopReporters(); 098 instanceUp.dec(); 099 } 100 101 protected boolean metricEnabled() { 102 return config != null && config.isEnabled(); 103 } 104 105 @Override 106 public void startReporters() { 107 if (!metricEnabled() || reporters != null) { 108 log.debug("Metrics disabled or already started."); 109 return; 110 } 111 log.info("Starting reporters"); 112 reporterConfigs = getDescriptors(REPORTER_EP); 113 updateInstrumentation(config.getInstruments(), true); 114 reporters = reporterConfigs.stream() 115 .filter(MetricsReporterDescriptor::isEnabled) 116 .map(MetricsReporterDescriptor::newInstance) 117 .collect(Collectors.toList()); 118 reporters.forEach(reporter -> reporter.start(registry, config, config.getDeniedExpansions())); 119 } 120 121 @Override 122 public void stopReporters() { 123 if (!metricEnabled() || reporters == null) { 124 log.debug("Metrics disabled or already stopped."); 125 return; 126 } 127 log.warn("Stopping reporters"); 128 reporters.forEach(MetricsReporter::stop); 129 updateInstrumentation(config.getInstruments(), false); 130 reporters.clear(); 131 reporters = null; 132 reporterConfigs = null; 133 } 134 135 protected void updateInstrumentation(List<MetricsConfigurationDescriptor.InstrumentDescriptor> instruments, boolean activate) { 136 for (String instrument : instruments.stream() 137 .filter(MetricsConfigurationDescriptor.InstrumentDescriptor::isEnabled) 138 .map(MetricsConfigurationDescriptor.InstrumentDescriptor::getId) 139 .collect(Collectors.toList())) { 140 switch (instrument) { 141 case "log4j": 142 instrumentLog4j(activate); 143 break; 144 case "tomcat": 145 instrumentTomcat(activate); 146 break; 147 case "jvm": 148 instrumentJvm(activate); 149 break; 150 default: 151 log.warn("Ignoring unknown instrumentation: " + instrument); 152 } 153 } 154 } 155 156 protected void instrumentLog4j(boolean activate) { 157 if (activate) { 158 appender = new InstrumentedAppender(registry, null, null, false); 159 appender.start(); 160 @SuppressWarnings("resource") // not ours to close 161 LoggerContext context = (LoggerContext) LogManager.getContext(false); 162 Configuration config = context.getConfiguration(); 163 config.getLoggerConfig(ROOT_LOGGER_NAME).addAppender(appender, INFO, null); 164 context.updateLoggers(config); 165 } else if (appender != null) { 166 try { 167 @SuppressWarnings("resource") // not ours to close 168 LoggerContext context = (LoggerContext) LogManager.getContext(false); 169 Configuration config = context.getConfiguration(); 170 config.getLoggerConfig(ROOT_LOGGER_NAME).removeAppender(appender.getName()); 171 context.updateLoggers(config); 172 } finally { 173 appender = null; 174 } 175 } 176 } 177 178 protected void registerTomcatGauge(String mbean, String attribute, MetricRegistry registry, String name) { 179 try { 180 registry.register(MetricRegistry.name("tomcat", name), 181 new JmxAttributeGauge(new ObjectName(mbean), attribute)); 182 } catch (MalformedObjectNameException | IllegalArgumentException e) { 183 throw new UnsupportedOperationException("Cannot compute object name of " + mbean, e); 184 } 185 } 186 187 protected void instrumentTomcat(boolean activate) { 188 if (activate) { 189 // TODO: do not hard code the common datasource 190 String pool = "org.nuxeo.ecm.core.management.jtajca:type=ConnectionPoolMonitor,name=jdbc/nuxeo"; 191 String connector = String.format("Catalina:type=ThreadPool,name=\"http-nio-%s-%s\"", 192 Framework.getProperty("nuxeo.bind.address", "0.0.0.0"), 193 Framework.getProperty("nuxeo.bind.port", "8080")); 194 String requestProcessor = String.format("Catalina:type=GlobalRequestProcessor,name=\"http-nio-%s-%s\"", 195 Framework.getProperty("nuxeo.bind.address", "0.0.0.0"), 196 Framework.getProperty("nuxeo.bind.port", "8080")); 197 String manager = "Catalina:type=Manager,host=localhost,context=/nuxeo"; 198 registerTomcatGauge(pool, "ConnectionCount", registry, "jdbc-numActive"); 199 registerTomcatGauge(pool, "IdleConnectionCount", registry, "jdbc-numIdle"); 200 registerTomcatGauge(connector, "currentThreadCount", registry, "currentThreadCount"); 201 registerTomcatGauge(connector, "currentThreadsBusy", registry, "currentThreadBusy"); 202 registerTomcatGauge(requestProcessor, "errorCount", registry, "errorCount"); 203 registerTomcatGauge(requestProcessor, "requestCount", registry, "requestCount"); 204 registerTomcatGauge(requestProcessor, "processingTime", registry, "processingTime"); 205 registerTomcatGauge(requestProcessor, "bytesReceived", registry, "bytesReceived"); 206 registerTomcatGauge(requestProcessor, "bytesSent", registry, "bytesSent"); 207 registerTomcatGauge(manager, "activeSessions", registry, "activeSessions"); 208 } else { 209 registry.removeMatching((name, metric) -> name.getKey().startsWith("tomcat.")); 210 } 211 } 212 213 protected void instrumentJvm(boolean activate) { 214 if (activate) { 215 registry.register("jvm.memory", new MemoryUsageGaugeSet()); 216 registry.register("jvm.garbage", new GarbageCollectorMetricSet()); 217 registry.register("jvm.threads", new ThreadStatesGaugeSet()); 218 registry.register("jvm.files", new FileDescriptorRatioGauge()); 219 registry.register("jvm.buffers", new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer())); 220 } else { 221 registry.removeMatching((name, metric) -> name.getKey().startsWith("jvm.")); 222 } 223 } 224}