/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.server.tracing.virtual;
import static org.helios.apmrouter.server.tracing.virtual.VirtualState.INIT;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.ObjectName;
import org.apache.log4j.Logger;
import org.helios.apmrouter.jmx.JMXHelper;
import org.helios.apmrouter.ref.RunnableReferenceQueue;
/**
* <p>Title: VirtualAgent</p>
* <p>Description: Represents a notional agent for which no agent actually exists but which is logically
* represented by apm-collectors using {@link VirtualTracer}s tagged to this agent.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.server.tracing.VirtualAgent</code></p>
*/
public class VirtualAgent implements VirtualAgentMXBean, Runnable, Iterable<VirtualTracer> {
/** The virtual tracer's state */
private final AtomicReference<VirtualState> agentState = new AtomicReference<VirtualState>(INIT);
/** A map of virtual tracerExpiryQueue keyed by the tracer name */
protected final Map<String, WeakReference<VirtualTracer>> tracers = new ConcurrentHashMap<String, WeakReference<VirtualTracer>>();
/** The agent's host */
protected final String host;
/** The agent's name */
protected final String agent;
/** The agent's local URI */
protected final String localURI;
/** The JMX ObjectName for this virtual agent */
protected final ObjectName objectName;
/** A reference to the Virtual Agent Manager */
protected final VirtualAgentManager vaManager;
/** Instance logger */
protected final Logger log;
/** A reference to the ref cleaner */
protected final RunnableReferenceQueue rrq = RunnableReferenceQueue.getInstance();
/**
* Creates a new VirtualAgent
* @param host The agent's host
* @param agent The agent name
* @param localURI The designated local URI for this virtual agent
* @param vaManager A reference to the Virtual Agent Manager
*/
VirtualAgent(String host, String agent, String localURI, VirtualAgentManager vaManager) {
this.host = host;
this.agent = agent;
this.localURI = localURI;
this.vaManager = vaManager;
log = Logger.getLogger(String.format("%s.%s:%s", getClass().getName() ,host, agent));
objectName = JMXHelper.objectName(String.format(VA_OBJ_NAME, host, agent));
registerJmx();
this.vaManager.sendAgentStateChangeNotification(this, VirtualState.INIT, null);
}
/**
* Registers this tracer's management MBean
*/
void registerJmx() {
if(JMXHelper.getHeliosMBeanServer().isRegistered(objectName)) {
JMXHelper.unregisterMBean(objectName);
}
JMXHelper.registerMBean(this, objectName);
}
/**
* Unegisters this tracer's management MBean
*/
void unregisterJmx() {
if(JMXHelper.getHeliosMBeanServer().isRegistered(objectName)) {
try { JMXHelper.unregisterMBean(objectName); } catch (Exception ex) {}
}
}
/**
* Returns a set of all the tracers in this agent
* @return a set of all the tracers in this agent
*/
protected Set<VirtualTracer> getAllTracers() {
Set<VirtualTracer> allTracers = new HashSet<VirtualTracer>(tracers.size());
for(String tracerName: tracers.keySet()) {
VirtualTracer vt = getTracer(tracerName);
if(vt!=null) allTracers.add(vt);
}
return allTracers;
}
/**
* Inserts the passed tracer into the tracer map
* @param vt the tracer to add
*/
protected void putTracer(final VirtualTracer vt) {
if(vt==null) throw new IllegalArgumentException("The passed virtual tracer was null", new Throwable());
final String vtName = vt.getName();
if(!tracers.containsKey(vtName)) {
synchronized(tracers) {
if(!tracers.containsKey(vtName)) {
Runnable r = new Runnable() {
final String id = getHost() + "/" + getAgent() + ":" + vtName;
@Override
public void run() {
log.info("Virtual Tracer [" + id + "] was evicted");
tracers.remove(vtName);
}
};
tracers.put(vt.getName(), rrq.buildWeakReference(vt, r));
}
}
}
}
/**
* Retrieves a tracer from the tracer map
* @param name The name of the tracer
* @return the tracer
*/
protected VirtualTracer getTracer(String name) {
WeakReference<VirtualTracer> ref = tracers.get(name);
if(ref==null) {
return null;
}
return ref.get();
}
/**
* Transitions the state of this agent
* @param state The state to transition to
* @return the prior state
*/
protected VirtualState setState(final VirtualState state) {
if(state==null) throw new IllegalArgumentException("The passed state was null", new Throwable());
VirtualState priorState = agentState.getAndSet(state);
if(priorState!=state) {
if(priorState!=state) {
// ================================================================
// Case statement for non-timestamp or state actions to fire
// on a valid state change
// ================================================================
switch(state) {
case HARDDOWN:
unregisterJmx();
break;
case SOFTDOWN:
break;
case UP:
registerJmx();
break;
}
this.vaManager.sendAgentStateChangeNotification(this, state, priorState);
}
}
return priorState;
}
/**
* Callback from a virtual tracer when it changes state
* @param tracerName The name of the tracer that changed state
* @param state The state the tracer transitioned to
* @param priorState The prior state of the tracer
*/
void onTracerStateChange(VirtualTracer tracer, VirtualState state, VirtualState priorState) {
if(state==VirtualState.UP && priorState!=state) {
putTracer(tracer);
}
vaManager.sendTracerStateChangeNotification(this, tracer.getName(), state, priorState);
}
/**
* Returns the named virtual tracer
* @param name The name of the virtual tracer to retrieve
* @return the named virtual tracer
*/
public VirtualTracer getVirtualTracer(String name) {
return getTracer(name);
}
/**
* Returns the named virtual tracer
* @param name The name of the virtual tracer to retrieve
* @param timeout The timeout for this tracer in ms.
* @return the named virtual tracer
* @FIXME
*/
public VirtualTracer getVirtualTracer(String name, long timeout) {
return getTracer(name);
}
/**
* Returns the reference key for this virtual agent
* @return the reference key
*/
public String getKey() {
return String.format("%s:%s", host, agent);
}
/**
* Adds a virtual tracer to this virtual agent
* @param vt The virtual tracer to add
*/
public void addVirtualTracer(VirtualTracer vt) {
if(vt!=null) {
if(!host.equals(vt.getHost())) throw new IllegalArgumentException("The virtual tracer for host [" + vt.getHost() + "] does not belong in this agent for host [" + host + "]", new Throwable());
if(!agent.equals(vt.getAgent())) throw new IllegalArgumentException("The virtual tracer for agent [" + vt.getAgent() + "] does not belong in this agent for agent [" + agent + "]", new Throwable());
if(!tracers.containsKey(vt.getName())) {
synchronized(tracers) {
if(!tracers.containsKey(vt.getName())) {
putTracer(vt);
}
}
}
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getStateName()
*/
@Override
public String getStateName() {
return agentState.get().name();
}
/**
* Returns the state of this VirtualAgent
* @return the state
*/
public VirtualState getState() {
return agentState.get();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getTimeToSoftDown()
*/
@Override
public long getTimeToSoftDown() {
TreeSet<VirtualTracer> sorter = new TreeSet<VirtualTracer>(DESCENDING_SOFT_SORTER);
sorter.addAll(getAllTracers());
if(sorter.isEmpty()) return -1L;
return sorter.iterator().next().getTimeToSoftDown();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getTimeToHardDown()
*/
@Override
public long getTimeToHardDown() {
TreeSet<VirtualTracer> sorter = new TreeSet<VirtualTracer>(DESCENDING_HARD_SORTER);
sorter.addAll(getAllTracers());
if(sorter.isEmpty()) return -1L;
return sorter.iterator().next().getTimeToHardDown();
}
/** A descending comparator for VirtualTracers by time to soft down */
public static final Comparator<VirtualTracer> DESCENDING_SOFT_SORTER = new VirtualTracer.SoftDownDescendingComparator();
/** A descending comparator for VirtualTracers by time to hard down */
public static final Comparator<VirtualTracer> DESCENDING_HARD_SORTER = new VirtualTracer.HardDownDescendingComparator();
/** A descending comparator for VirtualTracers by last touch time */
public static final Comparator<VirtualTracer> DESCENDING_LT_SORTER = new VirtualTracer.LastTouchDescendingComparator();
/**
* Periodic check for expirations
*/
public void check() {
long currentTime = System.currentTimeMillis();
for(VirtualTracer vt: getAllTracers()) {
if(vt.isInvalid()) continue;
vt.checkState(currentTime);
}
if(getTimeToHardDown()<1) {
invalidate();
} else if(getTimeToSoftDown()<1) {
expire();
}
// long age = currentTime - touched.get();
// if(age < softDownPeriod) return;
// if(age >= softDownPeriod && age < hardDownPeriod) {
// // SOFTDOWN
// setState(VirtualState.SOFTDOWN);
// } else {
// // HARDDOWN
// setState(VirtualState.HARDDOWN);
// }
}
/**
* Touches all the agent's tracers
*/
@Override
public void touch() {
for(VirtualTracer vt: getAllTracers()) {
vt.touch();
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getHost()
*/
@Override
public String getHost() {
return host;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getAgent()
*/
@Override
public String getAgent() {
return agent;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#expire()
*/
@Override
public void expire() {
setState(VirtualState.SOFTDOWN);
for(VirtualTracer vt: getAllTracers()) {
vt.expire();
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#invalidate()
*/
@Override
public void invalidate() {
setState(VirtualState.HARDDOWN);
JMXHelper.unregisterMBean(objectName);
for(VirtualTracer vt: getAllTracers()) {
vt.invalidate();
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getVirtualTracers()
*/
@Override
public Map<String, VirtualTracerMBean> getVirtualTracers() {
Map<String, VirtualTracerMBean> map = new HashMap<String, VirtualTracerMBean>(tracers.size());
for(VirtualTracer vt: getAllTracers()) {
map.put(vt.getName(), vt);
}
return Collections.unmodifiableMap(map);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#isInvalid()
*/
@Override
public boolean isInvalid() {
return getState()==VirtualState.HARDDOWN;
}
/**
* Determines if this virtual agent has been expired or invalidated
* @return true if this virtual agent has been expired or invalidated, false otherwise
*/
public boolean isExpired() {
return getState().ordinal()>=VirtualState.SOFTDOWN.ordinal();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getLastTouchTimestamp()
*/
@Override
public long getLastTouchTimestamp() {
try {
TreeSet<VirtualTracer> sorter = new TreeSet<VirtualTracer>(DESCENDING_LT_SORTER);
sorter.addAll(getAllTracers());
if(sorter.isEmpty()) return -1L;
return sorter.iterator().next().getLastTouchTimestamp();
} catch (Exception nse) {
return 0;
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getLastTouchDate()
*/
@Override
public Date getLastTouchDate() {
try {
return new Date(getLastTouchTimestamp());
} catch (Exception nse) {
return null;
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getTracerCount()
*/
@Override
public int getTracerCount() {
return tracers.size();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.tracing.virtual.VirtualAgentMXBean#getActiveTracerCount()
*/
@Override
public int getActiveTracerCount() {
return tracers.size();
}
/**
* {@inheritDoc}
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((agent == null) ? 0 : agent.hashCode());
result = prime * result + ((host == null) ? 0 : host.hashCode());
return result;
}
/**
* {@inheritDoc}
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VirtualAgent other = (VirtualAgent) obj;
if (agent == null) {
if (other.agent != null)
return false;
} else if (!agent.equals(other.agent))
return false;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
return true;
}
/**
* {@inheritDoc}
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("VirtualAgent [host:%s, agent:%s, uri:%s]", host, agent, localURI);
}
/**
* Returns the local URI for this virtual agent
* @return the localURI
*/
public String getLocalURI() {
return localURI;
}
/**
* <p>Called when this virtual agent is invalidated</p>
* {@inheritDoc}
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
log.info("Invalidating " + this);
for(VirtualTracer t: this) {
t.invalidate();
}
}
/**
* {@inheritDoc}
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<VirtualTracer> iterator() {
return Collections.unmodifiableCollection(getAllTracers()).iterator();
}
/**
* <p>Title: HardDownDescendingComparator</p>
* <p>Description: Hard down time descending based comparator for virtual agents</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.server.tracing.virtual.VirtualAgent.HardDownDescendingComparator</code></p>
*/
public static class HardDownDescendingComparator implements Comparator<VirtualAgent> {
/**
* {@inheritDoc}
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(VirtualAgent vt1, VirtualAgent vt2) {
long vt1Time = vt1.getTimeToHardDown();
long vt2Time = vt2.getTimeToHardDown();
if(vt1Time<0) vt1Time = Long.MAX_VALUE;
if(vt2Time<0) vt2Time = Long.MAX_VALUE;
if(vt1Time < vt2Time) return 1;
if(vt2Time < vt1Time) return -1;
return 1;
}
}
}