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

io.datakernel.jmx.JmxRegistry Maven / Gradle / Ivy

Go to download

An intelligent way of booting complex applications and services according to their dependencies

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright (C) 2015 SoftIndex LLC.
 *
 * 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 io.datakernel.jmx;

import com.google.inject.Key;
import io.datakernel.jmx.JmxMBeans.JmxCustomTypeAdapter;
import io.datakernel.util.ReflectionUtils;
import io.datakernel.worker.WorkerPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

import static io.datakernel.util.Preconditions.checkNotNull;
import static io.datakernel.util.StringFormatUtils.formatDuration;
import static io.datakernel.util.StringFormatUtils.parseDuration;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.joining;

public final class JmxRegistry implements JmxRegistryMXBean {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	private static final String GENERIC_PARAM_NAME_FORMAT = "T%d=%s";

	private final MBeanServer mbs;
	private final DynamicMBeanFactory mbeanFactory;
	private final Map, String> keyToObjectNames;
	private final Map> customTypes;

	// jmx
	private int registeredSingletons;
	private int registeredPools;
	private int totallyRegisteredMBeans;

	private JmxRegistry(MBeanServer mbs,
			DynamicMBeanFactory mbeanFactory,
			Map, String> keyToObjectNames,
			Map> customTypes) {
		this.mbs = checkNotNull(mbs);
		this.mbeanFactory = checkNotNull(mbeanFactory);
		this.keyToObjectNames = keyToObjectNames;
		this.customTypes = customTypes;
	}

	public static JmxRegistry create(MBeanServer mbs, DynamicMBeanFactory mbeanFactory) {
		return new JmxRegistry(mbs, mbeanFactory, Collections.emptyMap(), Collections.emptyMap());
	}

	public static JmxRegistry create(MBeanServer mbs,
			DynamicMBeanFactory mbeanFactory,
			Map, String> keyToObjectNames,
			Map> customTypes) {
		return new JmxRegistry(mbs, mbeanFactory, keyToObjectNames, customTypes);
	}

	public void registerSingleton(Key key, Object singletonInstance, MBeanSettings settings) {
		checkNotNull(singletonInstance);
		checkNotNull(key);

		Class instanceClass = singletonInstance.getClass();
		Object mbean;
		if (isJmxMBean(instanceClass)) {
			// this will throw exception if something happens during initialization
			mbean = mbeanFactory.createFor(singletonList(singletonInstance), settings, true);
		} else if (isStandardMBean(instanceClass) || isMXBean(instanceClass) || isDynamicMBean(instanceClass)) {
			mbean = singletonInstance;
		} else {
			logger.trace(format("Instance with key %s was not registered to jmx, " +
					"because its type does not implement ConcurrentJmxMBean, EventloopJmxMBean " +
					"and does not implement neither *MBean nor *MXBean interface", key.toString()));
			return;
		}

		String name;
		try {
			name = createNameForKey(key);
		} catch (ReflectiveOperationException e) {
			String msg = format("Error during generation name for instance with key %s", key.toString());
			logger.error(msg, e);
			return;
		}

		ObjectName objectName;
		try {
			objectName = new ObjectName(name);
		} catch (MalformedObjectNameException e) {
			String msg = format("Cannot create ObjectName for instance with key %s. " +
					"Proposed String name was \"%s\".", key.toString(), name);
			logger.error(msg, e);
			return;
		}

		try {
			mbs.registerMBean(mbean, objectName);
			logger.trace(format("Instance with key %s was successfully registered to jmx " +
					"with ObjectName \"%s\" ", key.toString(), objectName.toString()));

			registeredSingletons++;
			totallyRegisteredMBeans++;

		} catch (NotCompliantMBeanException | InstanceAlreadyExistsException | MBeanRegistrationException e) {
			String msg = format("Cannot register MBean for instance with key %s and ObjectName \"%s\"",
					key.toString(), objectName.toString());
			logger.error(msg, e);
		}
	}

