/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2009-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.threshd;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.collectd.AliasedResource;
import org.opennms.netmgt.collectd.IfInfo;
import org.opennms.netmgt.config.collector.CollectionAttribute;
import org.opennms.netmgt.config.collector.CollectionResource;
import org.opennms.netmgt.dao.support.ResourceTypeUtils;
import org.opennms.netmgt.model.RrdRepository;
import org.opennms.netmgt.poller.LatencyCollectionResource;
/**
* <p>CollectionResourceWrapper class.</p>
*
* Wraps a CollectionResource with some methods and caching for the efficient application of thresholds (without
* pulling thresholding code into CollectionResource itself)
*
* A fresh instance should be created for each collection cycle (assumptions are made based on that premise)
*
* @author ranger
* @version $Id: $
*/
public class CollectionResourceWrapper {
private int m_nodeId;
private String m_hostAddress;
private String m_serviceName;
private String m_label;
private String m_iflabel;
private String m_ifindex;
private RrdRepository m_repository;
private CollectionResource m_resource;
private Map<String, CollectionAttribute> m_attributes;
/**
* Keeps track of both the Double value, and when it was collected, for the static cache of attributes
*
* This is necessary for the *correct* calculation of Counter rates, across variable collection times and possible
* collection failures (see NMS-4244)
*
* Just a holder class for two associated values; no need for the formality of accessors
*/
class CacheEntry {
Date timestamp;
Double value;
public CacheEntry(Date timestamp, Double value) {
this.timestamp = timestamp;
this.value = value;
}
}
/*
* Holds last values for counter attributes (in order to calculate delta)
*/
static Map<String, CacheEntry> s_cache = new ConcurrentHashMap<String,CacheEntry>();
/*
* To avoid update static cache on every call of getAttributeValue.
* In some cases, the same DS could be needed in many thresholds definitions for same resource.
* See Bug 3193
*/
private Map<String, Double> m_localCache = new HashMap<String,Double>();
/*
* Holds interface ifInfo data for interface resource only. This avoid multiple calls to database for same resource.
*/
private Map<String, String> m_ifInfo;
/*
* Holds the timestamp of the collection being thresholded, for the calculation of counter rates
*/
private Date m_collectionTimestamp;
/**
* <p>Constructor for CollectionResourceWrapper.</p>
*
* @param interval a long.
* @param nodeId a int.
* @param hostAddress a {@link java.lang.String} object.
* @param serviceName a {@link java.lang.String} object.
* @param repository a {@link org.opennms.netmgt.model.RrdRepository} object.
* @param resource a {@link org.opennms.netmgt.config.collector.CollectionResource} object.
* @param attributes a {@link java.util.Map} object.
*/
public CollectionResourceWrapper(Date collectionTimestamp, int nodeId, String hostAddress, String serviceName, RrdRepository repository, CollectionResource resource, Map<String, CollectionAttribute> attributes) {
m_collectionTimestamp = collectionTimestamp;
m_nodeId = nodeId;
m_hostAddress = hostAddress;
m_serviceName = serviceName;
m_repository = repository;
m_resource = resource;
m_attributes = attributes;
if (isAnInterfaceResource()) {
if (resource instanceof AliasedResource) { // TODO What about AliasedResource's custom attributes?
m_iflabel = ((AliasedResource) resource).getLabel();
m_ifInfo = ((AliasedResource) resource).getIfInfo().getAttributesMap();
m_ifInfo.put("domain", ((AliasedResource) resource).getDomain());
}
if (resource instanceof IfInfo) {
m_iflabel = ((IfInfo) resource).getLabel();
m_ifInfo = ((IfInfo) resource).getAttributesMap();
}
if (resource instanceof LatencyCollectionResource) {
JdbcIfInfoGetter ifInfoGetter = new JdbcIfInfoGetter();
String ipAddress = ((LatencyCollectionResource) resource).getIpAddress();
m_iflabel = ifInfoGetter.getIfLabel(getNodeId(), ipAddress);
if (m_iflabel != null) { // See Bug 3488
m_ifInfo = ifInfoGetter.getIfInfoForNodeAndLabel(getNodeId(), m_iflabel);
} else {
log().info("Can't find ifLabel for latency resource " + resource.getInstance() + " on node " + getNodeId());
}
}
if (m_ifInfo != null) {
m_ifindex = m_ifInfo.get("snmpifindex");
} else {
log().info("Can't find ifInfo for " + resource);
}
}
}
/**
* <p>getNodeId</p>
*
* @return a int.
*/
public int getNodeId() {
return m_nodeId;
}
/**
* <p>getHostAddress</p>
*
* @return a {@link java.lang.String} object.
*/
public String getHostAddress() {
return m_hostAddress;
}
/**
* <p>getServiceName</p>
*
* @return a {@link java.lang.String} object.
*/
public String getServiceName() {
return m_serviceName;
}
/**
* <p>getRepository</p>
*
* @return a {@link org.opennms.netmgt.model.RrdRepository} object.
*/
public RrdRepository getRepository() {
return m_repository;
}
/**
* <p>getLabel</p>
*
* @return a {@link java.lang.String} object.
*/
public String getLabel() {
return m_label;
}
/**
* <p>setLabel</p>
*
* @param label a {@link java.lang.String} object.
*/
public void setLabel(String label) {
m_label = label;
}
/**
* <p>getInstance</p>
*
* @return a {@link java.lang.String} object.
*/
public String getInstance() {
return m_resource != null ? m_resource.getInstance() : null;
}
/**
* <p>getResourceTypeName</p>
*
* @return a {@link java.lang.String} object.
*/
public String getResourceTypeName() {
return m_resource != null ? m_resource.getResourceTypeName() : null;
}
/**
* <p>getIfLabel</p>
*
* @return a {@link java.lang.String} object.
*/
public String getIfLabel() {
return m_iflabel;
}
/**
* <p>getIfIndex</p>
*
* @return a {@link java.lang.String} object.
*/
public String getIfIndex() {
return m_ifindex;
}
/**
* <p>getIfInfoValue</p>
*
* @param attribute a {@link java.lang.String} object.
* @return a {@link java.lang.String} object.
*/
protected String getIfInfoValue(String attribute) {
if (m_ifInfo != null)
return m_ifInfo.get(attribute);
return null;
}
/**
* <p>isAnInterfaceResource</p>
*
* @return a boolean.
*/
public boolean isAnInterfaceResource() {
return getResourceTypeName() != null && getResourceTypeName().equals("if");
}
/**
* <p>isValidInterfaceResource</p>
*
* @return a boolean.
*/
public boolean isValidInterfaceResource() {
if (m_ifInfo == null) {
return false;
}
try {
if(null == m_ifindex)
return false;
if(Integer.parseInt(m_ifindex) < 0)
return false;
} catch(Throwable e) {
return false;
}
return true;
}
/*
* FIXME What happen with numeric fields from strings.properties ?
*/
/**
* <p>getAttributeValue</p>
*
* @param ds a {@link java.lang.String} object.
* @return a {@link java.lang.Double} object.
*/
public Double getAttributeValue(String ds) {
if (m_attributes == null || m_attributes.get(ds) == null) {
log().warn("getAttributeValue: can't find attribute called " + ds + " on " + m_resource);
return null;
}
String numValue = m_attributes.get(ds).getNumericValue();
if (numValue == null) {
log().warn("getAttributeValue: can't find numeric value for " + ds + " on " + m_resource);
return null;
}
// TODO Is this ID unique ? Here is a suggestion:
// String id = "node[" + m_nodeId + '].resourceType[' + m_resource.getResourceTypeName()
// + '].instance[' + m_resource.getInstance() + "].label[" + m_resource.getLabel()
// + "].metric[" + ds + "]"
String id = m_resource.toString() + "." + ds;
Double current = Double.parseDouble(numValue);
if (m_attributes.get(ds).getType().toLowerCase().startsWith("counter") == false) {
if (log().isDebugEnabled()) {
log().debug("getAttributeValue: id=" + id + ", value= " + current);
}
return current;
}
return getCounterValue(id, current);
}
/*
* This will return the rate based on configured collection step
*/
private Double getCounterValue(String id, Double current) {
if (m_localCache.containsKey(id) == false) {
CacheEntry last = s_cache.get(id);
if (log().isDebugEnabled()) {
log().debug("getCounterValue: id=" + id + ", last=" +
(last==null ? last : last.value +"@"+last.timestamp) +
", current=" + current);
}
s_cache.put(id, new CacheEntry(m_collectionTimestamp, current));
if (last == null) {
m_localCache.put(id, Double.NaN);
log().info("getCounterValue: unknown last value, ignoring current");
} else {
if ( m_collectionTimestamp == null ) {
//If you get this, you need to ensure you passed a non-null timestamp to the constructor.
// This usually comes from the CollectionSet that is being visited.
log().error("getCounterValue: Haven't got a collection timestamp while calculating a counter for key "+ id + " on " + m_resource
+". This is a programmer error and should be reported");
return null;
}
Double delta = current.doubleValue() - last.value.doubleValue();
// wrapped counter handling(negative delta), rrd style
if (delta < 0) {
double newDelta = delta.doubleValue();
// 2-phase adjustment method
// try 32-bit adjustment
newDelta += Math.pow(2, 32);
if (newDelta < 0) {
// try 64-bit adjustment
newDelta += Math.pow(2, 64) - Math.pow(2, 32);
}
log().info("getCounterValue: " + id +
"(counter) wrapped counter adjusted last=" +
last.value +"@"+last.timestamp +
", current=" + current +
", olddelta=" + delta +
", newdelta=" + newDelta);
delta = newDelta;
}
//Get the interval between when this current collection was taken, and the last time this
// value was collected (and had a counter rate calculated for it)
// FIXME If interval == 0 then, the returned value will be infinite.
// This is the problem experienced by some customers.
// This has been always related with SNMP Interface resources.
// Here is a temporal workaround to avoid the threshold messages.
long interval = ( m_collectionTimestamp.getTime() - last.timestamp.getTime() ) / 1000;
if (interval > 0) {
m_localCache.put(id, delta / interval);
} else {
log().error("getCounterValue: invalid interval rate for " + id + ". The last valid value for the metric was " + last.value + " at " + last.timestamp + ". This value will be used instead.");
}
}
}
return m_localCache.get(id);
}
/**
* <p>getLabelValue</p>
*
* @param ds a {@link java.lang.String} object.
* @return a {@link java.lang.String} object.
*/
public String getLabelValue(String ds) {
if (ds == null || ds.equals(""))
return null;
if (log().isDebugEnabled()) {
log().debug("getLabelValue: Getting Value for " + m_resource.getResourceTypeName() + "::" + ds);
}
if ("nodeid".equals(ds))
return Integer.toString(m_nodeId);
if ("ipaddress".equals(ds))
return m_hostAddress;
if ("iflabel".equals(ds))
return getIfLabel();
String value = null;
File resourceDirectory = m_resource.getResourceDir(m_repository);
if ("ID".equals(ds)) {
return resourceDirectory.getName();
}
try {
if (isAnInterfaceResource()) { // Get Value from ifInfo only for Interface Resource
value = getIfInfoValue(ds);
}
if (value == null) { // Find value on saved string attributes
value = ResourceTypeUtils.getStringProperty(resourceDirectory, ds);
}
} catch (Throwable e) {
log().info("getLabelValue: Can't get value for attribute " + ds + " for resource " + m_resource + ". " + e, e);
}
if (value == null) {
log().debug("getLabelValue: The field " + ds + " is not a string property. Trying to parse it as numeric metric.");
Double d = getAttributeValue(ds);
if (d != null)
value = d.toString();
}
return value;
}
/** {@inheritDoc} */
@Override
public String toString() {
return m_resource.toString();
}
private ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
}