All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kairosdb.metrics4j.configuration.MetricConfig Maven / Gradle / Ivy

There is a newer version: 1.0.2
Show newest version
package org.kairosdb.metrics4j.configuration;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigRenderOptions;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
import org.kairosdb.metrics4j.MetricsContext;
import org.kairosdb.metrics4j.internal.ArgKey;
import org.kairosdb.metrics4j.internal.BeanInjector;
import org.kairosdb.metrics4j.internal.MetricsContextImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.IntrospectionException;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MetricConfig
{
	public static final String CLASS_PROPERTY = "_class";
	public static final String FOLDER_PROPERTY = "_folder";
	public static final String DUMP_FILE = "_dump-file";
	public static final String METRIC_NAME = "_metric-name";


	private static Logger log = LoggerFactory.getLogger(MetricConfig.class);

	private static final Pattern formatPattern = Pattern.compile("\\%\\{([^\\}]*)\\}");

	private Properties m_properties = new Properties();

	private final Map, Map> m_mappedTags;
	private final Map, Map> m_mappedProps;
	private final Map, String> m_mappedMetricNames;
	private final Map, Boolean> m_disabledPaths;

	private final MetricsContextImpl m_context;
	private final List m_closeables;

	private boolean m_shutdownOverride = false;
	private boolean m_dumpMetrics = false;
	private String m_dumpFile;
	private Map m_dumpConfig;



	private String formatValue(String value)
	{
		Matcher matcher = formatPattern.matcher(value);
		StringBuilder sb = new StringBuilder();

		int endLastMatch = 0;
		while (matcher.find())
		{
			int start = matcher.start();
			int end = matcher.end();

			if (start != endLastMatch)
			{
				sb.append(value, endLastMatch, start);
			}

			String token = matcher.group(1);

			//todo look for values from properties file and from env
			sb.append(m_properties.getProperty(token, "%{"+token+"}"));

			endLastMatch = end;
		}

		sb.append(value.substring(endLastMatch));

		return sb.toString();
	}


	private  T loadClass(Config config, String objName)
	{
		T ret = null;
		String className = config.getString(CLASS_PROPERTY);

		try
		{
			ClassLoader pluginLoader = MetricConfig.class.getClassLoader();

			if (config.hasPath(FOLDER_PROPERTY))
			{
				String pluginFolder = config.getString(FOLDER_PROPERTY);
				pluginLoader = new PluginClassLoader(getJarsInPath(pluginFolder), pluginLoader);
			}

			Class pluginClass = pluginLoader.loadClass(className);

			BeanInjector beanInjector = new BeanInjector(objName, pluginClass);

			ret = (T) beanInjector.createInstance(config);
		}
		catch (ClassNotFoundException | MalformedURLException | IntrospectionException e)
		{
			throw new ConfigurationException("Unable to load plugin '"+objName+"' '"+className+"' for configuration element '"+config.origin().lineNumber()+"'");
		}

		return ret;
	}

	private static URL[] getJarsInPath(String path) throws MalformedURLException
	{
		List jars = new ArrayList<>();
		File libDir = new File(path);
		File[] fileList = libDir.listFiles();
		if(fileList != null)
		{
			for (File f : fileList)
			{
				if (f.getName().endsWith(".jar"))
				{
					jars.add(f.toURI().toURL());
				}
			}
		}

		//System.out.println(jars);
		return jars.toArray(new URL[0]);
	}

	private  void registerStuff(Config configs, BiConsumer register)
	{
		//Get entry keys
		Set keys = new HashSet<>();
		for (Map.Entry config : configs.entrySet())
		{
			keys.add(config.getKey().split("\\.")[0]);
		}

		for (String name : keys)
		{

			T classInstance = loadClass(configs.getConfig(name), name);

			register.accept(name, classInstance);

			if (classInstance instanceof Closeable)
			{
				m_closeables.add((Closeable)classInstance);
			}
		}
	}

	/*package*/ static List appendSourceName(List parent, String child)
	{
		List copy = new ArrayList<>(parent);

		String[] splitNames = child.split("\\.");

		copy.addAll(Arrays.asList(splitNames));
		return copy;
	}

	private String combinePath(String[] path, int limit)
	{
		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < limit; i++)
		{
			sb.append(path[i]).append(".");
		}

		sb.append(path[limit]);

		return sb.toString();
	}

	private List createList(String[] arr, int limit)
	{
		List ret = new ArrayList<>();
		for (int i = 0; i <= limit; i++)
		{
			ret.add(arr[i]);
		}

		return ret;
	}

	/**
	 Recursively parse through the sources elements
	 @param root
	 */
	private void parseSources(Config root)
	{
		if (root == null)
			throw new ConfigurationException("No 'sources' element in your configuration");

		Set> entries = root.entrySet();
		for (Map.Entry entry : entries)
		{
			String[] path = entry.getKey().split("\\.");

			for (int i = (path.length -1); i >= 0 ; i--)
			{
				if (path[i].startsWith("_"))
				{
					String internalProp = path[i];
					if (internalProp.equals(METRIC_NAME))
					{
						String combinedPath = combinePath(path, i);

						String metricName = root.getString(combinedPath);

						if (!metricName.isEmpty())
							m_mappedMetricNames.put(createList(path, i-1), metricName);
					}
					else if (internalProp.equals("_sink"))
					{
						//sink can be either a single string or a list of string
						String sinkPath = combinePath(path, i);

						ConfigValueType sinkValueType = root.getValue(sinkPath).valueType();
						if (sinkValueType == ConfigValueType.STRING)
						{
							String ref = root.getString(sinkPath);
							m_context.addSinkToPath(ref, createList(path, i-1));
						}
						else if (sinkValueType == ConfigValueType.LIST)
						{
							List sinkList = root.getStringList(sinkPath);
							for (String sink : sinkList)
							{
								m_context.addSinkToPath(sink, createList(path, i-1));
							}
						}

					}
					else if (internalProp.equals("_collector"))
					{
						//collector can be either a single string or a list of string
						String collectorPath = combinePath(path, i);

						ConfigValueType collectorValueType = root.getValue(collectorPath).valueType();
						if (collectorValueType == ConfigValueType.STRING)
						{
							String ref = root.getString(collectorPath);
							m_context.addCollectorToPath(ref, createList(path, i-1));
						}
						else if (collectorValueType == ConfigValueType.LIST)
						{
							List collectorList = root.getStringList(collectorPath);
							for (String collector : collectorList)
							{
								m_context.addCollectorToPath(collector, createList(path, i-1));
							}
						}
					}
					else if (internalProp.equals("_formatter"))
					{
						String ref = root.getString(combinePath(path, i));
						m_context.addFormatterToPath(ref, createList(path, i-1));
					}
					else if (internalProp.equals("_trigger"))
					{
						String ref = root.getString(combinePath(path, i));
						m_context.addTriggerToPath(ref, createList(path, i-1));
					}
					else if (internalProp.equals("_tags"))
					{
						String key = path[i+1];
						String value = (String) entry.getValue().unwrapped();

						List pathList = createList(path, i - 1);
						Map pathTags = m_mappedTags.computeIfAbsent(pathList, (k) -> new HashMap<>());
						pathTags.put(key, value);
					}
					else if (internalProp.equals("_prop"))
					{
						String key = path[i+1];
						String value = (String) entry.getValue().unwrapped();

						List pathList = createList(path, i - 1);
						Map pathProps = m_mappedProps.computeIfAbsent(pathList, (k) -> new HashMap<>());
						pathProps.put(key, value);
					}
					else if (internalProp.equals("_disabled"))
					{
						Boolean value = (Boolean)entry.getValue().unwrapped();
						m_disabledPaths.put(createList(path, i - 1), value);
					}
					else
					{
						throw new ConfigurationException("Unknown configuration element: " + internalProp);
					}
				}
			}
		}
	}

	private static void registerIfNotNull(Config config, String path, Consumer register)
	{
		if (config.hasPath(path))
			register.accept(config.getConfig(path));
	}

	/**
	 *
	 * @param baseConfig
	 * @param overridesConfig
	 * @return
	 */
	public static MetricConfig parseConfig(String baseConfig, String overridesConfig)
	{
		//todo break up this method so it can be built in parts by unit tests
		MetricsContextImpl context = new MetricsContextImpl();
		MetricConfig ret = new MetricConfig(context);

		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
		if (contextClassLoader.getResource(baseConfig) == null)
			log.info("Unable to locate "+baseConfig+" are you sure it is in the classpath?");

		Config base = ConfigFactory.parseResources(baseConfig);
		Config overrides = ConfigFactory.parseResources(overridesConfig);

		Config config = overrides.withFallback(base).resolve();

		if (config.hasPath("metrics4j"))
		{
			Config metrics4j = config.getConfig("metrics4j");

			//Parse out the sinks
			registerIfNotNull(config, "metrics4j.sinks", (sinks) -> ret.registerStuff(sinks, context::registerSink));
			registerIfNotNull(config, "metrics4j.collectors", (collectors) -> ret.registerStuff(collectors, context::registerCollector));
			registerIfNotNull(config, "metrics4j.formatters", (formatters) -> ret.registerStuff(formatters, context::registerFormatter));
			registerIfNotNull(config, "metrics4j.triggers", (triggers) -> ret.registerStuff(triggers, context::registerTrigger));

			if (metrics4j.hasPath(DUMP_FILE))
			{
				ret.m_dumpFile = metrics4j.getString(DUMP_FILE);

				ret.m_dumpMetrics = true;
				ret.m_dumpConfig = new HashMap<>();
			}

			registerIfNotNull(config, "metrics4j.sources", (sources) -> ret.parseSources(sources));
		}

		return ret;
	}


	/*package*/
	public MetricConfig(MetricsContextImpl context)
	{
		m_context = context;
		m_closeables = new ArrayList<>();
		m_mappedTags = new HashMap<>();
		m_mappedProps = new HashMap<>();
		m_mappedMetricNames = new HashMap<>();
		m_disabledPaths = new HashMap<>();


		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				if (!m_shutdownOverride)
					shutdown();
			}
		}));
	}

	private void shutdown()
	{
		log.debug("Shutdown called for Metrics4j");
		for (Closeable closeable : m_closeables)
		{
			try
			{
				closeable.close();
			}
			catch (Exception e)
			{
				log.error("Error closing "+closeable.getClass().getName(), e);
			}
		}

		if (m_dumpFile != null)
		{
			log.debug("Writing dump file {}", m_dumpFile);
			try
			{
				Map metrics4j = getAdd(m_dumpConfig, "metrics4j");

				//make sure root level configs are there.
				getAdd(metrics4j, "sources");
				getAdd(metrics4j, "sinks");
				getAdd(metrics4j, "collectors");
				getAdd(metrics4j, "formatters");
				getAdd(metrics4j, "triggers");

				String dumpConfigStr = ConfigFactory.parseMap(m_dumpConfig).root()
						.render(ConfigRenderOptions.defaults().setOriginComments(false).setJson(false));
				FileWriter out = new FileWriter(m_dumpFile);
				out.write(dumpConfigStr);
				out.flush();
				out.close();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}

	public ShutdownHookOverride getShutdownHookOverride()
	{
		m_shutdownOverride = true;
		return MetricConfig.this::shutdown;
	}


	public String getMetricNameForKey(ArgKey key)
	{
		return m_mappedMetricNames.get(key.getConfigPath());
	}


	public MetricsContext getContext()
	{
		return m_context;
	}

	public void setProperties(Properties properties)
	{
		m_properties = properties;
	}

	public boolean isDisabled(ArgKey argKey)
	{
		List configPath = argKey.getConfigPath();
		for (int i = configPath.size(); i >= 0; i--)
		{
			List searchPath = new ArrayList<>(configPath.subList(0, i));

			Boolean disabled = m_disabledPaths.get(searchPath);
			if (disabled != null)
			{
				return disabled;
			}
		}

		return false;
	}

	/**
	 Returns a map of tags that you can modify
	 @param argKey
	 @return
	 */
	public Map getTagsForKey(ArgKey argKey)
	{
		return getValuesForKey(argKey, m_mappedTags);
	}

	private Map getValuesForKey(ArgKey argKey, Map, Map> mappedTags)
	{
		Map ret = new HashMap<>();
		List configPath = argKey.getConfigPath();
		for (int i = configPath.size(); i >= 0; i--)
		{
			List searchPath = new ArrayList<>(configPath.subList(0, i));
			Map pathTags = mappedTags.getOrDefault(searchPath, new HashMap<>());

			for (String key : pathTags.keySet())
			{
				ret.putIfAbsent(formatValue(key), formatValue(pathTags.get(key)));
			}
		}

		return ret;
	}

	public Map getPropsForKey(ArgKey argKey)
	{
		return getValuesForKey(argKey, m_mappedProps);
	}

	public boolean isDumpMetrics()
	{
		return m_dumpMetrics;
	}

	private Map getAdd(Map root, String segment)
	{
		return (Map) root.computeIfAbsent(segment, s -> new HashMap());
	}

	/**
	 Adds a source that will be dumped out on shutdown.@param src
	 @param helpText

	 */
	public void addDumpSource(String src, String helpText)
	{
		Map sources = getAdd(getAdd(m_dumpConfig, "metrics4j"), "sources");

		String[] split = src.split("\\.");

		for (int i = 0; i < split.length; i++)
		{
			sources = getAdd(sources, split[i]);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy