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