/*
* Copyright 2011 Future Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.snmpmon;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.krakenapps.log.api.AbstractLogger;
import org.krakenapps.log.api.Log;
import org.krakenapps.log.api.LoggerFactory;
import org.krakenapps.log.api.SimpleLog;
import org.krakenapps.snmpmon.Interface.IfEntry;
import org.krakenapps.snmpmon.SnmpQueryLoggerFactory.ConfigOption;
import org.slf4j.Logger;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Counter32;
import org.snmp4j.smi.Counter64;
import org.snmp4j.smi.Gauge32;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TimeTicks;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.UnsignedInteger32;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.DefaultPDUFactory;
import org.snmp4j.util.TreeEvent;
import org.snmp4j.util.TreeListener;
import org.snmp4j.util.TreeUtils;
/**
* @author stania
*/
public class SnmpQueryLogger extends AbstractLogger {
private Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass().getName());
private static final int ethernetCsmacd = 6;
private static final int timeout = 3000;
private static ExecutorService sender;
private Snmp snmp;
private CommunityTarget commTarget;
private PDU pdu;
private Integer ifNumber;
private long loggerCreated;
private SnmpAgent target;
public static void open() {
BlockingQueue<Runnable> workQueue = new PriorityBlockingQueue<Runnable>(11, new Comparator<Runnable>() {
@Override
public int compare(Runnable o1, Runnable o2) {
if (o1 == null || !(o1 instanceof SnmpQueryLogger))
return -1;
if (o2 == null || !(o2 instanceof SnmpQueryLogger))
return 1;
SnmpQueryLogger l1 = (SnmpQueryLogger) o1;
if (l1.getLastRunDate() == null)
return -1;
SnmpQueryLogger l2 = (SnmpQueryLogger) o2;
if (l2.getLastRunDate() == null)
return 1;
boolean result = (l1.getLastRunDate().getTime() + l1.getInterval()) < (l2.getLastRunDate().getTime() + l2
.getInterval());
return result ? -1 : 1;
}
});
if (sender != null)
sender.shutdownNow();
sender = new ThreadPoolExecutor(1, 1, 1, TimeUnit.DAYS, workQueue, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Snmp Query Logger");
}
});
}
public static void close() {
sender.shutdownNow();
sender = null;
}
public SnmpQueryLogger(String hostGuid, String name, String description, LoggerFactory loggerFactory, Properties config) {
super(hostGuid, name, description, loggerFactory, config);
parseConfig(config);
init();
}
public SnmpQueryLogger(String hostGuid, String name, String description, LoggerFactory loggerFactory, SnmpAgent agent) {
super(hostGuid, name, description, loggerFactory);
target = agent;
init();
}
private void init() {
loggerCreated = new Date().getTime();
commTarget = new CommunityTarget();
commTarget.setCommunity(new OctetString(target.getCommunity()));
commTarget.setVersion(getSnmpVersionContstant(target.getSnmpVersion()));
commTarget.setAddress(new UdpAddress(target.getIp() + "/" + Integer.toString(target.getPort())));
commTarget.setRetries(2);
commTarget.setTimeout(timeout);
}
private int getSnmpVersionContstant(int snmpVersion) {
switch (snmpVersion) {
case 1:
return SnmpConstants.version1;
case 2:
return SnmpConstants.version2c;
case 3:
return SnmpConstants.version3;
default:
return SnmpConstants.version2c;
}
}
private void parseConfig(Properties config) {
if (logger.isDebugEnabled()) {
for (Object key : config.keySet())
logger.debug("kraken snmpmon: logger option key [{}], value [{}]", key, config.get(key));
}
String ip = (String) config.get(ConfigOption.AgentIP.getConfigKey());
if (ip == null)
throw new RuntimeException("target address cannot be null.");
int port = 161;
if (config.containsKey(ConfigOption.AgentPort.getConfigKey()))
port = Integer.valueOf((String) config.get(ConfigOption.AgentPort.getConfigKey()));
target = new SnmpAgent();
target.setIp(ip);
target.setPort(port);
// parse community strings
target.setCommunity((String) config.get(SnmpQueryLoggerFactory.ConfigOption.SnmpCommunity.getConfigKey()));
// parse versions
String version = config.getProperty(SnmpQueryLoggerFactory.ConfigOption.SnmpVersion.getConfigKey());
target.setSnmpVersion(getSnmpVersion(version));
}
private int getSnmpVersion(String version) {
if (version != null && Integer.valueOf(version) == 1) {
return SnmpConstants.version1;
} else {
return SnmpConstants.version2c;
}
}
public void setSnmp(Snmp snmp) {
this.snmp = snmp;
}
@Override
protected void runOnce() {
try {
query();
} catch (Exception e) {
String targetStr = String.format("for %s:%d", target.getIp(), target.getPort());
if (e instanceof RuntimeException) {
if (e.getCause() != null) {
logger.warn("SNMP query failed " + targetStr + ", cause: " + e.getCause().getMessage());
logger.debug("SNMP query failed exception detail", e);
} else {
if (!(e instanceof TimeoutException)) {
logger.warn("SNMP query failed " + targetStr + ", msg: " + e.getClass().getName() + "(" + e.getMessage()
+ ")");
logger.debug("SNMP query failed exception detail", e);
}
}
if (e instanceof TimeoutException) {
Map<String, Object> m = new HashMap<String, Object>();
m.put("source_ip", deviceIp);
m.put("target_ip", target.getIp());
m.put("proto", "snmp");
m.put("port", 161);
m.put("timeout", timeout);
// Log on error
Log log = new SimpleLog(new Date(), getFullName(), "heartbeat", "Heartbeat failed", m);
write(log);
}
} else {
logger.warn("SNMP query failed " + targetStr + ": " + e.getClass().getName() + ":" + e.getMessage());
logger.debug("SNMP query failed exception detail", e);
}
}
}
private ResponseListener listener = new ResponseListener() {
@SuppressWarnings("unchecked")
@Override
public void onResponse(ResponseEvent event) {
if (event.getUserObject() != null) {
try {
((Snmp) event.getUserObject()).close();
} catch (IOException e) {
}
}
if (event == null || event.getResponse() == null)
return;
List<VariableBinding> results = event.getResponse().getVariableBindings();
if (results == null)
return;
parseResult(results);
}
};
private void parseResult(List<VariableBinding> results) {
VariableBinding vbIfNumber = results.get(0);
String oidIfNumber = Interface.oidInterfaces + ".1.0";
if (!vbIfNumber.getOid().equals(new OID(oidIfNumber)))
throw new ParseException("first result doesn't match with " + oidIfNumber + ": " + vbIfNumber.getOid());
int ifNumber = vbIfNumber.getVariable().toInt();
if (this.ifNumber == null)
this.ifNumber = ifNumber;
else {
if (this.ifNumber != ifNumber)
pdu = null;
}
Map<Integer, Interface> interfaces = new HashMap<Integer, Interface>();
// building interfaceImpl instances
for (int i = 0; i < ifNumber; ++i) {
VariableBinding vb = results.get(1 + i);
if (!vb.getOid().startsWith(Interface.IfEntry.ifIndex.getOID()))
throw new ParseException("invalid parse value while building interfaces: " + vb);
int ifIndex = vb.getVariable().toInt();
interfaces.put(ifIndex, new Interface(ifIndex));
}
// set properties for interfaces
for (VariableBinding vb : results.subList(1 + ifNumber, results.size())) {
OID oid = vb.getOid();
OID parent = (OID) oid.clone();
int ifIndex = parent.removeLast();
IfEntry ifEntry = IfEntry.valueOf(parent);
if (ifEntry == null)
continue;
Variable var = vb.getVariable();
Interface iface = interfaces.get(ifIndex);
if (iface == null) {
continue;
} else {
// filter non-etherCsmacd
iface.setProperty(ifEntry, var);
if (ifEntry.equals(IfEntry.ifType)) {
if (var.toInt() != ethernetCsmacd) {
interfaces.remove(ifIndex);
}
}
}
}
Map<String, Object> data = new HashMap<String, Object>();
Map<OldValueKey, Long> newOldValues = new HashMap<OldValueKey, Long>();
try {
Date now = new Date();
// calculate interval
OldValueKey intervalKey = new OldValueKey(target, -1, IfEntry.INTERVAL);
newOldValues.put(intervalKey, now.getTime());
long interval;
Long oldInterval = oldValues.get(intervalKey);
if (oldInterval == null)
interval = delta32(now.getTime(), loggerCreated);
else
interval = delta32(now.getTime(), oldInterval.longValue());
TrafficSummary max_ts = new TrafficSummary(' ', -1, -1, -1);
String max_descr = null;
String max_physAddress = null;
// build & write logs for each interfaces
for (Entry<Integer, Interface> entry : interfaces.entrySet()) {
Interface iface = entry.getValue();
Map<String, Object> params = new HashMap<String, Object>();
params.put("scope", "device");
for (Entry<IfEntry, Object> ve : iface.getProperties().entrySet()) {
Variable value = (Variable) ve.getValue();
if (value instanceof Counter32) {
OldValueKey key = new OldValueKey(target, entry.getKey(), ve.getKey());
newOldValues.put(key, value.toLong());
Long oldValue = oldValues.get(key);
if (oldValue == null) {
value = new UnsignedInteger32(0);
} else {
// to calc sanitized delta..
long delta = delta32(value.toLong(), oldValue.longValue());
value = new UnsignedInteger32(delta);
}
}
String logParamKey = logParamKeys.get(ve.getKey());
if (logParamKey == null)
logParamKey = ve.getKey().toString();
params.put(logParamKey, convertVariableToPrimitive(value));
}
// interval in milliseconds
params.put("interval", interval);
String descr = iface.getProperties().get(IfEntry.ifDescr).toString();
String physAddress = iface.getProperties().get(IfEntry.ifPhysAddress).toString();
TrafficSummary rts = getTrafficSummary('R', params);
TrafficSummary tts = getTrafficSummary('T', params);
if (params.get(logParamKeys.get(IfEntry.ifOperStatus)).toString().equals("1")) {
if (max_ts.bps < rts.bps) {
max_ts = rts;
max_descr = descr;
max_physAddress = physAddress;
}
if (max_ts.bps < tts.bps) {
max_ts = tts;
max_descr = descr;
max_physAddress = physAddress;
}
}
params.put("msg", getMessage(descr, physAddress, rts, tts));
data.put(descr, params);
}
// max log
Map<String, Object> m = new HashMap<String, Object>();
String totalMsg = null;
if (max_ts.utilization < 0) {
logger.warn("SnmpMonitor max_ts.utilization < 0 ({}, {}, {})", new Object[] { max_ts.utilization, max_ts.bps,
max_descr });
m.put("scope", "total");
m.put("max_usage", 0);
m.put("description", max_descr);
m.put("mac", max_physAddress);
totalMsg = String.format("network usage: max network usage [%s (%s) - %d%%]", max_descr, max_physAddress,
max_ts.utilization);
m.put("msg", totalMsg);
data.put("_total", m);
} else {
m.put("scope", "total");
m.put("max_usage", max_ts.utilization);
m.put("description", max_descr);
m.put("mac", max_physAddress);
totalMsg = String.format("network usage: max network usage [%s (%s) - %d%%]", max_descr, max_physAddress,
max_ts.utilization);
m.put("msg", totalMsg);
data.put("_total", m);
}
Log log = new SimpleLog(new Date(), getFullName(), "network-usage", totalMsg, data);
write(log);
} finally {
oldValues = newOldValues;
}
}
private void query() throws Exception {
Snmp snmp = this.snmp;
if (snmp == null) {
TransportMapping transport = new DefaultUdpTransportMapping(new UdpAddress());
transport.listen();
snmp = new Snmp(transport);
}
if (pdu != null) {
snmp.getBulk(pdu, commTarget, (this.snmp == null) ? snmp : null, listener);
} else {
ifNumber = null;
pdu = new PDU();
pdu.add(new VariableBinding(new OID(Interface.oidInterfaces)));
pdu.setType(PDU.GETBULK);
pdu.setMaxRepetitions(10);
TreeUtils treeUtils = new TreeUtils(snmp, new DefaultPDUFactory());
treeUtils.getSubtree(commTarget, Interface.oidInterfaces, (this.snmp == null) ? snmp : null, new TreeListener() {
private List<VariableBinding> results = new ArrayList<VariableBinding>();
private boolean finish = false;
@Override
public boolean next(TreeEvent event) {
if (event.getVariableBindings() != null) {
pdu.addAllOIDs(event.getVariableBindings());
for (VariableBinding vb : event.getVariableBindings())
results.add(vb);
}
return true;
}
@Override
public void finished(TreeEvent event) {
finish = true;
if (event.getVariableBindings() != null) {
pdu.addAllOIDs(event.getVariableBindings());
for (VariableBinding vb : event.getVariableBindings())
results.add(vb);
}
if (event.getUserObject() != null) {
try {
((Snmp) event.getUserObject()).close();
} catch (IOException e) {
}
}
parseResult(results);
}
@Override
public boolean isFinished() {
return finish;
}
});
}
}
@Override
protected ExecutorService getExecutor() {
return sender;
}
private static String deviceIp = null;
static {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
if (networkInterface.getHardwareAddress() == null || networkInterface.getHardwareAddress().length != 6)
continue;
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (inetAddress.getHostAddress().startsWith("127."))
continue;
deviceIp = inetAddress.getHostAddress();
}
}
if (deviceIp == null)
deviceIp = InetAddress.getLocalHost().getHostAddress();
} catch (SocketException e) {
} catch (UnknownHostException e) {
}
}
private static class OldValueKey {
private SnmpAgent agent;
private int ifIndex;
private IfEntry ifEntry;
public OldValueKey(SnmpAgent agent, int ifIndex, IfEntry ifEntry) {
this.agent = agent;
this.ifIndex = ifIndex;
this.ifEntry = ifEntry;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((agent == null) ? 0 : agent.hashCode());
result = prime * result + ((ifEntry == null) ? 0 : ifEntry.hashCode());
result = prime * result + ifIndex;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OldValueKey other = (OldValueKey) obj;
if (agent == null) {
if (other.agent != null)
return false;
} else if (!agent.equals(other.agent))
return false;
if (ifEntry != other.ifEntry)
return false;
if (ifIndex != other.ifIndex)
return false;
return true;
}
}
private Map<OldValueKey, Long> oldValues = new HashMap<OldValueKey, Long>();
private static EnumMap<IfEntry, String> logParamKeys = new EnumMap<IfEntry, String>(IfEntry.class);
static {
logParamKeys.put(IfEntry.ifIndex, "index");
logParamKeys.put(IfEntry.ifType, "type");
logParamKeys.put(IfEntry.ifDescr, "description");
logParamKeys.put(IfEntry.ifMtu, "mtu");
logParamKeys.put(IfEntry.ifPhysAddress, "mac");
logParamKeys.put(IfEntry.ifSpeed, "bandwidth");
logParamKeys.put(IfEntry.ifOperStatus, "oper_status");
logParamKeys.put(IfEntry.ifAdminStatus, "admin_status");
logParamKeys.put(IfEntry.ifSpecific, "specific");
logParamKeys.put(IfEntry.ifLastChange, "last_change");
logParamKeys.put(IfEntry.ifInOctets, "rx_bytes_delta");
logParamKeys.put(IfEntry.ifOutOctets, "tx_bytes_delta");
logParamKeys.put(IfEntry.ifInUcastPkts, "rx_ucast_pkts_delta");
logParamKeys.put(IfEntry.ifOutUcastPkts, "tx_ucast_pkts_delta");
logParamKeys.put(IfEntry.ifInNUcastPkts, "rx_nucast_pkts_delta");
logParamKeys.put(IfEntry.ifOutNUcastPkts, "tx_nucast_pkts_delta");
logParamKeys.put(IfEntry.ifInErrors, "rx_errors_delta");
logParamKeys.put(IfEntry.ifOutErrors, "tx_errors_delta");
logParamKeys.put(IfEntry.ifInDiscards, "rx_discards_delta");
logParamKeys.put(IfEntry.ifOutDiscards, "tx_discards_delta");
logParamKeys.put(IfEntry.ifInUnknownProtos, "rx_unknown_protos");
logParamKeys.put(IfEntry.ifOutQLen, "tx_queue_length");
}
private long delta32(long nv, long ov) {
if (nv < ov) {
return nv + 0xFFFFFFFFL - ov;
} else {
return nv - ov;
}
}
private <T extends Variable> Object convertVariableToPrimitive(T value) {
Class<? extends Variable> cls = value.getClass();
if (cls.equals(Counter64.class))
return value.toLong();
else if (cls.equals(UnsignedInteger32.class))
return value.toLong();
else if (cls.equals(Counter32.class))
return value.toLong();
else if (cls.equals(Gauge32.class))
return value.toLong();
else if (cls.equals(TimeTicks.class))
return value.toLong();
else if (cls.equals(Integer32.class))
return value.toInt();
return value.toString();
}
private TrafficSummary getTrafficSummary(char type, Map<String, Object> params) {
String octets = (type == 'R') ? logParamKeys.get(IfEntry.ifInOctets) : logParamKeys.get(IfEntry.ifOutOctets);
String ucastPkts = (type == 'R') ? logParamKeys.get(IfEntry.ifInUcastPkts) : logParamKeys.get(IfEntry.ifOutUcastPkts);
String nucastPkts = (type == 'R') ? logParamKeys.get(IfEntry.ifInNUcastPkts) : logParamKeys.get(IfEntry.ifOutNUcastPkts);
long octet_delta = ((Number) params.get(octets)).longValue();
long pkt_delta = 0;
if (params.containsKey(ucastPkts)) {
pkt_delta += ((Number) params.get(ucastPkts)).longValue();
}
if (params.containsKey(nucastPkts)) {
pkt_delta += ((Number) params.get(nucastPkts)).longValue();
}
long bandwidth = ((Number) params.get(logParamKeys.get(IfEntry.ifSpeed))).longValue();
long interval = (Long) params.get("interval");
int utilization;
int bps = (int) ((double) octet_delta / interval * 8.0 * 1000);
int fps = (int) ((double) pkt_delta / interval * 1000);
if (bandwidth == 0)
utilization = 0;
else
utilization = (int) ((long) bps * 100L / bandwidth);
if (utilization < 0) {
logger.warn("minus utilization: {}, {}, {}, {}", new Object[] { utilization, bps, fps, bandwidth });
}
if (utilization > 100) {
logger.warn("max_ts utilization > 100 ! ({})", utilization);
logger.warn(String.format(
"ifIndex: %d, physAddress: %s, interval(ms): %d, octet_delta: %d, pkt_delta: %d, bandwidth: %d",
(Number) params.get(logParamKeys.get(IfEntry.ifIndex)),
(String) params.get(logParamKeys.get(IfEntry.ifPhysAddress)), interval, octet_delta, pkt_delta, bandwidth));
}
return new TrafficSummary(type, utilization, bps, fps);
}
private String getMessage(String descr, String physAddress, TrafficSummary rts, TrafficSummary tts) {
return String.format("%s (%s), %s, %s", descr, physAddress, rts.toString(), tts.toString());
}
private static class TrafficSummary {
public char type;
public int utilization;
public int bps;
public int fps;
public TrafficSummary(char type, int utilization, int bps, int fps) {
this.type = type;
this.utilization = utilization;
this.bps = bps;
this.fps = fps;
}
@Override
public String toString() {
return String.format("%cX[%d%% %s, %dfps]", type, utilization, bpsToString(bps), fps);
}
private static String[] bpsUnits = new String[] { "", "K", "M", "G", "T", "P" };
private static String bpsToString(int bps) {
int mul = 0;
while (bps >= 1000) {
bps = bps / 1000;
mul++;
}
String bpsUnit = bpsUnits[mul];
return String.format("%d%sbps", bps, bpsUnit);
}
}
}