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}