/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.monitoring.system.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.sigar.FileSystem; import org.hyperic.sigar.FileSystemUsage; import org.hyperic.sigar.Humidor; import org.hyperic.sigar.ProcState; import org.hyperic.sigar.Sigar; import org.hyperic.sigar.SigarException; import org.hyperic.sigar.SigarProxy; import org.hyperic.sigar.ptql.ProcessFinder; import org.osgi.framework.BundleContext; import de.rcenvironment.core.monitoring.system.api.OperatingSystemException; import de.rcenvironment.core.monitoring.system.api.SystemMonitoringDataService; import de.rcenvironment.core.monitoring.system.api.model.ProcessInformation; /** * Implementation of {@link SystemMonitoringDataService}. * * @author David Scholz * @author Robert Mischke */ public class SystemMonitoringDataServiceImpl implements SystemMonitoringDataService { private static final Sigar SIGAR = new Sigar(); private static final int CONVERSION_FACTOR_1K = 1024; private static final double CONVERSION_FACTOR_PERCENT = 100; private static final int SIG_ID_KILL = 15; private static final int SIG_ID_FORCE_KILL = -9; private static final String NO_SUCH_PROCESS_MESSAGE = "No such process"; private static final String ACCESS_DENIED_EXCEPTION = "Access is denied."; private static final Log LOGGER = LogFactory.getLog(SystemMonitoringDataServiceImpl.class); private static final int TOTAL_CPU_NAN_MAX_RETRY_COUNT = 50; // maximum observed in local testing was ~20 retries at 1 msec wait private static final int TOTAL_CPU_NAN_RETRY_WAIT_MSEC = 1; private long cachedTotalSystemRam; protected void activate(BundleContext bundleContext) { try { cachedTotalSystemRam = fetchTotalSystemRAM(); } catch (OperatingSystemException e) { // TODO or re-throw the exception and refuse to start instead? cachedTotalSystemRam = 0; LOGGER.error("Failed to initialize total system RAM: " + e.toString()); } } @Override public double getTotalCPUUsage() throws OperatingSystemException { try { double cpuUsage; final SigarProxy sigarProxy = Humidor.getInstance().getSigar(); cpuUsage = sigarProxy.getCpuPerc().getCombined(); if (Double.isNaN(cpuUsage)) { int retryCount = 1; while (true) { cpuUsage = sigarProxy.getCpuPerc().getCombined(); if (!Double.isNaN(cpuUsage)) { // valid LOGGER.debug("Fetched valid CPU load data after " + retryCount + " immediate retries"); break; } if (retryCount < TOTAL_CPU_NAN_MAX_RETRY_COUNT) { try { Thread.sleep(TOTAL_CPU_NAN_RETRY_WAIT_MSEC); } catch (InterruptedException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_TOTAL_CPU_USAGE, "Interrupted while waiting for CPU load data retry"); } retryCount++; } else { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_TOTAL_CPU_USAGE, "Failed to fetch valid CPU load data even after " + retryCount + " immediate retries"); } } } return SystemMonitoringUtils.clampToPercentageOrNAN(cpuUsage); } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_TOTAL_CPU_USAGE); } } @Override public double getProcessCPUUsage(Long pid) throws OperatingSystemException { double processCPUusage = 0; int watch = 0; try { if (pid == null) { throw new OperatingSystemException(OperatingSystemException.ErrorType.NO_SUCH_PROCESS); } // TODO review this for NaN handling; this only tries to prevent 0.0 values do { synchronized (SIGAR) { processCPUusage = SIGAR.getProcCpu(pid).getPercent(); } watch++; if (watch == 10) { return processCPUusage; } } while (processCPUusage == 0.0); } catch (SigarException e) { if (e.getMessage().contains(NO_SUCH_PROCESS_MESSAGE)) { throw createNoSuchProcessException(pid); } else if (e.getMessage().contains(ACCESS_DENIED_EXCEPTION)) { throw createAccessDeniedException(pid); } else { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_CPU_PROCESS_USAGE, " of process with pid: " + pid); } } synchronized (SIGAR) { try { processCPUusage /= SIGAR.getCpuList().length; } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GET_CPU_LIST); } } return SystemMonitoringUtils.clampToPercentageOrNAN(processCPUusage); } @Override public double getReportedCPUIdle() throws OperatingSystemException { try { double idle = 0.0; synchronized (SIGAR) { idle = SIGAR.getCpuPerc().getIdle(); } return SystemMonitoringUtils.clampToPercentageOrNAN(idle); } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_IDLE); } } @Override public long getTotalSystemRAM() throws OperatingSystemException { return cachedTotalSystemRam; } @Override public long getProcessRAMUsage(Long pid) throws OperatingSystemException { try { if (pid == null) { throw new OperatingSystemException(OperatingSystemException.ErrorType.NO_SUCH_PROCESS); } return Humidor.getInstance().getSigar().getProcMem(pid).getResident() / (CONVERSION_FACTOR_1K * CONVERSION_FACTOR_1K); } catch (SigarException e) { if (e.getMessage().contains(NO_SUCH_PROCESS_MESSAGE)) { throw createNoSuchProcessException(pid); } else if (e.getMessage().contains(ACCESS_DENIED_EXCEPTION)) { throw createAccessDeniedException(pid); } else { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_RAM_PROCESS_USAGE, " of process with pid: " + pid); } } } @Override public long getTotalUsedRAM() throws OperatingSystemException { return (long) (cachedTotalSystemRam * getTotalUsedRAMPercentage()); } @Override public long getFreeRAM() throws OperatingSystemException { return (long) (cachedTotalSystemRam * (1.0 - getTotalUsedRAMPercentage())); } @Override public double getTotalUsedRAMPercentage() throws OperatingSystemException { try { return Humidor.getInstance().getSigar().getMem().getUsedPercent() / CONVERSION_FACTOR_PERCENT; } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_TOTAL_RAM_PERCENTAGE); } } @Override public double getProcessRAMPercentage(Long pid) throws OperatingSystemException { long totalRam = getTotalSystemRAM(); if (totalRam == 0) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_TOTAL_RAM_PERCENTAGE); } long ram = getProcessRAMUsage(pid); return ram / totalRam; } @Override public long getUsedLocalDiskSpace() throws OperatingSystemException { return getFileSystemUsage().getUsed(); } @Override public long getFreeLocalDiskSpace() throws OperatingSystemException { return getFileSystemUsage().getFree(); } @Override public Map<Long, String> getProcesses() throws OperatingSystemException { try { final Map<Long, String> processMap = new HashMap<>(); final List<Long> processList = Arrays.<Long> asList(ArrayUtils.toObject(Humidor.getInstance().getSigar().getProcList())); for (Long pid : processList) { synchronized (SIGAR) { // this is necessary! long[] pids = ProcessFinder.find(SIGAR, PTQLWrapper.createQuery().createQueryString(PTQLWrapper.pid(), PTQLWrapper.eq()) + pid); for (long process : pids) { processMap.put(process, Humidor.getInstance().getSigar().getProcState(process).getName()); } } } return processMap; } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GET_PROCESS_LIST); } } @Override public Map<Long, String> getChildProcessesAndIds(Long pid) throws OperatingSystemException { if (pid == null) { return Collections.emptyMap(); } final List<ProcessInformation> childList; childList = getFullChildProcessInformation(pid); final Map<Long, String> childMap = new HashMap<>(); if (!childList.isEmpty()) { for (ProcessInformation process : childList) { childMap.put(process.getPid(), process.getName()); } } return childMap; } @Override public void kill(Long pid, Boolean force) throws OperatingSystemException { try { LOGGER.info("Killing process with pid: " + pid); if (force) { synchronized (SIGAR) { SIGAR.kill(pid, SIG_ID_FORCE_KILL); } } else { synchronized (SIGAR) { SIGAR.kill(pid, SIG_ID_KILL); } } } catch (final SigarException e) { if (e.getMessage().contains("The parameter is incorrect.")) { LOGGER.error("Failed to shut down process caused by incorrect parameters. The process with pid: " + pid + " may already be dead."); return; } else { if (e.getMessage().contains(ACCESS_DENIED_EXCEPTION)) { throw createAccessDeniedException(pid); } throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_KILL_PROCESS, " with pid: " + pid); } } } private long fetchTotalSystemRAM() throws OperatingSystemException { try { final long ramValue = Humidor.getInstance().getSigar().getMem().getRam(); if (ramValue == 0) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_TOTAL_RAM); } return ramValue; } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GATHER_TOTAL_RAM); } } @Override public List<ProcessInformation> getFullChildProcessInformation(long pid) throws OperatingSystemException { long[] pids = null; List<ProcessInformation> children = null; synchronized (SIGAR) { try { pids = ProcessFinder.find(SIGAR, PTQLWrapper.createQuery().createQueryString(PTQLWrapper.statePPID(), PTQLWrapper.eq()) + pid); } catch (SigarException e) { if (e.getMessage().equals(ACCESS_DENIED_EXCEPTION)) { throw createAccessDeniedException(pid); } throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GET_CHILD_PROCESS_LIST, " of parent process with pid: " + pid); } } if (pids != null && pids.length > 0) { children = new ArrayList<>(); for (Long child : pids) { ProcessInformation p; try { p = new ProcessInformation(child, fetchProcessState(child).getName(), getFullChildProcessInformation(child), getProcessCPUUsage(child), getProcessRAMUsage(child)); } catch (OperatingSystemException e) { if (e.getErrorType().equals(OperatingSystemException.ErrorType.NO_SUCH_PROCESS)) { LOGGER.info("Couldn't find process with pid: " + child + ". Process might already be dead."); continue; } throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GET_CHILD_PROCESS_LIST, " of parent process with pid: " + pid); } children.add(p); } } if (children != null) { return children; } else { return Collections.emptyList(); } } private FileSystemUsage getFileSystemUsage() throws OperatingSystemException { FileSystem[] fileSystemList; try { fileSystemList = Humidor.getInstance().getSigar().getFileSystemList(); } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GET_FILE_SYSTEM); } FileSystemUsage usage = null; for (FileSystem fs : fileSystemList) { if (fs.getType() == FileSystem.TYPE_LOCAL_DISK) { try { usage = Humidor.getInstance().getSigar().getFileSystemUsage(fs.getDirName()); } catch (SigarException e) { throw new OperatingSystemException(OperatingSystemException.ErrorType.FAILED_TO_GET_FILE_SYSTEM_USAGE); } } } return usage; } @Override public ProcState fetchProcessState(long pid) throws OperatingSystemException { try { return Humidor.getInstance().getSigar().getProcState(pid); } catch (SigarException e) { throw new OperatingSystemException( OperatingSystemException.ErrorType.FAILED_TO_GET_PROCESS_STATE + " for process with pid: " + pid); } } private OperatingSystemException createAccessDeniedException(long pid) { return new OperatingSystemException(OperatingSystemException.ErrorType.ACCESS_DENIED, " to process with pid: " + pid + ". You may not have the appropriate permissions."); } private OperatingSystemException createNoSuchProcessException(long pid) { return new OperatingSystemException(OperatingSystemException.ErrorType.NO_SUCH_PROCESS, ". The process with pid: " + pid + " may not exist."); } }