package io.pcp.parfait;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
/**
* A collection of Monitorables to be monitored by a given output source (or
* sources). Each {@link Monitorable} is associated with a particular
* MonitorableRegistry, and informs the registry of its own details via a
* callback to {@link #register(Monitorable)} when the Monitorable is created.
* the MonitorableRegistryListener interface can be used for clients interested in state changes
* to be made aware of when new Monitorables are added, such as objects that wish
* to serialize state to external stores. (ie... A PCPmmvWriter.. say..)
*/
public class MonitorableRegistry {
private static final ConcurrentMap<String, MonitorableRegistry> NAMED_INSTANCES = new ConcurrentHashMap<String, MonitorableRegistry>();
/**
* A single central registry which can be used by non-Registry-aware
* Monitorables. This is very limiting in terms of system flexibility and an
* explicit {@link MonitorableRegistry} should be used instead.
*/
public static MonitorableRegistry DEFAULT_REGISTRY = new MonitorableRegistry();
/**
* This is a TreeMap so that the Monitorables are maintained in alphabetical
* order for convenience.
*/
private final Map<String, Monitorable<?>> monitorables = new TreeMap<String, Monitorable<?>>();
private final List<MonitorableRegistryListener> registryListeners = new CopyOnWriteArrayList<MonitorableRegistryListener>();
/**
* Informs this MonitorableRegistry of a new {@link Monitorable}; that
* Monitorable will be added to the registry, assuming no Monitorable with
* the same name has previously been registered.
*
* @throws UnsupportedOperationException
* if the name of the provided monitorable has already been
* registered
*/
public synchronized <T> void register(Monitorable<T> monitorable) {
if (monitorables.containsKey(monitorable.getName())) {
throw new UnsupportedOperationException(
"There is already an instance of the Monitorable [" + monitorable.getName()
+ "] registered.");
}
monitorables.put(monitorable.getName(), monitorable);
notifyListenersOfNewMonitorable(monitorable);
}
/**
* Registers the monitorable if it does not already exist, but otherwise returns an already registered
* Monitorable with the same name, Semantics and UNnit definition. This method is useful when objects
* appear and disappear, and then return, and the lifecycle of the application requires an attempt to recreate
* the Monitorable without knowing if it has already been created.
*
* If there exists a Monitorable with the same name, but with different Semantics or Unit then an IllegalArgumentException
* is thrown.
*
*/
@SuppressWarnings("unchecked")
public synchronized <T> T registerOrReuse(Monitorable<T> monitorable) {
String name = monitorable.getName();
if (monitorables.containsKey(name)) {
Monitorable<?> existingMonitorableWithSameName = monitorables.get(name);
if (monitorable.getSemantics().equals(existingMonitorableWithSameName.getSemantics()) && monitorable.getUnit().equals(existingMonitorableWithSameName.getUnit())) {
return (T) existingMonitorableWithSameName;
} else {
throw new IllegalArgumentException(String.format("Cannot reuse the same name %s for a monitorable with different Semantics or Unit: requested=%s, existing=%s", name, monitorable, existingMonitorableWithSameName));
}
} else {
monitorables.put(name, monitorable);
notifyListenersOfNewMonitorable(monitorable);
return (T) monitorable;
}
}
private void notifyListenersOfNewMonitorable(Monitorable<?> monitorable) {
for (MonitorableRegistryListener listener : registryListeners) {
listener.monitorableAdded(monitorable);
}
}
/**
* @return a list of all Monitorables which are registered with this
* MonitorableRegistry.
*/
public synchronized Collection<Monitorable<?>> getMonitorables() {
return ImmutableList.copyOf(monitorables.values());
}
/*
* Testing only -- should be eliminated once the default registry is gone
*/
public static void clearDefaultRegistry() {
DEFAULT_REGISTRY = new MonitorableRegistry();
}
/**
* Retrieves or creates a centrally-accessible named instance, identified
* uniquely by the provided String. This is a convenience method to bridge
* between the old-style 'single registry' model (see
* {@link #DEFAULT_REGISTRY}) and having to pass a MonitorableRegistry down
* to the very depths of your class hierarchy. This is especially useful
* when instrumenting third-party code which cannot easily get access to a
* given MonitorableRegistry from a non-static context.
*
* @param name
* @return
*/
public static MonitorableRegistry getNamedInstance(String name) {
MonitorableRegistry instance = NAMED_INSTANCES.get(name);
if (instance == null) {
instance = new MonitorableRegistry();
MonitorableRegistry existing = NAMED_INSTANCES.putIfAbsent(name, instance);
if (existing != null) {
return existing;
}
}
return instance;
}
public void addRegistryListener(MonitorableRegistryListener monitorableRegistryListener) {
this.registryListeners.add(monitorableRegistryListener);
}
public void removeRegistryListener(MonitorableRegistryListener listener) {
this.registryListeners.remove(listener);
}
@VisibleForTesting
boolean containsMetric(String name) {
return monitorables.containsKey(name);
}
@VisibleForTesting
Monitorable<?> getMetric(String name) {
return monitorables.get(name);
}
}