	public void unregisterSingleton(Key key, Object singletonInstance) {
		checkNotNull(key);

		if (isMBean(singletonInstance.getClass())) {
			try {
				String name = createNameForKey(key);
				ObjectName objectName = new ObjectName(name);
				mbs.unregisterMBean(objectName);
			} catch (ReflectiveOperationException | JMException e) {
				String msg =
						format("Error during attempt to unregister MBean for instance with key %s.", key.toString());
				logger.error(msg, e);
			}
		}
	}

	public void registerWorkers(WorkerPool pool, Key key, List poolInstances, MBeanSettings settings) {
		checkNotNull(poolInstances);
		checkNotNull(key);

		if (poolInstances.size() == 0) {
			logger.info(format("Pool of instances with key %s is empty", key.toString()));
			return;
		}

		if (!allInstancesAreOfSameType(poolInstances)) {
			logger.info(format("Pool of instances with key %s was not registered to jmx because their types differ", key.toString()));
			return;
		}

		if (!isJmxMBean(poolInstances.get(0).getClass())) {
			logger.info(format("Pool of instances with key %s was not registered to jmx, " +
					"because instances' type implements neither ConcurrentJmxMBean " +
					"nor EventloopJmxMBean interface", key.toString()));
			return;
		}

		String commonName;
		try {
			commonName = createNameForKey(key, pool);
		} catch (Exception e) {
			String msg = format("Error during generation name for pool of instances with key %s", key.toString());
			logger.error(msg, e);
			return;
		}

		// register mbeans for each worker separately
		for (int i = 0; i < poolInstances.size(); i++) {
			MBeanSettings settingsForOptionals = MBeanSettings.of(
					settings.getIncludedOptionals(), new HashMap<>(), customTypes);
			registerMBeanForWorker(poolInstances.get(i), i, commonName, key, settingsForOptionals);
		}

		// register aggregated mbean for pool of workers
		DynamicMBean mbean;
		try {
			mbean = mbeanFactory.createFor(poolInstances, settings, true);
		} catch (Exception e) {
			String msg = format("Cannot create DynamicMBean for aggregated MBean of pool of workers with key %s",
					key.toString());
			logger.error(msg, e);
			return;
		}

		ObjectName objectName;
		try {
			objectName = new ObjectName(commonName);
		} catch (MalformedObjectNameException e) {
			String msg = format("Cannot create ObjectName for aggregated MBean of pool of workers with key %s. " +
					"Proposed String name was \"%s\".", key.toString(), commonName);
			logger.error(msg, e);
			return;
		}

		try {
			mbs.registerMBean(mbean, objectName);
			logger.trace(format("Pool of instances with key %s was successfully registered to jmx " +
					"with ObjectName \"%s\"", key.toString(), objectName.toString()));

			registeredPools++;
			totallyRegisteredMBeans++;

		} catch (NotCompliantMBeanException | InstanceAlreadyExistsException | MBeanRegistrationException e) {
			String msg = format("Cannot register aggregated MBean of pool of workers with key %s " +
					"and ObjectName \"%s\"", key.toString(), objectName.toString());
			logger.error(msg, e);
		}
	}

	public void unregisterWorkers(WorkerPool pool, Key key, List poolInstances) {
		checkNotNull(key);

		if (poolInstances.size() == 0) {
			return;
		}

		if (!allInstancesAreOfSameType(poolInstances)) {
			return;
		}

		if (!isJmxMBean(poolInstances.get(0).getClass())) {
			return;
		}

		String commonName;
		try {
			commonName = createNameForKey(key, pool);
		} catch (ReflectiveOperationException e) {
			String msg = format("Error during generation name for pool of instances with key %s", key.toString());
			logger.error(msg, e);
			return;
		}

		// unregister mbeans for each worker separately
		for (int i = 0; i < poolInstances.size(); i++) {
			try {
				String workerName = createWorkerName(commonName, i);
				mbs.unregisterMBean(new ObjectName(workerName));
			} catch (JMException e) {
				String msg = format("Error during attempt to unregister mbean for worker" +
								" of pool of instances with key %s. Worker id is \"%d\"",
						key.toString(), i);
				logger.error(msg, e);
			}
		}

		// unregister aggregated mbean for pool of workers
		try {
			mbs.unregisterMBean(new ObjectName(commonName));
		} catch (JMException e) {
			String msg = format("Error during attempt to unregister aggregated mbean for pool of instances " +
					"with key %s.", key.toString());
			logger.error(msg, e);
		}
	}

