2015-01-09 23:55:35 +00:00
|
|
|
package com.github.lookout.verspaetung
|
|
|
|
|
2015-01-19 19:45:22 +00:00
|
|
|
import com.github.lookout.verspaetung.zk.BrokerTreeWatcher
|
2015-01-28 10:45:49 +00:00
|
|
|
import com.github.lookout.verspaetung.zk.KafkaSpoutTreeWatcher
|
2015-01-19 21:08:01 +00:00
|
|
|
import com.github.lookout.verspaetung.zk.StandardTreeWatcher
|
2015-01-19 19:45:22 +00:00
|
|
|
|
2015-01-28 09:13:13 +00:00
|
|
|
import java.util.AbstractMap
|
2015-01-18 18:25:04 +00:00
|
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
|
|
import groovy.transform.TypeChecked
|
|
|
|
|
2015-01-20 21:23:01 +00:00
|
|
|
import com.timgroup.statsd.StatsDClient
|
2015-01-26 12:09:47 +00:00
|
|
|
import com.timgroup.statsd.NonBlockingDogStatsDClient
|
2015-01-20 21:23:01 +00:00
|
|
|
|
2015-01-26 14:15:35 +00:00
|
|
|
import org.apache.commons.cli.*
|
2015-01-10 02:17:45 +00:00
|
|
|
import org.apache.curator.retry.ExponentialBackoffRetry
|
|
|
|
import org.apache.curator.framework.CuratorFrameworkFactory
|
2015-01-17 22:42:24 +00:00
|
|
|
import org.apache.curator.framework.CuratorFramework
|
|
|
|
import org.apache.curator.framework.recipes.cache.TreeCache
|
2015-01-26 12:09:47 +00:00
|
|
|
import org.slf4j.Logger
|
|
|
|
import org.slf4j.LoggerFactory
|
2015-01-19 19:45:22 +00:00
|
|
|
|
2015-01-10 02:17:45 +00:00
|
|
|
class Main {
|
2015-01-26 14:15:35 +00:00
|
|
|
private static final String METRICS_PREFIX = 'verspaetung'
|
|
|
|
|
|
|
|
private static StatsDClient statsd
|
|
|
|
private static Logger logger
|
2015-01-20 21:23:01 +00:00
|
|
|
|
2015-01-10 02:17:45 +00:00
|
|
|
static void main(String[] args) {
|
2015-01-26 14:15:35 +00:00
|
|
|
String zookeeperHosts = 'localhost:2181'
|
|
|
|
String statsdHost = 'localhost'
|
|
|
|
Integer statsdPort = 8125
|
|
|
|
|
|
|
|
CommandLine cli = parseCommandLine(args)
|
|
|
|
|
|
|
|
if (cli.hasOption('z')) {
|
|
|
|
zookeeperHosts = cli.getOptionValue('z')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cli.hasOption('H')) {
|
|
|
|
statsdHost = cli.getOptionValue('H')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cli.hasOption('p')) {
|
|
|
|
statsdPort = cli.getOptionValue('p')
|
|
|
|
}
|
|
|
|
|
|
|
|
logger = LoggerFactory.getLogger(Main.class)
|
2015-01-26 12:09:47 +00:00
|
|
|
logger.info("Running with: ${args}")
|
2015-01-26 14:15:35 +00:00
|
|
|
logger.warn("Using: zookeepers=${zookeeperHosts} statsd=${statsdHost}:${statsdPort}")
|
2015-01-10 02:17:45 +00:00
|
|
|
|
|
|
|
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3)
|
2015-01-26 14:15:35 +00:00
|
|
|
CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeperHosts, retry)
|
2015-01-19 18:10:28 +00:00
|
|
|
ConcurrentHashMap<TopicPartition, List<zk.ConsumerOffset>> consumers = new ConcurrentHashMap()
|
2015-01-19 19:45:22 +00:00
|
|
|
|
2015-01-26 14:15:35 +00:00
|
|
|
statsd = new NonBlockingDogStatsDClient(METRICS_PREFIX, statsdHost, statsdPort)
|
|
|
|
|
2015-01-10 02:17:45 +00:00
|
|
|
client.start()
|
2015-01-17 22:42:24 +00:00
|
|
|
|
2015-01-28 09:13:13 +00:00
|
|
|
KafkaPoller poller = setupKafkaPoller(consumers, statsd, cli.hasOption('n'))
|
2015-01-28 10:45:49 +00:00
|
|
|
BrokerTreeWatcher brokerWatcher = new BrokerTreeWatcher(client).start()
|
|
|
|
StandardTreeWatcher consumerWatcher = new StandardTreeWatcher(client, consumers).start()
|
|
|
|
|
|
|
|
/* Assuming that most people aren't needing to run Storm-based watchers
|
|
|
|
* as well
|
|
|
|
*/
|
|
|
|
if (cli.hasOption('s')) {
|
|
|
|
KafkaSpoutTreeWatcher stormWatcher = new KafkaSpoutTreeWatcher(client, consumers)
|
|
|
|
stormWatcher.start()
|
|
|
|
}
|
2015-01-28 09:13:13 +00:00
|
|
|
|
2015-01-19 21:15:00 +00:00
|
|
|
consumerWatcher.onInitComplete << {
|
2015-01-26 12:09:47 +00:00
|
|
|
logger.info("standard consumers initialized to ${consumers.size()} (topic, partition) tuples")
|
2015-01-19 21:08:01 +00:00
|
|
|
}
|
|
|
|
|
2015-01-19 21:15:00 +00:00
|
|
|
brokerWatcher.onBrokerUpdates << { brokers ->
|
2015-01-19 19:45:22 +00:00
|
|
|
poller.refresh(brokers)
|
|
|
|
}
|
|
|
|
|
2015-01-26 12:09:47 +00:00
|
|
|
logger.info("Started wait loop...")
|
2015-01-17 22:42:24 +00:00
|
|
|
|
2015-01-26 15:00:07 +00:00
|
|
|
while (true) {
|
|
|
|
statsd?.recordGaugeValue('heartbeat', 1)
|
|
|
|
Thread.sleep(1 * 1000)
|
|
|
|
}
|
2015-01-17 22:42:24 +00:00
|
|
|
|
2015-01-26 12:09:47 +00:00
|
|
|
logger.info("exiting..")
|
2015-01-19 19:45:22 +00:00
|
|
|
poller.die()
|
2015-01-20 00:22:22 +00:00
|
|
|
poller.join()
|
2015-01-17 22:42:24 +00:00
|
|
|
return
|
2015-01-10 02:17:45 +00:00
|
|
|
}
|
2015-01-26 14:15:35 +00:00
|
|
|
|
2015-01-28 09:13:13 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create and start a KafkaPoller with the given statsd and consumers map
|
|
|
|
*/
|
|
|
|
static KafkaPoller setupKafkaPoller(AbstractMap consumers,
|
|
|
|
NonBlockingDogStatsDClient statsd,
|
|
|
|
Boolean dryRun) {
|
|
|
|
KafkaPoller poller = new KafkaPoller(consumers)
|
|
|
|
Closure deltaCallback = { String name, TopicPartition tp, Long delta ->
|
|
|
|
println "${tp.topic}:${tp.partition}-${name} = ${delta}"
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dryRun) {
|
|
|
|
deltaCallback = { String name, TopicPartition tp, Long delta ->
|
|
|
|
statsd.recordGaugeValue(tp.topic, delta, [
|
|
|
|
'topic' : tp.topic,
|
|
|
|
'partition' : tp.partition,
|
|
|
|
'consumer-group' : name
|
|
|
|
])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
poller.onDelta << deltaCallback
|
|
|
|
poller.start()
|
|
|
|
return poller
|
|
|
|
}
|
|
|
|
|
2015-01-26 14:15:35 +00:00
|
|
|
static Options createCLI() {
|
|
|
|
Options options = new Options()
|
|
|
|
|
|
|
|
Option zookeeper = OptionBuilder.withArgName('HOSTS')
|
|
|
|
.hasArg()
|
|
|
|
.withDescription('Comma separated list of Zookeeper hosts (e.g. localhost:2181)')
|
|
|
|
.withLongOpt('zookeeper')
|
|
|
|
.withValueSeparator(',' as char)
|
|
|
|
.create('z')
|
|
|
|
|
2015-01-28 09:13:13 +00:00
|
|
|
Option statsdHost = OptionBuilder.withArgName('STATSD')
|
|
|
|
.hasArg()
|
|
|
|
.withType(String)
|
|
|
|
.withDescription('Hostname for a statsd instance (defaults to localhost)')
|
|
|
|
.withLongOpt('statsd-host')
|
|
|
|
.create('H')
|
|
|
|
|
|
|
|
Option statsdPort = OptionBuilder.withArgName('PORT')
|
|
|
|
.hasArg()
|
|
|
|
.withType(Integer)
|
|
|
|
.withDescription('Port for the statsd instance (defaults to 8125)')
|
|
|
|
.withLongOpt('statsd-port')
|
|
|
|
.create('p')
|
|
|
|
|
|
|
|
Option dryRun = OptionBuilder.withDescription('Disable reporting to a statsd host')
|
|
|
|
.withLongOpt('dry-run')
|
|
|
|
.create('n')
|
2015-01-26 14:15:35 +00:00
|
|
|
|
2015-01-28 10:45:49 +00:00
|
|
|
Option stormSpouts = OptionBuilder.withDescription('Watch Storm KafkaSpout offsets (under /kafka_spout)')
|
|
|
|
.withLongOpt('storm')
|
|
|
|
.create('s')
|
|
|
|
|
2015-01-26 14:15:35 +00:00
|
|
|
options.addOption(zookeeper)
|
2015-01-28 09:13:13 +00:00
|
|
|
options.addOption(statsdHost)
|
|
|
|
options.addOption(statsdPort)
|
|
|
|
options.addOption(dryRun)
|
2015-01-28 10:45:49 +00:00
|
|
|
options.addOption(stormSpouts)
|
2015-01-26 14:15:35 +00:00
|
|
|
|
|
|
|
return options
|
|
|
|
}
|
|
|
|
|
|
|
|
static CommandLine parseCommandLine(String[] args) {
|
|
|
|
Options options = createCLI()
|
|
|
|
PosixParser parser = new PosixParser()
|
|
|
|
|
|
|
|
try {
|
|
|
|
return parser.parse(options, args)
|
|
|
|
}
|
|
|
|
catch (MissingOptionException|UnrecognizedOptionException ex) {
|
|
|
|
HelpFormatter formatter = new HelpFormatter()
|
|
|
|
println ex.message
|
|
|
|
formatter.printHelp('verspaetung', options)
|
|
|
|
System.exit(1)
|
|
|
|
}
|
|
|
|
}
|
2015-01-10 02:17:45 +00:00
|
|
|
}
|