/** * Copyright 2016 LinkedIn Corp. 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. */ package com.linkedin.kmf; import com.fasterxml.jackson.databind.ObjectMapper; import com.linkedin.kmf.services.Service; import com.linkedin.kmf.apps.App; import com.linkedin.kmf.tests.Test; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.FileReader; import java.util.Iterator; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * This is the main entry point of the monitor. It reads the configuration and manages the life cycle of the monitoring * applications. */ public class KafkaMonitor { private static final Logger LOG = LoggerFactory.getLogger(KafkaMonitor.class); public static final String CLASS_NAME_CONFIG = "class.name"; /** This is concurrent because healthCheck() can modify this map, but awaitShutdown() can be called at any time by * a different thread. */ private final ConcurrentMap<String, App> _apps; private final ConcurrentMap<String, Service> _services; private final ScheduledExecutorService _executor; /** When true start has been called on this instance of Kafka monitor. */ private final AtomicBoolean _isRunning = new AtomicBoolean(false); public KafkaMonitor(Map<String, Map> testProps) throws Exception { _apps = new ConcurrentHashMap<>(); _services = new ConcurrentHashMap<>(); for (Map.Entry<String, Map> entry : testProps.entrySet()) { String name = entry.getKey(); Map props = entry.getValue(); if (!props.containsKey(CLASS_NAME_CONFIG)) throw new IllegalArgumentException(name + " is not configured with " + CLASS_NAME_CONFIG); String className = (String) props.get(CLASS_NAME_CONFIG); if (className.startsWith(App.class.getPackage().getName()) || className.startsWith(Test.class.getPackage().getName())) { App test = (App) Class.forName(className).getConstructor(Map.class, String.class).newInstance(props, name); _apps.put(name, test); } else { Service service = (Service) Class.forName(className).getConstructor(Map.class, String.class).newInstance(props, name); _services.put(name, service); } } _executor = Executors.newSingleThreadScheduledExecutor(); } public synchronized void start() { if (!_isRunning.compareAndSet(false, true)) { return; } for (Map.Entry<String, App> entry: _apps.entrySet()) { entry.getValue().start(); } for (Map.Entry<String, Service> entry: _services.entrySet()) { entry.getValue().start(); } _executor.scheduleAtFixedRate( new Runnable() { @Override public void run() { try { checkHealth(); } catch (Exception e) { LOG.error("Failed to check health of tests and services", e); } } }, 5, 5, TimeUnit.SECONDS ); } private void checkHealth() { Iterator<Map.Entry<String, App>> testIt = _apps.entrySet().iterator(); while (testIt.hasNext()) { Map.Entry<String, App> entry = testIt.next(); if (!entry.getValue().isRunning()) { LOG.error("Test " + entry.getKey() + " has stopped."); } } Iterator<Map.Entry<String, Service>> serviceIt = _services.entrySet().iterator(); while (serviceIt.hasNext()) { Map.Entry<String, Service> entry = serviceIt.next(); if (!entry.getValue().isRunning()) { LOG.error("Service " + entry.getKey() + " has stopped."); } } } public synchronized void stop() { if (!_isRunning.compareAndSet(true, false)) { return; } _executor.shutdownNow(); for (App test: _apps.values()) test.stop(); for (Service service: _services.values()) service.stop(); } public void awaitShutdown() { for (App test: _apps.values()) test.awaitShutdown(); for (Service service: _services.values()) service.awaitShutdown(); } public static void main(String[] args) throws Exception { if (args.length <= 0) { LOG.info("USAGE: java [options] " + KafkaMonitor.class.getName() + " config/kafka-monitor.properties"); return; } StringBuilder buffer = new StringBuilder(); try (BufferedReader br = new BufferedReader(new FileReader(args[0].trim()))) { String line; while ((line = br.readLine()) != null) { if (!line.startsWith("#")) buffer.append(line); } } @SuppressWarnings("unchecked") Map<String, Map> props = new ObjectMapper().readValue(buffer.toString(), Map.class); KafkaMonitor kafkaMonitor = new KafkaMonitor(props); kafkaMonitor.start(); LOG.info("KafkaMonitor started"); kafkaMonitor.awaitShutdown(); } }