	private boolean allInstancesAreOfSameType(List instances) {
		int last = instances.size() - 1;
		for (int i = 0; i < last; i++) {
			if (!instances.get(i).getClass().equals(instances.get(i + 1).getClass())) {
				return false;
			}
		}
		return true;
	}

	private void registerMBeanForWorker(Object worker, int workerId, String commonName,
			Key key, MBeanSettings settings) {
		String workerName = createWorkerName(commonName, workerId);

		DynamicMBean mbean;
		try {
			mbean = mbeanFactory.createFor(singletonList(worker), settings, false);
		} catch (Exception e) {
			String msg = format("Cannot create DynamicMBean for worker " +
					"of pool of instances with key %s", key.toString());
			logger.error(msg, e);
			return;
		}

		ObjectName objectName;
		try {
			objectName = new ObjectName(workerName);
		} catch (MalformedObjectNameException e) {
			String msg = format("Cannot create ObjectName for worker of pool of instances with key %s. " +
					"Proposed String name was \"%s\".", key.toString(), workerName);
			logger.error(msg, e);
			return;
		}

		try {
			mbs.registerMBean(mbean, objectName);

			totallyRegisteredMBeans++;

		} catch (NotCompliantMBeanException | InstanceAlreadyExistsException | MBeanRegistrationException e) {
			String msg = format("Cannot register MBean for worker of pool of instances with key %s. " +
					"ObjectName for worker is \"%s\"", key.toString(), objectName.toString());
			logger.error(msg, e);
		}
	}

	private static String createWorkerName(String commonName, int workerId) {
		return commonName + format(",workerId=worker-%d", workerId);
	}

	private String createNameForKey(Key key) throws ReflectiveOperationException {
		return createNameForKey(key, null);
	}

	private String createNameForKey(Key key, WorkerPool pool) throws ReflectiveOperationException {
		if (keyToObjectNames.containsKey(key)) {
			return keyToObjectNames.get(key);
		}
		Class rawType = key.getTypeLiteral().getRawType();
		Annotation annotation = key.getAnnotation();
		String domain = rawType.getPackage().getName();
		String name = domain + ":" + "type=" + rawType.getSimpleName();

		if (annotation != null) { // with annotation
			name += ',';
			String annotationString = ReflectionUtils.getAnnotationString(annotation);
			if (!annotationString.contains("(")) {
				name += "annotation=" + annotationString;
			} else if (!annotationString.startsWith("(")) {
				name += annotationString.substring(0, annotationString.indexOf('('));
				name += '=' + annotationString.substring(annotationString.indexOf('(') + 1, annotationString.length() - 1);
			} else {
				name += annotationString.substring(1, annotationString.length() - 1);
			}
		}
		if (pool != null && !pool.getAnnotationString().equals("")) {
			name += format(",workerPool=%s", pool.toString());
		}
		return addGenericParamsInfo(name, key);
	}

	private static String addGenericParamsInfo(String srcName, Key key) {
		Type type = key.getTypeLiteral().getType();
		StringBuilder resultName = new StringBuilder(srcName);
		if (type instanceof ParameterizedType) {
			ParameterizedType pType = (ParameterizedType) type;
			Type[] genericArgs = pType.getActualTypeArguments();
			for (int i = 0; i < genericArgs.length; i++) {
				Type genericArg = genericArgs[i];
				String argClassName = formatSimpleGenericName(genericArg);
				int argId = i + 1;
				resultName.append(",").append(format(GENERIC_PARAM_NAME_FORMAT, argId, argClassName));
			}
		}
		return resultName.toString();
	}

	private static String formatSimpleGenericName(Type type) {
		if (type instanceof Class) {
			return ((Class) type).getSimpleName();
		}
		ParameterizedType genericType = (ParameterizedType) type;
		return ((Class) genericType.getRawType()).getSimpleName() +
				Arrays.stream(genericType.getActualTypeArguments())
						.map(JmxRegistry::formatSimpleGenericName)
						.collect(joining(";", "<", ">"));
	}

	private static boolean isStandardMBean(Class clazz) {
		return classImplementsInterfaceWithNameEndingWith(clazz, "MBean");
	}

	private static boolean isMXBean(Class clazz) {
		return classFollowsMXBeanConvention(clazz);
	}

	private static boolean classImplementsInterfaceWithNameEndingWith(Class clazz, String ending) {
		String clazzName = clazz.getSimpleName();
		Class[] interfaces = clazz.getInterfaces();
		for (Class anInterface : interfaces) {
			String interfaceName = anInterface.getSimpleName();
			if (interfaceName.equals(clazzName + ending)) {
				return true;
			}
		}
		return false;
	}

	private static boolean classFollowsMXBeanConvention(Class clazz) {
		Class[] interfazes = clazz.getInterfaces();
		for (Class interfaze : interfazes) {
			if (interfaceFollowsMXBeanConvention(interfaze)) {
				return true;
			}
		}

		Class superClazz = clazz.getSuperclass();
		if (superClazz != null) {
			return classFollowsMXBeanConvention(superClazz);
		}

		return false;
	}

	private static boolean interfaceFollowsMXBeanConvention(Class interfaze) {
		if (interfaze.getSimpleName().endsWith("MXBean") || interfaze.isAnnotationPresent(MXBean.class)) {
			return true;
		}

		Class[] subInterfazes = interfaze.getInterfaces();
		for (Class subInterfaze : subInterfazes) {
			if (interfaceFollowsMXBeanConvention(subInterfaze)) {
				return true;
			}
		}

		return false;
	}

	private static boolean isJmxMBean(Class clazz) {
		return ConcurrentJmxMBean.class.isAssignableFrom(clazz) || EventloopJmxMBean.class.isAssignableFrom(clazz);
	}

	private static boolean isDynamicMBean(Class clazz) {
		return DynamicMBean.class.isAssignableFrom(clazz);
	}

	private static boolean isMBean(Class clazz) {
		return isJmxMBean(clazz) || isStandardMBean(clazz) || isMXBean(clazz) || isDynamicMBean(clazz);
	}

	// region jmx
	@Override
	public int getRegisteredSingletons() {
		return registeredSingletons;
	}

	@Override
	public int getRegisteredPools() {
		return registeredPools;
	}

	@Override
	public int getTotallyRegisteredMBeans() {
		return totallyRegisteredMBeans;
	}

	@Override
	public String getRefreshPeriod() {
		return formatDuration(((JmxMBeans) mbeanFactory).getSpecifiedRefreshPeriod());
	}

	@Override
	public void setRefreshPeriod(String refreshPeriod) {
		((JmxMBeans) mbeanFactory).setRefreshPeriod(parseDuration(refreshPeriod));
	}

	@Override
	public int getMaxRefreshesPerOneCycle() {
		return ((JmxMBeans) mbeanFactory).getMaxJmxRefreshesPerOneCycle();
	}

	@Override
	public void setMaxRefreshesPerOneCycle(int maxRefreshesPerOneCycle) {
		((JmxMBeans) mbeanFactory).setMaxJmxRefreshesPerOneCycle(maxRefreshesPerOneCycle);
	}

	@Override
	public double[] getEffectiveRefreshPeriods() {
		List effectivePeriods =
				new ArrayList<>(((JmxMBeans) mbeanFactory).getEffectiveRefreshPeriods().values());
		double[] effectivePeriodsSeconds = new double[effectivePeriods.size()];
		for (int i = 0; i < effectivePeriods.size(); i++) {
			int periodMillis = effectivePeriods.get(i);
			effectivePeriodsSeconds[i] = periodMillis / (double) 1000;

		}
		return effectivePeriodsSeconds;
	}

	@Override
	public int[] getRefreshableStatsCount() {
		List counts = new ArrayList<>(((JmxMBeans) mbeanFactory).getRefreshableStatsCounts().values());
		int[] refreshableStatsCountsArr = new int[counts.size()];
		for (int i = 0; i < counts.size(); i++) {
			Integer count = counts.get(i);
			refreshableStatsCountsArr[i] = count;
		}
		return refreshableStatsCountsArr;
	}
	// endregion
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy