// Copyright 2017 JanusGraph Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.janusgraph.util.stats;
import com.codahale.metrics.*;
import com.codahale.metrics.ganglia.GangliaReporter;
import com.codahale.metrics.graphite.Graphite;
import com.codahale.metrics.graphite.GraphiteReporter;
import com.google.common.base.Preconditions;
import info.ganglia.gmetric4j.gmetric.GMetric;
import info.ganglia.gmetric4j.gmetric.GMetric.UDPAddressingMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Singleton that contains and configures JanusGraph's {@code MetricRegistry}.
*/
public enum MetricManager {
INSTANCE;
private static final Logger log =
LoggerFactory.getLogger(MetricManager.class);
private final MetricRegistry registry = new MetricRegistry();
private ConsoleReporter consoleReporter = null;
private CsvReporter csvReporter = null;
private JmxReporter jmxReporter = null;
private Slf4jReporter slf4jReporter = null;
private GangliaReporter gangliaReporter = null;
private GraphiteReporter graphiteReporter = null;
/**
* Return the JanusGraph Metrics registry.
*
* @return the single {@code MetricRegistry} used for all of JanusGraph's Metrics
* monitoring
*/
public MetricRegistry getRegistry() {
return registry;
}
/**
* Create a {@link ConsoleReporter} attached to the JanusGraph Metrics registry.
*
* @param reportInterval
* time to wait between dumping metrics to the console
*/
public synchronized void addConsoleReporter(Duration reportInterval) {
if (null != consoleReporter) {
log.debug("Metrics ConsoleReporter already active; not creating another");
return;
}
consoleReporter = ConsoleReporter.forRegistry(getRegistry()).build();
consoleReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
}
/**
* Stop a {@link ConsoleReporter} previously created by a call to
* {@link #addConsoleReporter(long)} and release it for GC. Idempotent
* between calls to the associated add method. Does nothing before the first
* call to the associated add method.
*/
public synchronized void removeConsoleReporter() {
if (null != consoleReporter)
consoleReporter.stop();
consoleReporter = null;
}
/**
* Create a {@link CsvReporter} attached to the JanusGraph Metrics registry.
* <p>
* The {@code output} argument must be non-null but need not exist. If it
* doesn't already exist, this method attempts to create it by calling
* {@link File#mkdirs()}.
*
* @param reportInterval
* time to wait between dumping metrics to CSV files in
* the configured directory
* @param output
* the path to a directory into which Metrics will periodically
* write CSV data
*/
public synchronized void addCsvReporter(Duration reportInterval,
String output) {
File outputDir = new File(output);
if (null != csvReporter) {
log.debug("Metrics CsvReporter already active; not creating another");
return;
}
if (!outputDir.exists()) {
if (!outputDir.mkdirs()) {
log.warn("Failed to create CSV metrics dir {}", outputDir);
}
}
csvReporter = CsvReporter.forRegistry(getRegistry()).build(outputDir);
csvReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
}
/**
* Stop a {@link CsvReporter} previously created by a call to
* {@link #addCsvReporter(long, String)} and release it for GC. Idempotent
* between calls to the associated add method. Does nothing before the first
* call to the associated add method.
*/
public synchronized void removeCsvReporter() {
if (null != csvReporter)
csvReporter.stop();
csvReporter = null;
}
/**
* Create a {@link JmxReporter} attached to the JanusGraph Metrics registry.
* <p>
* If {@code domain} or {@code agentId} is null, then Metrics's uses its own
* internal default value(s).
* <p>
* If {@code agentId} is non-null, then
* {@link MBeanServerFactory#findMBeanServer(agentId)} must return exactly
* one {@code MBeanServer}. The reporter will register with that server. If
* the {@code findMBeanServer(agentId)} call returns no or multiple servers,
* then this method logs an error and falls back on the Metrics default for
* {@code agentId}.
*
* @param domain
* the JMX domain in which to continuously expose metrics
* @param agentId
* the JMX agent ID
*/
public synchronized void addJmxReporter(String domain, String agentId) {
if (null != jmxReporter) {
log.debug("Metrics JmxReporter already active; not creating another");
return;
}
JmxReporter.Builder b = JmxReporter.forRegistry(getRegistry());
if (null != domain) {
b.inDomain(domain);
}
if (null != agentId) {
List<MBeanServer> servs = MBeanServerFactory.findMBeanServer(agentId);
if (null != servs && 1 == servs.size()) {
b.registerWith(servs.get(0));
} else {
log.error("Metrics Slf4jReporter agentId {} does not resolve to a single MBeanServer", agentId);
}
}
jmxReporter = b.build();
jmxReporter.start();
}
/**
* Stop a {@link JmxReporter} previously created by a call to
* {@link #addJmxReporter(String, String)} and release it for GC. Idempotent
* between calls to the associated add method. Does nothing before the first
* call to the associated add method.
*/
public synchronized void removeJmxReporter() {
if (null != jmxReporter)
jmxReporter.stop();
jmxReporter = null;
}
/**
* Create a {@link Slf4jReporter} attached to the JanusGraph Metrics registry.
* <p>
* If {@code loggerName} is null, or if it is non-null but
* {@link LoggerFactory#getLogger(loggerName)} returns null, then Metrics's
* default Slf4j logger name is used instead.
*
* @param reportInterval
* time to wait between writing metrics to the Slf4j
* logger
* @param loggerName
* the name of the Slf4j logger that receives metrics
*/
public synchronized void addSlf4jReporter(Duration reportInterval, String loggerName) {
if (null != slf4jReporter) {
log.debug("Metrics Slf4jReporter already active; not creating another");
return;
}
Slf4jReporter.Builder b = Slf4jReporter.forRegistry(getRegistry());
if (null != loggerName) {
Logger l = LoggerFactory.getLogger(loggerName);
if (null != l) {
b.outputTo(l);
} else {
log.error("Logger with name {} could not be obtained", loggerName);
}
}
slf4jReporter = b.build();
slf4jReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
}
/**
* Stop a {@link Slf4jReporter} previously created by a call to
* {@link #addSlf4jReporter(long, String)} and release it for GC. Idempotent
* between calls to the associated add method. Does nothing before the first
* call to the associated add method.
*/
public synchronized void removeSlf4jReporter() {
if (null != slf4jReporter)
slf4jReporter.stop();
slf4jReporter = null;
}
/**
* Create a {@link GangliaReporter} attached to the JanusGraph Metrics registry.
* <p>
* {@code groupOrHost} and {@code addressingMode} must be non-null. The
* remaining non-primitive arguments may be null. If {@code protocol31} is
* null, then true is assumed. Null values of {@code hostUUID} or
* {@code spoof} are passed into the {@link GMetric} constructor, which
* causes Ganglia to use its internal logic for generating a default UUID
* and default reporting hostname (respectively).
*
* @param groupOrHost
* the multicast group or unicast hostname to which Ganglia
* events are sent
* @param port
* the port to which events are sent
* @param addressingMode
* whether to send events with multicast or unicast
* @param ttl
* multicast ttl (ignored for unicast)
* @param protocol31
* true to use Ganglia protocol version 3.1, false to use 3.0
* @param hostUUID
* uuid for the host
* @param spoof
* override this machine's IP/hostname as it appears on the
* Ganglia server
* @param reportInterval
* titme to wait before sending data to the ganglia
* unicast host or multicast group
* @throws IOException
* when a {@link GMetric} can't be instantiated using the
* provided arguments
*/
public synchronized void addGangliaReporter(String groupOrHost, int port,
UDPAddressingMode addressingMode, int ttl, Boolean protocol31,
UUID hostUUID, String spoof, Duration reportInterval) throws IOException {
Preconditions.checkNotNull(groupOrHost);
Preconditions.checkNotNull(addressingMode);
if (null != gangliaReporter) {
log.debug("Metrics GangliaReporter already active; not creating another");
return;
}
if (null == protocol31)
protocol31 = true;
GMetric ganglia = new GMetric(groupOrHost, port, addressingMode, ttl,
protocol31, hostUUID, spoof);
GangliaReporter.Builder b = GangliaReporter.forRegistry(getRegistry());
gangliaReporter = b.build(ganglia);
gangliaReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
log.info("Configured Ganglia Metrics reporter host={} interval={} port={} addrmode={} ttl={} proto31={} uuid={} spoof={}",
new Object[] { groupOrHost, reportInterval, port, addressingMode, ttl, protocol31, hostUUID, spoof });
}
/**
* Stop a {@link GangliaReporter} previously created by a call to
* {@link #addGangliaReporter(String, int, UDPAddressingMode, int, Boolean, UUID, long)}
* and release it for GC. Idempotent between calls to the associated add
* method. Does nothing before the first call to the associated add method.
*/
public synchronized void removeGangliaReporter() {
if (null != gangliaReporter)
gangliaReporter.stop();
gangliaReporter = null;
}
/**
* Create a {@link GraphiteReporter} attached to the JanusGraph Metrics registry.
* <p>
* If {@code prefix} is null, then Metrics's internal default prefix is used
* (empty string at the time this comment was written).
*
* @param host
* the host to which Graphite reports are sent
* @param port
* the port to which Graphite reports are sent
* @param prefix
* the optional metrics prefix
* @param reportInterval
* time to wait between sending metrics to the configured
* Graphite host and port
*/
public synchronized void addGraphiteReporter(String host, int port,
String prefix, Duration reportInterval) {
Preconditions.checkNotNull(host);
Graphite graphite = new Graphite(new InetSocketAddress(host, port));
GraphiteReporter.Builder b = GraphiteReporter
.forRegistry(getRegistry());
if (null != prefix)
b.prefixedWith(prefix);
b.filter(MetricFilter.ALL);
graphiteReporter = b.build(graphite);
graphiteReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
log.info("Configured Graphite reporter host={} interval={} port={} prefix={}",
new Object[] { host, reportInterval, port, prefix });
}
/**
* Stop a {@link GraphiteReporter} previously created by a call to
* {@link #addGraphiteReporter(String, int, String, long)} and release it
* for GC. Idempotent between calls to the associated add method. Does
* nothing before the first call to the associated add method.
*/
public synchronized void removeGraphiteReporter() {
if (null != graphiteReporter)
graphiteReporter.stop();
graphiteReporter = null;
}
/**
* Remove all JanusGraph Metrics reporters previously configured through the
* {@code add*} methods on this class.
*/
public synchronized void removeAllReporters() {
removeConsoleReporter();
removeCsvReporter();
removeJmxReporter();
removeSlf4jReporter();
removeGangliaReporter();
removeGraphiteReporter();
}
public Counter getCounter(String name) {
return getRegistry().counter(name);
}
public Counter getCounter(String prefix, String... names) {
return getRegistry().counter(MetricRegistry.name(prefix, names));
}
public Timer getTimer(String name) {
return getRegistry().timer(name);
}
public Timer getTimer(String prefix, String... names) {
return getRegistry().timer(MetricRegistry.name(prefix, names));
}
public Histogram getHistogram(String name) {
return getRegistry().histogram(name);
}
public Histogram getHistogram(String prefix, String... names) {
return getRegistry().histogram(MetricRegistry.name(prefix, names));
}
public boolean remove(String name) {
return getRegistry().remove(name);
}
}