blob: 37a67a80b4ffda89eb63f90878ec8d7bdb31f46c [file] [log] [blame]
/*
* Copyright 2017 HugeGraph Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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 com.baidu.hugegraph.stats;
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;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.baidu.hugegraph.util.Log;
import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.Counter;
import com.codahale.metrics.CsvReporter;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Slf4jReporter;
import com.codahale.metrics.Timer;
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;
/**
* Singleton that contains and configures HugeGraph's {@code MetricRegistry}.
*/
public class MetricsManager {
private static final Logger LOG = Log.logger(MetricsManager.class);
private static final MetricsManager INSTANCE = new MetricsManager();
private final MetricRegistry registry;
private ConsoleReporter consoleReporter = null;
private CsvReporter csvReporter = null;
private JmxReporter jmxReporter = null;
private Slf4jReporter slf4jReporter = null;
private GangliaReporter gangliaReporter = null;
private GraphiteReporter graphiteReporter = null;
/**
* Private constructor for singleton.
*/
private MetricsManager() {
this.registry = new MetricRegistry();
}
/**
* Return the Singleton Metrics Manager.
*
* @return the single {@code MetricManager}
*/
public static MetricsManager instance() {
return INSTANCE;
}
/**
* Return the HugeGraph Metrics registry.
*
* @return the single {@code MetricRegistry} used for all of HugeGraph's
* Metrics monitoring
*/
public MetricRegistry getRegistry() {
return this.registry;
}
/**
* Create a {@link ConsoleReporter} attached to the HugeGraph Metrics
* registry.
*
* @param reportInterval
* time to wait between dumping metrics to the console
*/
public synchronized void addConsoleReporter(Duration reportInterval) {
if (this.consoleReporter != null) {
LOG.debug("Metrics ConsoleReporter already active");
return;
}
this.consoleReporter = ConsoleReporter.forRegistry(getRegistry()).build();
this.consoleReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
}
/**
* Stop a {@link ConsoleReporter} previously created by a call to
* {@link #addConsoleReporter(Duration)} 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 (this.consoleReporter != null) {
this.consoleReporter.stop();
}
this.consoleReporter = null;
}
/**
* Create a {@link CsvReporter} attached to the HugeGraph 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 (this.csvReporter != null) {
LOG.debug("Metrics CsvReporter already active");
return;
}
if (!outputDir.exists()) {
if (!outputDir.mkdirs()) {
LOG.warn("Failed to create CSV metrics dir {}", outputDir);
}
}
this.csvReporter = CsvReporter.forRegistry(getRegistry()).build(outputDir);
this.csvReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
}
/**
* Stop a {@link CsvReporter} previously created by a call to
* {@link #addCsvReporter(Duration, 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 (this.csvReporter != null) {
this.csvReporter.stop();
}
this.csvReporter = null;
}
/**
* Create a {@link JmxReporter} attached to the HugeGraph 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(String)} 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 (this.jmxReporter != null) {
LOG.debug("Metrics JmxReporter already active");
return;
}
JmxReporter.Builder builder = JmxReporter.forRegistry(getRegistry());
if (domain != null) {
builder.inDomain(domain);
}
if (agentId != null) {
List<MBeanServer> servs =
MBeanServerFactory.findMBeanServer(agentId);
if (servs != null && servs.size() == 1) {
builder.registerWith(servs.get(0));
} else {
LOG.error("Metrics Slf4jReporter agentId {} does not " +
"resolve to a single MBeanServer", agentId);
}
}
this.jmxReporter = builder.build();
this.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 (this.jmxReporter != null) {
this.jmxReporter.stop();
}
this.jmxReporter = null;
}
/**
* Create a {@link Slf4jReporter} attached to the HugeGraph Metrics registry
* <p>
* If {@code loggerName} is null, or if it is non-null but
* {@link LoggerFactory#getLogger(String)} 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 (this.slf4jReporter != null) {
LOG.debug("Metrics Slf4jReporter already active");
return;
}
Slf4jReporter.Builder builder =
Slf4jReporter.forRegistry(getRegistry());
if (loggerName != null) {
Logger slfLogger = LoggerFactory.getLogger(loggerName);
if (slfLogger != null) {
builder.outputTo(slfLogger);
} else {
LOG.error("Logger with name {} could not be obtained",
loggerName);
}
}
this.slf4jReporter = builder.build();
this.slf4jReporter.start(reportInterval.toMillis(), TimeUnit.MILLISECONDS);
}
/**
* Stop a {@link Slf4jReporter} previously created by a call to
* {@link #addSlf4jReporter(Duration, 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 (this.slf4jReporter != null) {
this.slf4jReporter.stop();
}
this.slf4jReporter = null;
}
/**
* Create a {@link GangliaReporter} attached to the HugeGraph 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 (this.gangliaReporter != null) {
LOG.debug("Metrics GangliaReporter already active");
return;
}
if (protocol31 == null) {
protocol31 = true;
}
GMetric ganglia = new GMetric(groupOrHost, port, addressingMode, ttl,
protocol31, hostUUID, spoof);
GangliaReporter.Builder builder =
GangliaReporter.forRegistry(getRegistry());
this.gangliaReporter = builder.build(ganglia);
this.gangliaReporter.start(reportInterval.toMillis(),
TimeUnit.MILLISECONDS);
LOG.info("Configured Ganglia Metrics reporter host={} interval={} " +
"port={} addrmode={} ttl={} proto31={} uuid={} spoof={}",
groupOrHost, reportInterval, port, addressingMode, ttl,
protocol31, hostUUID, spoof);
}
/**
* Stop a {@link GangliaReporter} previously created by a call to
* {@link #addGangliaReporter}
* 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 (this.gangliaReporter != null) {
this.gangliaReporter.stop();
}
this.gangliaReporter = null;
}
/**
* Create a {@link GraphiteReporter} attached to the HugeGraph 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 builder =
GraphiteReporter.forRegistry(getRegistry());
if (prefix != null) {
builder.prefixedWith(prefix);
}
builder.filter(MetricFilter.ALL);
this.graphiteReporter = builder.build(graphite);
this.graphiteReporter.start(reportInterval.toMillis(),
TimeUnit.MILLISECONDS);
LOG.info("Configured Graphite reporter host={} interval={} " +
"port={} prefix={}", host, reportInterval, port, prefix);
}
/**
* Stop a {@link GraphiteReporter} previously created by a call to
* {@link #addGraphiteReporter(String, int, String, Duration)} 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 (this.graphiteReporter != null) {
this.graphiteReporter.stop();
}
this.graphiteReporter = null;
}
/**
* Remove all HugeGraph 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);
}
}