/**
* Copyright 2015 Otto (GmbH & Co KG)
*
* 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 com.ottogroup.bi.spqr.metrics;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.CsvReporter;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.Reporter;
import com.codahale.metrics.graphite.Graphite;
import com.codahale.metrics.graphite.GraphiteReporter;
import com.ottogroup.bi.spqr.exception.RequiredInputMissingException;
import com.ottogroup.bi.spqr.metrics.kafka.KafkaReporter;
import com.ottogroup.bi.spqr.pipeline.metrics.MetricReporterType;
import com.ottogroup.bi.spqr.pipeline.metrics.MicroPipelineMetricsReporterConfiguration;
/**
* Generates {@link Reporter} instances from provided settings and attaches them to a {@link MetricsHandler}
* @author mnxfst
* @since May 27, 2015
*/
public class MetricsReporterFactory {
/**
* Attaches a {@link GraphiteReporter} to provided {@link MetricsHandler}
* @param metricsHandler
* @param id
* @param period
* @param host
* @param port
* @throws RequiredInputMissingException
*/
private static void attachGraphiteReporter(final MetricsHandler metricsHandler, final String id, final int period, final String host, final int port) throws RequiredInputMissingException {
//////////////////////////////////////////////////////////////////////////
// validate input
if(StringUtils.isBlank(id))
throw new RequiredInputMissingException("Missing required metric reporter id");
if(metricsHandler == null)
throw new RequiredInputMissingException("Missing required metrics handler");
if(StringUtils.isBlank(host))
throw new RequiredInputMissingException("Missing required graphite host");
if(port < 0)
throw new RequiredInputMissingException("Missing required graphite port");
//////////////////////////////////////////////////////////////////////////
final Graphite graphite = new Graphite(new InetSocketAddress(host, port));
final GraphiteReporter reporter = GraphiteReporter.forRegistry(metricsHandler.getRegistry())
.convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(TimeUnit.MILLISECONDS)
.filter(MetricFilter.ALL)
.build(graphite);
reporter.start((period > 0 ? period : 1), TimeUnit.SECONDS);
metricsHandler.addScheduledReporter(id, reporter);
}
/**
* Attaches a {@link CsvReporter} to provided {@link MetricsHandler}
* @param metricsHandler
* @param id
* @param period
* @param outputFile
* @throws RequiredInputMissingException
*/
private static void attachCSVReporter(final MetricsHandler metricsHandler, final String id, final int period, final String outputFile) throws RequiredInputMissingException {
//////////////////////////////////////////////////////////////////////////
// validate input
if(StringUtils.isBlank(id))
throw new RequiredInputMissingException("Missing required metric reporter id");
if(metricsHandler == null)
throw new RequiredInputMissingException("Missing required metrics handler");
if(StringUtils.isBlank(outputFile))
throw new RequiredInputMissingException("Missing required output file for CSV metrics writer");
File oFile = new File(outputFile);
if(oFile.isDirectory())
throw new RequiredInputMissingException("Output file points to directory");
//////////////////////////////////////////////////////////////////////////
final CsvReporter reporter = CsvReporter.forRegistry(metricsHandler.getRegistry())
.formatFor(Locale.US).convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(TimeUnit.MILLISECONDS)
.build(oFile);
reporter.start((period > 0 ? period : 1), TimeUnit.SECONDS);
metricsHandler.addScheduledReporter(id, reporter);
}
/**
* Attaches a {@link ConsoleReporter} to provided {@link MetricsHandler}
* @param metricsHandler
* @param id
* @param period
* @throws RequiredInputMissingException
*/
private static void attachConsoleReporter(final MetricsHandler metricsHandler, final String id, final int period) throws RequiredInputMissingException {
//////////////////////////////////////////////////////////////////////////
// validate input
if(StringUtils.isBlank(id))
throw new RequiredInputMissingException("Missing required metric reporter id");
if(metricsHandler == null)
throw new RequiredInputMissingException("Missing required metrics handler");
//////////////////////////////////////////////////////////////////////////
final ConsoleReporter reporter = ConsoleReporter.forRegistry(metricsHandler.getRegistry()).
convertDurationsTo(TimeUnit.SECONDS).convertRatesTo(TimeUnit.MILLISECONDS).formattedFor(Locale.US).build();
reporter.start((period > 0 ? period : 1), TimeUnit.SECONDS);
metricsHandler.addScheduledReporter(id, reporter);
}
/**
* Attaches a {@link KafkaReporter} to provided {@link MetricsHandler}
* @param metricsHandler
* @param id
* @param period
* @param zookeeperConnect
* @param brokerList
* @param clientId
* @param topicId
* @throws RequiredInputMissingException
*/
private static void attachKafkaReporter(final MetricsHandler metricsHandler, final String id, final int period,
final String zookeeperConnect, final String brokerList, final String clientId, final String topicId) throws RequiredInputMissingException {
//////////////////////////////////////////////////////////////////////////
// validate input
if(StringUtils.isBlank(id))
throw new RequiredInputMissingException("Missing required metric reporter id");
if(metricsHandler == null)
throw new RequiredInputMissingException("Missing required metrics handler");
if(StringUtils.isBlank(zookeeperConnect))
throw new RequiredInputMissingException("Missing required zookeeper connect");
if(StringUtils.isBlank(brokerList))
throw new RequiredInputMissingException("Missing required broker list");
if(StringUtils.isBlank(clientId))
throw new RequiredInputMissingException("Missing required client identifier");
if(StringUtils.isBlank(topicId))
throw new RequiredInputMissingException("Missing required topic identifier");
//////////////////////////////////////////////////////////////////////////
final KafkaReporter reporter = KafkaReporter.forRegistry(metricsHandler.getRegistry())
.brokerList("localhost:9092").clientId(clientId)
.convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(TimeUnit.MICROSECONDS)
.topic(topicId).zookeeperConnect(zookeeperConnect).build();
reporter.start((period > 0 ? period : 1), TimeUnit.SECONDS);
metricsHandler.addScheduledReporter(id, reporter);
}
/**
* Attaches a new reporter of specified {@link MetricReporterType type} to contained {@link MetricsHandler#getRegistry() registry}
* @param metricsHandler
* @param reporterConfig
* @throws RequiredInputMissingException
*/
public static void attachReporters(final MetricsHandler metricsHandler, final List<MicroPipelineMetricsReporterConfiguration> reporterConfig) throws RequiredInputMissingException {
if(reporterConfig == null || reporterConfig.isEmpty())
return;
///////////////////////////////////////////////////////////////////
// validate input
if(metricsHandler == null)
throw new RequiredInputMissingException("Missing required metrics handler");
///////////////////////////////////////////////////////////////////
for(final MicroPipelineMetricsReporterConfiguration rc : reporterConfig) {
if(rc.getType() == null)
throw new RequiredInputMissingException("Missing required type for metrics reporter configuration [id="+rc.getId()+"]");
switch(rc.getType()) {
case CONSOLE: {
MetricsReporterFactory.attachConsoleReporter(metricsHandler, rc.getId(), rc.getPeriod());
break;
}
case CSV: {
if(rc.getSettings() == null || rc.getSettings().isEmpty())
throw new RequiredInputMissingException("Missing required settings for metrics reporter configuration [id="+rc.getId()+"]");
MetricsReporterFactory.attachCSVReporter(metricsHandler, rc.getId(), rc.getPeriod(), rc.getSettings().get(MicroPipelineMetricsReporterConfiguration.SETTING_CSV_OUTPUT_FILE));
break;
}
case GRAPHITE: {
if(rc.getSettings() == null || rc.getSettings().isEmpty())
throw new RequiredInputMissingException("Missing required settings for metrics reporter configuration [id="+rc.getId()+"]");
String p = rc.getSettings().get(MicroPipelineMetricsReporterConfiguration.SETTING_GRAPHITE_PORT);
int port = -1;
try {
port = Integer.parseInt(StringUtils.trim(p));
} catch(Exception e) {
throw new RequiredInputMissingException("Invalid input found for graphite port [id="+rc.getId()+"]. Error: " + e.getMessage());
}
MetricsReporterFactory.attachGraphiteReporter(metricsHandler, rc.getId(), rc.getPeriod(), rc.getSettings().get(MicroPipelineMetricsReporterConfiguration.SETTING_GRAPHITE_HOST), port);
break;
}
case KAFKA: {
MetricsReporterFactory.attachKafkaReporter(metricsHandler, rc.getId(), rc.getPeriod(),
rc.getSettings().get(MicroPipelineMetricsReporterConfiguration.SETTING_KAFKA_ZOOKEEPER_CONNECT),
rc.getSettings().get(MicroPipelineMetricsReporterConfiguration.SETTING_KAFKA_BROKER_LIST),
rc.getSettings().get(MicroPipelineMetricsReporterConfiguration.SETTING_KAFKA_CLIENT_ID),
rc.getSettings().get(MicroPipelineMetricsReporterConfiguration.SETTING_KAFKA_TOPIC_ID));
break;
}
default: {
throw new RequiredInputMissingException("Unknown metrics reporter type: " + rc.getType());
}
}
}
}
}