/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.systemservices.impl.healthmonitor; import com.emc.storageos.systemservices.exceptions.SyssvcException; import com.emc.storageos.systemservices.exceptions.SyssvcInternalException; import com.emc.storageos.systemservices.impl.healthmonitor.models.CPUStats; import com.emc.storageos.services.util.Exec; import com.emc.vipr.model.sys.healthmonitor.DataDiskStats; import com.emc.vipr.model.sys.healthmonitor.DiskStats; import com.emc.vipr.model.sys.healthmonitor.ProcModels; import com.google.common.primitives.UnsignedLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Provides static methods to retrieve info from /proc.. */ public class ProcStats extends ProcModels implements StatConstants { private static final Logger _log = LoggerFactory.getLogger(ProcStats.class); /** * Retrieves CPU stats from /proc/stat file. */ public static CPUStats getCPUStats() throws IOException, SyssvcInternalException { String[] fileData = FileReadUtil.readLines(PROC_STAT); for (String line : fileData) { if (line != null && line.startsWith("cpu")) { String[] stats = line.trim().split(SPACE_VALUE); UnsignedLong systemMode = UnsignedLong.valueOf(stats[3]); if (stats.length > 7) { systemMode = systemMode.plus(UnsignedLong.valueOf(stats[6]) .plus(UnsignedLong.valueOf(stats[7]))); } return new CPUStats(UnsignedLong.valueOf(stats[1]).plus (UnsignedLong.valueOf(stats[2])), systemMode, UnsignedLong.valueOf(stats[4]), UnsignedLong.valueOf(stats[5])); } } throw SyssvcException.syssvcExceptions.syssvcInternalError("CPU stats not found."); } /** * Returns number of CPUs */ public static int getCPUCount() { try { String[] fileData = FileReadUtil.readLines(CPU_INFO); int ncpu = 0; for (String line : fileData) { if (line != null && line.startsWith("processor")) { ncpu++; } } _log.info("Number of processors: {}", ncpu); return ncpu; } catch (Exception e) { _log.error("Error occurred while getting CPU count: {}", e); } return 0; } /** * Returns CPU frequence */ public static float getCPUFrequence() { float cpuFreq = 0; try { String[] fileData = FileReadUtil.readLines(CPU_INFO); for (String line : fileData) { if (line != null && line.contains("MHz")) { String[] cpuFreqData = line.split(":"); cpuFreq += Float.valueOf(cpuFreqData[1]); } } } catch (Exception e) { _log.error("Error occurred while getting CPU frequence: {}", e); } return cpuFreq; } /** * Returns the number of files in the File Descriptors from /proc/[pid]/fd * directory. Default to 0 if null. */ public static int getFileDescriptorCntrs(String pid) { File file = new File(String.format(FD_DIR, pid)); if (file.exists()) { File[] files = file.listFiles(); return files != null ? files.length : 0; } else { return 0; } } /** * Returns service (i.e. syssvc, dbsvc, coordinatorsvc, * etc) name from /proc/{pid}/cmdline file and adds it to the passed * in serviceStats object. Service name should be prefixed with * ACCEPTABLE_PID_COMMAND_PREFIXES. * * @param pid process id from which service stats is extracted */ public static String getServiceName(String pid) throws IOException, SyssvcInternalException { // validate that CMDLINE file exists. File cmdLineFile = new File(String.format(CMDLINE_FILE, pid)); if (cmdLineFile.exists()) { // The /proc/[pid]/cmdline file exists and is not the /proc/self/ // directory, // continue trying to get the service type. String cmdLineString = FileReadUtil.readFirstLine(String.format (CMDLINE_FILE, pid)); // if the /proc/[pid]/cmdline contains an acceptable command from the // list, we // should process this pid. // split the string delimited by null. String[] splitCmdLineString = cmdLineString.split(NULL_VALUES); for (String acceptablePrefix : ACCEPTABLE_PID_COMMAND_PREFIXES) { if (splitCmdLineString[0].startsWith(acceptablePrefix)) { return splitCmdLineString[0].substring (acceptablePrefix.length()); } } } throw SyssvcException.syssvcExceptions.serviceNameNotFoundException(pid); } /** * Reads the /proc/diskstats data and builds a map using the desired counters. * * @return Map with disk id as key. */ public static Map<String, DiskStats> getDiskStats() throws IOException, SyssvcInternalException { Map<String, DiskStats> diskStatsMap = new HashMap<String, DiskStats>(); String[] fileData = FileReadUtil.readLines(DISK_STATS); for (String line : fileData) { String[] diskStatArray = line.trim().split(SPACE_VALUE); if (!ACCEPTABLE_DISK_IDS.contains(diskStatArray[2])) { continue; } // verify if disk stats array has 13 elements if (diskStatArray.length < 13) { throw SyssvcException.syssvcExceptions.syssvcInternalError( "Disk stats file is invalid."); } DiskStats diskStats = new DiskStats(diskStatArray[2], Long.parseLong(diskStatArray[3]), Long.parseLong(diskStatArray[5]), Long.parseLong(diskStatArray[6]), Long.parseLong(diskStatArray[7]), Long.parseLong(diskStatArray[9]), Long.parseLong(diskStatArray[10]), Long.parseLong(diskStatArray[12])); diskStatsMap.put(diskStats.getDiskId(), diskStats); } return diskStatsMap; } /** * Loads the memory information from /proc/meminfo. We are only concerned with the * first three rows. */ public static MemoryStats getMemoryStats() { try { String[] fileData = FileReadUtil.readLines(MEM_INFO); Matcher matcher; Pattern pattern = Pattern.compile("[a-zA-Z]*:[ ]*([\\d]*)[ ]{1}kB"); matcher = pattern.matcher(fileData[0]); String memTotal = matcher.find() ? matcher.group(1) : null; matcher = pattern.matcher(fileData[1]); String memFree = matcher.find() ? matcher.group(1) : null; matcher = pattern.matcher(fileData[2]); String memBuffers = matcher.find() ? matcher.group(1) : null; matcher = pattern.matcher(fileData[3]); String memCached = matcher.find() ? matcher.group(1) : null; return new MemoryStats(Long.parseLong(memTotal), Long.parseLong(memFree), Long.parseLong(memBuffers), Long.parseLong(memCached)); } catch (Exception e) { _log.error("Error occurred while getting node memory stats: {}", e); } return null; } /** * Populates node's load average data information from /proc/loadavg. */ public static LoadAvgStats getLoadAvgStats() { try { String loadAvgDataLine = FileReadUtil.readFirstLine(LOAD_AVG); // split string into string[] delimited by a space. String[] loadAvgData = loadAvgDataLine.split(SPACE_VALUE); return new LoadAvgStats(Double.parseDouble(loadAvgData[0]), Double.parseDouble (loadAvgData[1]), Double.parseDouble (loadAvgData[2])); } catch (Exception e) { _log.error("Error occurred while getting load avg stats: {}", e); } return null; } /** * Loads stats from /proc/[pid]/stat and /proc/[pid]/statm files */ public static ProcessStatus getProcStats(String pid) { // using a space as the delimeter. String data; // get the /proc/[pid]/stat as a string and them split into string array // using a space as the delimeter. try { data = FileReadUtil.readFirstLine(String.format(STAT_FILE, pid)); String[] procStatData = data.split(SPACE_VALUE); long startTimeSecs = UnsignedLong.valueOf(procStatData[STAT_STARTTIME]).dividedBy (UnsignedLong.fromLongBits(HZ)).longValue(); long currTimeSecs = new Date().getTime() / 1000; long upTimeSecs = currTimeSecs - (getBootTime() + startTimeSecs); return new ProcessStatus(upTimeSecs, Long.parseLong(procStatData[STAT_NUM_THREADS]), startTimeSecs, Integer.parseInt(procStatData[STAT_PID]), Long.parseLong(procStatData[STAT_RSS]) * getPageSize(), Long.parseLong(procStatData[STAT_VSIZE])); } catch (Exception e) { _log.error("Error occurred while getting service stats from /stats file: {}", e); } return null; } /** * Returns boot time in seconds since the Epoch */ private static long getBootTime() throws IOException { String[] fileData = FileReadUtil.readLines(PROC_STAT); for (String line : fileData) { if (line != null && line.startsWith("btime")) { String[] bootLine = line.trim().split(SPACE_VALUE); if (bootLine.length > 1) { _log.info("Boot time in seconds: {}", bootLine[1]); return Long.parseLong(bootLine[1]); } } } return 0; } /** * Gets used, available size for data and root with the help of df command. */ public static DataDiskStats getDataDiskStats() { final String[] cmd = { DF_COMMAND }; Exec.Result result = Exec.sudo(DF_COMMAND_TIMEOUT, cmd); if (!result.exitedNormally() || result.getExitValue() != 0) { _log.error("getDataDiskStats() is unsuccessful. Command exit value is: {}", result.getExitValue()); return null; } _log.info("df result: {}", result.getStdOutput()); return parseDFResults(result.getStdOutput()); } /** * Parses the input string and returns data disk stats object. */ private static DataDiskStats parseDFResults(String dfResult) { String[] lines = dfResult.split("\n"); DataDiskStats dataDiskStats = new DataDiskStats(); for (String line : lines) { String[] v = line.split(SPACE_VALUE); if (v != null && v.length > 5) { if ("/".equals(v[5].trim())) { dataDiskStats.setRootUsedKB(Long.parseLong(v[2])); dataDiskStats.setRootAvailKB(Long.parseLong(v[3])); } else if ("/data".equals(v[5].trim())) { dataDiskStats.setDataUsedKB(Long.parseLong(v[2])); dataDiskStats.setDataAvailKB(Long.parseLong(v[3])); } } } return dataDiskStats; } /** * Returns default page size of 4k for now, need to be enhanced to return actual page size. * * @return */ private static int getPageSize() { return DEFAULT_PAGE_SIZE; } }