001/*
002 * (C) Copyright 2013-2014 Nuxeo SA (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 java.io.File;
022import java.io.Serializable;
023import java.net.InetAddress;
024import java.net.InetSocketAddress;
025import java.net.UnknownHostException;
026import java.text.DateFormat;
027import java.text.SimpleDateFormat;
028import java.util.Calendar;
029import java.util.Date;
030import java.util.concurrent.TimeUnit;
031
032import javax.management.MalformedObjectNameException;
033import javax.management.ObjectName;
034
035import org.apache.commons.logging.LogFactory;
036import org.apache.log4j.LogManager;
037
038import org.nuxeo.common.Environment;
039import org.nuxeo.common.xmap.annotation.XNode;
040import org.nuxeo.common.xmap.annotation.XObject;
041import org.nuxeo.runtime.api.Framework;
042import org.nuxeo.runtime.management.ServerLocator;
043
044import com.codahale.metrics.JmxAttributeGauge;
045import com.codahale.metrics.JmxReporter;
046import com.codahale.metrics.Metric;
047import com.codahale.metrics.MetricFilter;
048import com.codahale.metrics.MetricRegistry;
049import com.codahale.metrics.graphite.Graphite;
050import com.codahale.metrics.graphite.GraphiteReporter;
051import com.codahale.metrics.jvm.BufferPoolMetricSet;
052import com.codahale.metrics.jvm.FileDescriptorRatioGauge;
053import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
054import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
055import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
056import com.codahale.metrics.log4j.InstrumentedAppender;
057
058@XObject("metrics")
059public class MetricsDescriptor implements Serializable {
060
061    private static final long serialVersionUID = 7833869486922092460L;
062
063    public MetricsDescriptor() {
064        super();
065        graphiteReporter = new GraphiteDescriptor();
066        csvReporter = new CsvDescriptor();
067        tomcatInstrumentation = new TomcatInstrumentationDescriptor();
068        log4jInstrumentation = new Log4jInstrumentationDescriptor();
069    }
070
071    @XObject(value = "graphiteReporter")
072    public static class GraphiteDescriptor {
073
074        public static final String ENABLED_PROPERTY = "metrics.graphite.enabled";
075
076        public static final String HOST_PROPERTY = "metrics.graphite.host";
077
078        public static final String PORT_PROPERTY = "metrics.graphite.port";
079
080        public static final String PERIOD_PROPERTY = "metrics.graphite.period";
081
082        public static final String PREFIX_PROPERTY = "metrics.graphite.prefix";
083
084        @XNode("@enabled")
085        protected Boolean enabled = Boolean.valueOf(Framework.getProperty(ENABLED_PROPERTY, "false"));
086
087        @XNode("@host")
088        public String host = Framework.getProperty(HOST_PROPERTY, "0.0.0.0");
089
090        @XNode("@port")
091        public Integer port = Integer.valueOf(Framework.getProperty(PORT_PROPERTY, "2030"));
092
093        @XNode("@periodInSecond")
094        public Integer period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10"));
095
096        @XNode("@prefix")
097        public String prefix = prefix();
098
099        public String prefix() {
100            if (prefix == null) {
101                prefix = Framework.getProperty(PREFIX_PROPERTY, "servers.${hostname}.nuxeo");
102            }
103            String hostname;
104            try {
105                hostname = InetAddress.getLocalHost().getHostName().split("\\.")[0];
106            } catch (UnknownHostException e) {
107                hostname = "unknown";
108            }
109            return prefix.replace("${hostname}", hostname);
110        }
111
112        @Override
113        public String toString() {
114            return String.format("graphiteReporter %s prefix: %s, host: %s, port: %d, period: %d", enabled ? "enabled"
115                    : "disabled", prefix, host, port, period);
116        }
117
118        protected GraphiteReporter reporter;
119
120        public void enable(MetricRegistry registry) {
121            if (!enabled) {
122                return;
123            }
124            InetSocketAddress address = new InetSocketAddress(host, port);
125            Graphite graphite = new Graphite(address);
126            reporter = GraphiteReporter.forRegistry(registry).convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(
127                    TimeUnit.MICROSECONDS).prefixedWith(prefix()).build(graphite);
128            reporter.start(period, TimeUnit.SECONDS);
129        }
130
131        public void disable(MetricRegistry registry) {
132            if (reporter == null) {
133                return;
134            }
135            try {
136                reporter.stop();
137            } finally {
138                reporter = null;
139            }
140        }
141    }
142
143    @XObject(value = "csvReporter")
144    public static class CsvDescriptor {
145
146        public static final String ENABLED_PROPERTY = "metrics.csv.enabled";
147
148        public static final String PERIOD_PROPERTY = "metrics.csv.period";
149
150        public static final String OUTPUT_PROPERTY = "metrics.csv.output";
151
152        @XNode("@output")
153        public File outputDir = outputDir();
154
155        @XNode("@periodInSecond")
156        public Integer period = 10;
157
158        @XNode("@enabled")
159        public boolean enabled = Framework.isBooleanPropertyTrue(ENABLED_PROPERTY);
160
161        public int getPeriod() {
162            if (period == null) {
163                period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10"));
164            }
165            return period;
166        }
167
168        protected File outputDir() {
169            String path = Framework.getProperty(OUTPUT_PROPERTY, Framework.getProperty(Environment.NUXEO_LOG_DIR));
170            DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
171            Date today = Calendar.getInstance().getTime();
172            outputDir = new File(path, "metrics-" + df.format(today));
173            return outputDir;
174        }
175
176        @Override
177        public String toString() {
178            return String.format("csvReporter %s, outputDir: %s, period: %d", enabled ? "enabled" : "disabled",
179                    outputDir().toString(), getPeriod());
180        }
181
182        protected CsvReporter reporter;
183
184        public void enable(MetricRegistry registry) {
185            if (!enabled) {
186                return;
187            }
188            File parentDir = outputDir.getParentFile();
189            if (parentDir.exists() && parentDir.isDirectory()) {
190                outputDir.mkdir();
191                reporter = CsvReporter.forRegistry(registry).build(outputDir);
192                reporter.start(Long.valueOf(period), TimeUnit.SECONDS);
193            } else {
194                enabled = false;
195                LogFactory.getLog(MetricsServiceImpl.class).error("Invalid output directory, disabling: " + this);
196            }
197        }
198
199        public void disable(MetricRegistry registry) {
200            if (reporter == null) {
201                return;
202            }
203            try {
204                reporter.stop();
205            } finally {
206                reporter = null;
207            }
208        }
209
210    }
211
212    @XObject(value = "log4jInstrumentation")
213    public static class Log4jInstrumentationDescriptor {
214
215        public static final String ENABLED_PROPERTY = "metrics.log4j.enabled";
216
217        @XNode("@enabled")
218        protected boolean enabled = Boolean.getBoolean(Framework.getProperty(ENABLED_PROPERTY, "false"));
219
220        private InstrumentedAppender appender;
221
222        @Override
223        public String toString() {
224            return String.format("log4jInstrumentation %s", enabled ? "enabled" : "disabled");
225        }
226
227        public void enable(MetricRegistry registry) {
228            if (!enabled) {
229                return;
230            }
231            LogFactory.getLog(MetricsServiceImpl.class).info(this);
232            appender = new InstrumentedAppender(registry);
233            LogManager.getRootLogger().addAppender(appender);
234        }
235
236        public void disable(MetricRegistry registry) {
237            if (appender == null) {
238                return;
239            }
240            try {
241                LogManager.getRootLogger().removeAppender(appender);
242            } finally {
243                appender = null;
244            }
245        }
246
247    }
248
249    @XObject(value = "tomcatInstrumentation")
250    public static class TomcatInstrumentationDescriptor {
251
252        public static final String ENABLED_PROPERTY = "metrics.tomcat.enabled";
253
254        @XNode("@enabled")
255        protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "false"));
256
257        @Override
258        public String toString() {
259            return String.format("tomcatInstrumentation %s", enabled ? "enabled" : "disabled");
260        }
261
262        protected void registerTomcatGauge(String mbean, String attribute, MetricRegistry registry, String name) {
263            try {
264                registry.register(MetricRegistry.name("tomcat", name), new JmxAttributeGauge(new ObjectName(mbean),
265                        attribute));
266            } catch (MalformedObjectNameException | IllegalArgumentException e) {
267                throw new UnsupportedOperationException("Cannot compute object name of " + mbean, e);
268            }
269        }
270
271        public void enable(MetricRegistry registry) {
272            if (!enabled) {
273                return;
274            }
275            LogFactory.getLog(MetricsServiceImpl.class).info(this);
276            // TODO: do not hard code the common datasource
277            // nameenable(registry)
278            String pool = "Catalina:type=DataSource,class=javax.sql.DataSource,name=\"jdbc/nuxeo\"";
279            String connector = String.format("Catalina:type=ThreadPool,name=\"http-bio-%s-%s\"",
280                    Framework.getProperty("nuxeo.bind.address", "0.0.0.0"),
281                    Framework.getProperty("nuxeo.bind.port", "8080"));
282            String requestProcessor = String.format("Catalina:type=GlobalRequestProcessor,name=\"http-bio-%s-%s\"",
283                    Framework.getProperty("nuxeo.bind.address", "0.0.0.0"),
284                    Framework.getProperty("nuxeo.bind.port", "8080"));
285            String manager = "Catalina:type=Manager,context=/nuxeo,host=localhost";
286            registerTomcatGauge(pool, "numActive", registry, "jdbc-numActive");
287            registerTomcatGauge(pool, "numIdle", registry, "jdbc-numIdle");
288            registerTomcatGauge(connector, "currentThreadCount", registry, "currentThreadCount");
289            registerTomcatGauge(connector, "currentThreadsBusy", registry, "currentThreadBusy");
290            registerTomcatGauge(requestProcessor, "errorCount", registry, "errorCount");
291            registerTomcatGauge(requestProcessor, "requestCount", registry, "requestCount");
292            registerTomcatGauge(requestProcessor, "processingTime", registry, "processingTime");
293            registerTomcatGauge(manager, "activeSessions", registry, "activeSessions");
294        }
295
296        public void disable(MetricRegistry registry) {
297            registry.remove("tomcat.jdbc-numActive");
298            registry.remove("tomcat.jdbc-numIdle");
299            registry.remove("tomcat.currentThreadCount");
300            registry.remove("tomcat.currentThreadBusy");
301            registry.remove("tomcat.errorCount");
302            registry.remove("tomcat.requestCount");
303            registry.remove("tomcat.processingTime");
304            registry.remove("tomcat.activeSessions");
305        }
306    }
307
308    @XObject(value = "jvmInstrumentation")
309    public static class JvmInstrumentationDescriptor {
310
311        public static final String ENABLED_PROPERTY = "metrics.jvm.enabled";
312
313        @XNode("@enabled")
314        protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "true"));
315
316        public void enable(MetricRegistry registry) {
317            if (!enabled) {
318                return;
319            }
320            registry.register("jvm.memory", new MemoryUsageGaugeSet());
321            registry.register("jvm.garbage", new GarbageCollectorMetricSet());
322            registry.register("jvm.threads", new ThreadStatesGaugeSet());
323            registry.register("jvm.files", new FileDescriptorRatioGauge());
324            registry.register("jvm.buffers", new BufferPoolMetricSet(
325                    Framework.getLocalService(ServerLocator.class).lookupServer()));
326        }
327
328        public void disable(MetricRegistry registry) {
329            if (!enabled) {
330                return;
331            }
332            registry.removeMatching(new MetricFilter() {
333
334                @Override
335                public boolean matches(String name, Metric metric) {
336                    return name.startsWith("jvm.");
337                }
338            });
339
340        }
341    }
342
343    @XNode("graphiteReporter")
344    public GraphiteDescriptor graphiteReporter = new GraphiteDescriptor();
345
346    @XNode("csvReporter")
347    public CsvDescriptor csvReporter = new CsvDescriptor();
348
349    @XNode("log4jInstrumentation")
350    public Log4jInstrumentationDescriptor log4jInstrumentation = new Log4jInstrumentationDescriptor();
351
352    @XNode("tomcatInstrumentation")
353    public TomcatInstrumentationDescriptor tomcatInstrumentation = new TomcatInstrumentationDescriptor();
354
355    @XNode(value = "jvmInstrumentation")
356    public JvmInstrumentationDescriptor jvmInstrumentation = new JvmInstrumentationDescriptor();
357
358    protected JmxReporter jmxReporter;
359
360    public void enable(MetricRegistry registry) {
361        jmxReporter = JmxReporter.forRegistry(registry).build();
362        jmxReporter.start();
363        graphiteReporter.enable(registry);
364        csvReporter.enable(registry);
365        log4jInstrumentation.enable(registry);
366        tomcatInstrumentation.enable(registry);
367        jvmInstrumentation.enable(registry);
368    }
369
370    public void disable(MetricRegistry registry) {
371        try {
372            graphiteReporter.disable(registry);
373            csvReporter.disable(registry);
374            log4jInstrumentation.disable(registry);
375            tomcatInstrumentation.disable(registry);
376            jvmInstrumentation.disable(registry);
377            jmxReporter.stop();
378        } finally {
379            jmxReporter = null;
380        }
381    }
382
383}