/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.systemservices.impl.healthmonitor;
import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCode;
import com.emc.storageos.systemservices.exceptions.SyssvcException;
import com.emc.storageos.systemservices.impl.healthmonitor.models.CPUStats;
import com.emc.vipr.model.sys.healthmonitor.DiskStats;
import com.emc.vipr.model.sys.healthmonitor.ServiceStats;
import com.google.common.primitives.UnsignedLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that calculates node and its services statistics.
* Node stats are retrieved from
* /proc/meminfo: contains information about memory usage of the system
* /proc/diskstats: contains disk I/O statistics for each disk device.
* /proc/loadavg: contains information about load average numbers
* <p/>
* Service stats are retrieved from /proc/{pid}/comm: the command that invoked this process /proc/{pid}/cmdline: contains command line for
* the process. Gives service name. /proc/{pid}/stat: contains status information about the process /proc/{pid}/statm: contains information
* about memory usage /proc/{pid}/fd: contains entries for files that this process has opened
*/
public class NodeStatsExtractor implements StatConstants {
private static final Logger _log = LoggerFactory.getLogger(NodeStatsExtractor.class);
/**
* Method that returns all service statistics in the order of availableservices list.
*/
public static List<ServiceStats> getServiceStats(
List<String> availableServices) {
List<ServiceStats> serviceStatsList = new ArrayList<ServiceStats>();
Map<String, ServiceStats> tempServiceStatsMap = new HashMap<String,
ServiceStats>();
File procDir = new File(PROC_DIR);
File[] procFiles = procDir.listFiles();
// Get required data from the relevant
// /proc/[pid]/(comm,cmdline,stat,statm,fd) files.
for (File procFile : procFiles) {
String pid = procFile.getName().trim();
if (pid.equalsIgnoreCase(SELF_DIR)) {
continue;
}
try {
String serviceName = ProcStats.getServiceName(pid);
if (!serviceName.isEmpty() && !MONITOR_SVCNAME
.equals(serviceName)) {
String commandFile = null;
try {
commandFile = FileReadUtil.readFirstLine(String.format
(COMM_FILE, pid));
} catch (Exception e) {
_log.error("Error occurred while reading command file: {}", e);
}
_log.info("Get serviceStats for service {}", serviceName);
if (serviceName.contains(COVERAGE_SVCNAME_SUFFIX)) {
serviceName = serviceName.split("-")[0];
}
ServiceStats serviceStats = new ServiceStats(serviceName, commandFile,
ProcStats.getFileDescriptorCntrs(pid),
ProcStats.getProcStats(pid));
tempServiceStatsMap.put(serviceName, serviceStats);
}
} catch (SyssvcException ex) {
if (ex.getServiceCode() == ServiceCode.SYS_INTERNAL_SERVICE_NAME_NOT_FOUND) {
continue;
}
_log.debug("Syssvc Exception: {}",ex);
} catch (Exception e) {
_log.debug("Internal error: {}", e);
}
}
// Ordering service stats
if (availableServices == null || availableServices.isEmpty()) {
_log.warn("List of available services is null or empty: {}",
availableServices);
return new ArrayList<ServiceStats>(tempServiceStatsMap.values());
} else {
for (String svcName : availableServices) {
if (tempServiceStatsMap.containsKey(svcName)) {
serviceStatsList.add(tempServiceStatsMap.remove(svcName));
} else {
serviceStatsList.add(new ServiceStats(svcName));
}
}
return serviceStatsList;
}
}
/**
* Get /proc/diskstats data. If "interval" value is > 0 this will get stats again
* after sleep for interval seconds.
*
* @param intervalInSecs interval value in seconds
* @return List of disk stats
*/
public static List<DiskStats> getDiskStats(int intervalInSecs) {
// Getting disk/cpu stats
try {
Map<String, DiskStats> oldDiskDataMap = ProcStats.getDiskStats();
CPUStats oldCPUStats = ProcStats.getCPUStats();
// sleep if needed
Map<String, DiskStats> newDiskDataMap = null;
CPUStats newCPUStats = null;
if (intervalInSecs > 0) {
try {
Thread.sleep(intervalInSecs * 1000);
} catch (InterruptedException e) {
_log.error("Thread Sleep InterrupdtedExcepion: {}", e);
return null;
}
// Getting disk/cpu stats after sleep
newDiskDataMap = ProcStats.getDiskStats();
newCPUStats = ProcStats.getCPUStats();
}
// perform method that will actually perform the calucations.
return getDifferentialDiskStats(oldDiskDataMap, newDiskDataMap, getCPUTimeDeltaMS(oldCPUStats,
newCPUStats));
} catch (Exception e) {
_log.error("Error occurred while getting disk stats: {}", e);
}
return null;
}
/**
* This methods does the work of calculating the diskstats.
* calculated values: read/sec, write/sec, read_sec/sec, write_sec/sec
* avg_wait, svc_time, %util.
*
* @param oldDiskDataMap disk data values collected during initial run
* @param newDiskDataMap disk data values collected after 2s
*/
private static List<DiskStats> getDifferentialDiskStats(
Map<String, DiskStats> oldDiskDataMap,
Map<String, DiskStats> newDiskDataMap, double deltaMS) {
List<DiskStats> diskStatsList = new ArrayList<DiskStats>();
// iterate though initial map as driver for getting data from the
// compare map and determining the average per second;
DecimalFormat decimalFormat = new DecimalFormat("#####0.00");
for (Map.Entry<String, DiskStats> entry : oldDiskDataMap.entrySet()) {
String diskId = entry.getKey();
DiskStats oldStats = entry.getValue();
DiskStats newStats = null;
if (newDiskDataMap != null && newDiskDataMap.get(diskId) != null) {
newStats = newDiskDataMap.get(diskId);
}
DiskStats diffStats = getDifference(oldStats, newStats);
// number of requests
double numOfIOs = diffStats.getNumberOfReads()
+ diffStats.getNumberOfWrites();
// await
double wait = numOfIOs > 0 ?
(diffStats.getReadTicks() + diffStats.getWriteTicks()) / numOfIOs : 0;
// svctm
double svcTime = numOfIOs > 0 ? diffStats.getNumberOfIOInMs() / numOfIOs : 0;
// %util
double busy = 0;
if (deltaMS > 0) {
busy = 100 * diffStats.getNumberOfIOInMs() / deltaMS;
busy = busy > 100 ? 100 : busy;
}
diskStatsList.add(new DiskStats(diskId,
Double.parseDouble(decimalFormat.format(getRate
(diffStats.getNumberOfReads(), deltaMS))),
Double.parseDouble(decimalFormat.format(getRate(diffStats
.getSectorsRead(), deltaMS))),
Double.parseDouble(decimalFormat.format(getRate
(diffStats.getNumberOfWrites(), deltaMS))),
Double.parseDouble(decimalFormat.format(getRate(diffStats
.getSectorsWrite(), deltaMS))),
Double.parseDouble(decimalFormat.format(wait)),
Double.parseDouble(decimalFormat.format(svcTime)),
Double.parseDouble(decimalFormat.format(busy))));
}
return diskStatsList;
}
/**
* Calculates the difference between newStats and oldStats values and returns them
* as new disk stats object.
* If interval is 0, newStats will be null. in this case it just returns
* oldStats
*/
private static DiskStats getDifference(DiskStats oldStats, DiskStats newStats) {
if (newStats == null) {
return oldStats;
} else {
return new DiskStats(oldStats.getDiskId(),
diff(newStats.getNumberOfReads(), oldStats.getNumberOfReads()),
diff(newStats.getSectorsRead(), oldStats.getSectorsRead()),
diff(newStats.getReadTicks(), oldStats.getReadTicks()),
diff(newStats.getNumberOfWrites(), oldStats.getNumberOfWrites()),
diff(newStats.getSectorsWrite(), oldStats.getSectorsWrite()),
diff(newStats.getWriteTicks(), oldStats.getWriteTicks()),
diff(newStats.getNumberOfIOInMs(), oldStats.getNumberOfIOInMs()));
}
}
private static long diff(long newVal, long oldVal) {
if (newVal >= oldVal) {
return newVal - oldVal;
} else {
// If values are wrapped, adding max val to it and subtracting
_log.debug("New value is wrapped. newVal: {} oldVal: {}", newVal, oldVal);
return newVal + (Long.MAX_VALUE - oldVal);
}
}
private static UnsignedLong diff(UnsignedLong newVal, UnsignedLong oldVal) {
if (newVal.compareTo(oldVal) >= 0) {
return newVal.minus(oldVal);
} else {
// If values are wrapped, adding max val to it and subtracting
_log.debug("New value is wrapped. newVal: {} oldVal: {}", newVal, oldVal);
return UnsignedLong.MAX_VALUE.minus(oldVal).plus(newVal);
}
}
/**
* Calculates and returns the value/sec.
*/
protected static double getRate(long val, double deltaMS) {
_log.debug("Calculating per sec for val: {} with delta: {}", val, deltaMS);
return deltaMS > 0 ? (1000 * val / deltaMS) : 0;
}
/**
* Returns the cpu time spent in MS. If interval is > 0 delta value is returned.
* This is used to calculate per sec values.
*/
protected static double getCPUTimeDeltaMS(CPUStats oldCPUStats, CPUStats
newCPUStats) {
if (oldCPUStats != null) {
double statsDiff;
UnsignedLong oldTotal = oldCPUStats.getUserMode().plus(
oldCPUStats.getSystemMode()
.plus(oldCPUStats.getIdle().plus(oldCPUStats.getIowait())));
if (newCPUStats != null) {
UnsignedLong newTotal = newCPUStats.getUserMode().plus(
newCPUStats.getSystemMode().plus(
newCPUStats.getIdle().plus(newCPUStats.getIowait())));
statsDiff = newTotal.minus(oldTotal).doubleValue();
} else {
statsDiff = oldTotal.doubleValue();
}
return ProcStats.getCPUCount() > 0 ? 1000.0 * statsDiff / ProcStats
.getCPUCount() / HZ : 0;
} else {
return 0;
}
}
}