/**
* 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.metric.catalog;
import org.helios.apmrouter.metric.IMetric;
import org.helios.apmrouter.metric.MetricType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import static org.helios.apmrouter.util.Methods.nvl;
/**
* <p>Title: AbstractMetricCatalog</p>
* <p>Description: The base abstract metric catalog support class</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.metric.catalog.AbstractMetricCatalog</code></p>
* @param <K> The key type used in the name cache
* @param <V> The value type used in the name cache
*/
public abstract class AbstractMetricCatalog<K, V> implements IMetricCatalog {
/** The name cache of metrics */
protected final Map<K, V> namecache;
/** The delta cache for metrics */
protected final Map<K, Long> deltacache;
/** The metric token map */
protected final Map<Long, IDelegateMetric> tokencache;
/** The token ID factory */
protected final AtomicLong tokenSerial = new AtomicLong(0);
/**
* Creates a new AbstractMetricCatalog
* TODO: Add overrides for the map sizing in sys props
*/
protected AbstractMetricCatalog() {
namecache = new ConcurrentHashMap<K, V>(1024, 0.5f, 16);
deltacache = new ConcurrentHashMap<K, Long>(128, 0.5f, 16);
tokencache = new ConcurrentHashMap<Long, IDelegateMetric>(1024, 0.5f, 16);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IMetricCatalog#get(long)
*/
@Override
public IDelegateMetric get(long metricIdToken) {
return tokencache.get(metricIdToken);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IMetricCatalog#resetTokens()
*/
public void resetTokens() {
for(IDelegateMetric idm: tokencache.values()) {
idm.setToken(-1);
}
tokencache.clear();
}
/**
* Sets the serialization token for the passed metric identifier using a contrived token.
* Intended only for testing or server side.
* @param host The host name
* @param agent The agent name
* @param name The metric name
* @param type The metric type
* @param namespace The namespace segments
* @return the assigned token
*/
@Override
public long setToken(String host, String agent, CharSequence name, MetricType type, CharSequence... namespace) {
return setToken(tokenSerial.incrementAndGet(), host,agent, name, type, namespace);
}
/**
* Sets the serialization token for the passed metric identifier
* @param token The token to set on the metric
* @param host The host name
* @param agent The agent name
* @param name The metric name
* @param type The metric type
* @param namespace The namespace segments
* @return the assigned token
*/
public long setToken(long token, String host, String agent, CharSequence name, MetricType type, CharSequence... namespace) {
IDelegateMetric metric = get(host, agent, name, type, namespace);
metric.setToken(token);
tokencache.put(token, metric);
return token;
}
/**
* Sets the serialization token for the IDelegateMetric with the passed FQN
* @param metricFqn The FQN of the IDelegateMetric to update
* @param token The token
*/
@Override
public void setToken(CharSequence metricFqn, long token) {
K key = createKey(metricFqn.toString());
IDelegateMetric idm = get(key);
if(idm!=null) {
setToken(idm, token);
} else {
System.err.println("Null IDelegateMetric for [" + metricFqn + "]:" + token);
}
}
/**
* Sets the serialization token for the passed un-tokenized metric
* @param metric the un-tokenized IMetric to tokenize
* @return the assigned token
* FIXME: This needs to assign a REAL token from a persistent store.
*/
@Override
public long setToken(IMetric metric) {
return setToken(metric.getHost(), metric.getAgent(), metric.getName(), metric.getType(), metric.getNamespace());
}
/**
* Sets the serialization token for the passed un-tokenized metricId
* @param metricId the un-tokenized IDelegateMetric to tokenize
* @param token The token to set
* @return the set token
*/
@Override
public long setToken(IDelegateMetric metricId, long token) {
return setToken(token, metricId.getHost(), metricId.getAgent(), metricId.getName(), metricId.getType(), metricId.getNamespace());
}
/**
* Returns the delta for the passed value and metricId key
* @param value The long value to get the delta for
* @param host The host name
* @param agent The agent name
* @param name The metric name
* @param namespace The namespace
* @return the delta value or null if this was the first call for the metric
*/
@Override
public Long getDelta(long value, String host, String agent, CharSequence name, CharSequence... namespace) {
return _getDelta(value, host, agent, name, namespace);
}
protected Long _getDelta(long value, String host, String agent, CharSequence name, CharSequence... namespace) {
K key = createKey(getFQN(host, agent, name, namespace));
Long state = deltacache.put(key, value);
if(state==null || value < state) return null;
return value-state;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IMetricCatalog#size()
*/
@Override
public int size() {
return namecache.size();
}
/**
* Creates a key from the FQN
* @param fqn The fully qualified metric name
* @return the key that represents the fully qualified metric name
*/
protected abstract K createKey(String fqn);
/**
* Returns the IDelegateMetric for the passed key
* @param key The metric catalog key
* @return the keyed IDelegateMetric or null if one was not found
*/
protected abstract IDelegateMetric get(K key);
/**
* Creates a new IDelegateMetric and writes the cache entry
* @param key The key the metric will be cached by
* @param host The host name
* @param agent The agent name
* @param name The metric name
* @param type The metric type
* @param namespace The namespace segments
* @return the created metric
*/
protected abstract IDelegateMetric create(K key, String host, String agent, CharSequence name, MetricType type, CharSequence... namespace);
/**
* Returns a catalog key for the passed metric id
* @param host The host name
* @param agent The agent name
* @param name The metric name
* @param namespace The namespace
* @return the catalog key
*/
protected K createKey(String host, String agent, CharSequence name, CharSequence... namespace) {
return createKey(getFQN(host, agent, name, namespace));
}
/**
* Builds the FQN
* @param host The host name
* @param agent The agent name
* @param name The metric name
* @param namespace The namespace
* @return The fully qualified metric name
*/
public String getFQN(String host, String agent, CharSequence name, CharSequence... namespace) {
StringBuilder sb = new StringBuilder(nvl(host, "Host Name")).append(NSDELIM).append(nvl(agent, "Agent Name"));
if(namespace!=null && namespace.length>0) {
List<String> ns = new ArrayList<String>(namespace.length);
for(int i = 0; i < namespace.length; i++) {
if(namespace[i]==null) continue;
String s = namespace[i].toString().trim();
if(s.isEmpty()) continue;
ns.add(s);
}
if(!ns.isEmpty()) {
for(String s: ns) {
sb.append(NSDELIM).append(s);
}
}
}
sb.append(NADELIM).append(nvl(name, "Metric Name"));
return sb.toString();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IMetricCatalog#get(java.lang.String, java.lang.String, java.lang.CharSequence, org.helios.apmrouter.metric.MetricType, java.lang.CharSequence[])
*/
@Override
public IDelegateMetric get(String host, String agent, CharSequence name, MetricType type, CharSequence... namespace) {
String fqn = getFQN(host, agent, name, namespace);
K key = createKey(fqn);
IDelegateMetric idm = get(key);
if(idm==null) {
synchronized(namecache) {
idm = get(key);
if(idm==null) {
idm = create(key, host, agent, name, type, namespace);
}
}
}
// ====================================================================================================
// Adding this to divert metric-type collisions where a tracer called for a metric with the same name
// as an already registered metric, but with a different metric type.
// ====================================================================================================
if(!idm.getType().equals(type)) {
//System.err.println("MetricType Collision [" + fqn + "]");
return get(host, agent, new StringBuilder(name.toString()).append("*").append(type.name()).append("*"), type, namespace);
}
assert fqn.equals(idm.getFQN());
return idm;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IMetricCatalog#get(java.lang.String, java.lang.String, java.lang.CharSequence, int, java.lang.CharSequence[])
*/
@Override
public IDelegateMetric get(String host, String agent, CharSequence name, int type, CharSequence... namespace) {
return get(host, agent, name, MetricType.valueOf(type), namespace);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IMetricCatalog#get(java.lang.String, java.lang.String, java.lang.CharSequence, java.lang.String, java.lang.CharSequence[])
*/
@Override
public IDelegateMetric get(String host, String agent, CharSequence name, String type, CharSequence... namespace) {
return get(host, agent, name, MetricType.valueOfName(type), namespace);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IMetricCatalog#dispose()
* <p><b>DO NOT CALL THIS METHOD UNLESS YOU KNOW WHAT YOU'RE DOING.</b>
*/
@Override
public void dispose() {
namecache.clear();
deltacache.clear();
tokencache.clear();
}
/**
* Returns a delegate metric for the passed FQN and type
* @param fqn The metric FQN
* @param type The metric type
* @return the delegate metric
*/
@Override
public IDelegateMetric build(String fqn, MetricType type) {
return ICEMetricCatalog.build(fqn, type, this);
}
/**
* Calculates a low collision hash code for the passed string
* @param s The string to calculate the hash code for
* @return the long hashcode
*/
public static long longHashCode(String s) {
long h = 0;
int len = s.length();
int off = 0;
int hashPrime = s.hashCode();
char val[] = s.toCharArray();
for (int i = 0; i < len; i++) {
h = (31*h + val[off++] + (hashPrime*h));
}
return h;
}
}