package org.radargun.service; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import static org.radargun.traits.TopologyHistory.Event.EventType; public class Infinispan60ServerTopologyHistory extends AbstractTopologyHistory { protected final InfinispanServerService service; protected static final String ALL_CACHES = "__all_caches__"; protected static final String JMX_CACHE_COMPONENT = "%s:type=Cache,name=\"*\",manager=\"clustered\",component=%s"; // Topology change strings protected static final String JMX_RPC_MANAGER = "RpcManager"; protected static final String JMX_PENDING_VIEW_ATTR = "pendingViewAsString"; // Rehash strings protected static final String JMX_STATE_TRANSFER_MANAGER = "StateTransferManager"; protected static final String JMX_STATE_TRANSFER_IN_PROGRESS_ATTR = "stateTransferInProgress"; // Cache status protected static final String JMX_CACHE_IMPL = "Cache"; protected static final String JMX_CACHE_AVAILABILITY = "cacheAvailability"; protected final Map<String, CacheStatus> cacheChangesOngoing = new HashMap<>(); protected final Map<String, Map<ObjectName, String>> objectNameToCacheNames = new HashMap<>(); public Infinispan60ServerTopologyHistory(InfinispanServerService service) { this.service = service; service.schedule(new Runnable() { @Override public void run() { try { processCacheStatus(); } catch (Throwable e) { log.error("Checking cache status failed", e); } } }, service.viewCheckPeriod); service.lifecycle.addListener(new ProcessLifecycle.ListenerAdapter() { @Override public void afterStop(boolean graceful) { reset(); cacheChangesOngoing.clear(); objectNameToCacheNames.clear(); } }); } @Override protected String getDefaultCacheName() { return ALL_CACHES; } /** * Process the results from the map returned by {@link #cacheStatus()} and compare to the current * state. The map has a key for the cache name and a boolean value for rehash and topology * changes on a cache. If the new value for the cache doesn't match the current value, add an * event. If there is no current value then add it. */ protected void processCacheStatus() { Map<String, CacheStatus> newResult = cacheStatus(); processCacheAvailability(newResult); for (Map.Entry<String, CacheStatus> entry : newResult.entrySet()) { if (cacheChangesOngoing.containsKey(entry.getKey())) { if (entry.getValue().rehashInProgress && !cacheChangesOngoing.get(entry.getKey()).rehashInProgress) { addEvent(hashChanges, entry.getKey(), EventType.START, 0, 0); } if (!entry.getValue().rehashInProgress && cacheChangesOngoing.get(entry.getKey()).rehashInProgress) { addEvent(hashChanges, entry.getKey(), EventType.END, 0, 0); } if (entry.getValue().topologyChangeInProgress && !cacheChangesOngoing.get(entry.getKey()).topologyChangeInProgress) { addEvent(topologyChanges, entry.getKey(), EventType.START, 0, 0); } if (!entry.getValue().topologyChangeInProgress && cacheChangesOngoing.get(entry.getKey()).topologyChangeInProgress) { addEvent(topologyChanges, entry.getKey(), EventType.END, 0, 0); } // No cache availability change event start/end, just register an event with current timestamp if (entry.getValue().cacheAvailabilityChanged) { addEvent(cacheStatusChanges, entry.getKey(), EventType.SINGLE, 0, 0); } } else { if (entry.getValue().rehashInProgress) { addEvent(hashChanges, entry.getKey(), EventType.START, 0, 0); } if (entry.getValue().topologyChangeInProgress) { addEvent(topologyChanges, entry.getKey(), EventType.START, 0, 0); } if (entry.getValue().cacheAvailabilityChanged) { addEvent(cacheStatusChanges, entry.getKey(), EventType.SINGLE, 0, 0); } } cacheChangesOngoing.put(entry.getKey(), entry.getValue()); } } private void processCacheAvailability(Map<String, CacheStatus> statusMap) { // Not yet initialized, skip if (statusMap.size() == 0) { return; } int cacheAvailabilityChanges = 0; for (Map.Entry<String, CacheStatus> entry : statusMap.entrySet()) { CacheStatus status = entry.getValue(); // Comparison with previous cache availability is required CacheStatus oldStatus = cacheChangesOngoing.get(entry.getKey()); // init if (oldStatus == null) { oldStatus = status; } CacheAvailability cacheAvailability = entry.getValue().prevCacheAvailability; if (cacheAvailability != oldStatus.prevCacheAvailability) { status.cacheAvailabilityChanged = true; cacheAvailabilityChanges++; log.debugf("Availability status change for cache %s: %s -> %s", entry.getKey(), oldStatus.prevCacheAvailability, cacheAvailability); } } // Keep track of all cache availability changes CacheStatus status = statusMap.get(ALL_CACHES); if (cacheAvailabilityChanges == 0) { status.cacheAvailabilityChanged = false; } else { status.cacheAvailabilityChanged = true; } } /** * Uses JMX attributes to determine if a topology change or a rehash is occurring on a cache. * * The <code>pendingViewAsString</code> attribute of the RpcManager component of each defined * cache determines if a topology change is in progress on this cache. If * <code>pendingViewAsString</code> equals <code>null</code>, no topology change is in progress. * * The <code>stateTransferInProgress</code> attribute of the StateTransferManager component of * each defined cache determines if a rehash is in progress on this cache. If * <code>stateTransferInProgress</code> is <code>true</code>, a rehash is in progress. * * The <code>cacheAvailability</code> attribute of the CacheImpl component of each defined * cache determines whether cache resides in AVAILABLE or DEGRADED mode. This implementation * keeps only track of changes in cache availability (i.e. without start/stop events). * * @return a Map where the key is the cache name, and the value is a {@link CacheStatus} object. * Also includes an entry for {@link #ALL_CACHES}. */ protected Map<String, CacheStatus> cacheStatus() { Map<String, CacheStatus> statusMap = new HashMap<>(); int rehashesInProgress = 0; int topologyChangesInProgress = 0; try { MBeanServerConnection connection = service.connection; if (connection == null) return statusMap; // Check for rehash Map<String, Object> cacheAttributes = this.retrieveJMXAttributeValues(connection, String.format(JMX_CACHE_COMPONENT, service.jmxDomain, JMX_STATE_TRANSFER_MANAGER), JMX_STATE_TRANSFER_IN_PROGRESS_ATTR); for (Map.Entry<String, Object> entry : cacheAttributes.entrySet()) { CacheStatus status = new CacheStatus(); if ((Boolean) entry.getValue()) { log.debug("Rehash in progress on cache: " + entry.getKey()); status.rehashInProgress = true; rehashesInProgress++; } else { log.trace("No rehash in progress on cache: " + entry.getKey()); status.rehashInProgress = false; } statusMap.put(entry.getKey(), status); } // Keep track of all cache rehashes CacheStatus status = new CacheStatus(); if (rehashesInProgress == 0) { status.rehashInProgress = false; } else { status.rehashInProgress = true; } statusMap.put(ALL_CACHES, status); // Check for topology changes cacheAttributes = this.retrieveJMXAttributeValues(connection, String.format(JMX_CACHE_COMPONENT, service.jmxDomain, JMX_RPC_MANAGER), JMX_PENDING_VIEW_ATTR); for (Map.Entry<String, Object> entry : cacheAttributes.entrySet()) { status = statusMap.get(entry.getKey()); if (((String) entry.getValue()).equals("null")) { log.trace("No topology change in progress on cache: " + entry.getKey()); status.topologyChangeInProgress = false; } else { log.debug("Topology change in progress on cache: " + entry.getKey() + ". Pending view = " + entry.getValue()); status.topologyChangeInProgress = true; topologyChangesInProgress++; } statusMap.put(entry.getKey(), status); } // Keep track of all cache topology changes status = statusMap.get(ALL_CACHES); if (topologyChangesInProgress == 0) { status.topologyChangeInProgress = false; } else { status.topologyChangeInProgress = true; } statusMap.put(ALL_CACHES, status); // Check for cache availability changes. cacheAttributes = this.retrieveJMXAttributeValues(connection, String.format(JMX_CACHE_COMPONENT, service.jmxDomain, JMX_CACHE_IMPL), JMX_CACHE_AVAILABILITY); for (Map.Entry<String, Object> entry : cacheAttributes.entrySet()) { status = statusMap.get(entry.getKey()); if (status != null) { status.prevCacheAvailability = CacheAvailability.valueOf(entry.getValue().toString()); statusMap.put(entry.getKey(), status); } } } catch (Exception e) { log.error("Failed to retrieve data from JMX", e); } return statusMap; } /** * Retrieve the specified attribute from the specified manager for every cache defined * * @param connection * the connection to the MBeanServer * @param mbeanQueryString * A query string to return the ObjectName for each MBean associated with a cache * @param attrName * the name of the attribute * @return a Map where the key is the cache name, and the value is the specified attribute */ protected Map<String, Object> retrieveJMXAttributeValues(MBeanServerConnection connection, String mbeanQueryString, String attrName) { Map<String, Object> result = new HashMap<>(); if (!objectNameToCacheNames.containsKey(mbeanQueryString)) { try { Set<ObjectName> objectNames = connection.queryNames(new ObjectName(mbeanQueryString), null); HashMap<ObjectName, String> cacheNameMap = new HashMap<>(); for (ObjectName objectName : objectNames) { // Parse out cache name from ObjectName String fullCacheName = ObjectName.unquote(objectName.getKeyProperty("name")); String cacheName = fullCacheName.substring(0, fullCacheName.indexOf('(')); cacheNameMap.put(objectName, cacheName); } objectNameToCacheNames.put(mbeanQueryString, cacheNameMap); } catch (IOException e) { // Server isn't started yet? log.debug("Couldn't query " + mbeanQueryString + " object names.", e); } catch (MalformedObjectNameException e) { log.debug("ObjectName " + mbeanQueryString + " is malformed", e); } } Map<ObjectName, String> obj2CacheName = objectNameToCacheNames.get(mbeanQueryString); for (ObjectName objectName : obj2CacheName.keySet()) { try { result.put(obj2CacheName.get(objectName), connection.getAttribute(objectName, attrName)); } catch (Exception e) { log.debug("Failed to retrieve attribute: " + attrName + " for cache " + obj2CacheName.get(objectName), e); } } return result; } protected static class CacheStatus { boolean rehashInProgress; boolean topologyChangeInProgress; boolean cacheAvailabilityChanged; // Stores previous availability status CacheAvailability prevCacheAvailability; } protected static enum CacheAvailability { AVAILABLE, DEGRADED_MODE; } }