/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.monitoring.system.internal; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.awt.Desktop; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecuteResultHandler; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteException; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.easymock.EasyMock; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; 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.FullSystemAndProcessDataSnapshot; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.testing.CommonTestOptions; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService; import de.rcenvironment.toolkit.modules.concurrency.api.RunnablesGroup; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; import de.rcenvironment.toolkit.modules.objectbindings.api.ObjectBindingsService; /** * Unit test for {@link SystemMonitoringDataServiceImpl}. * * @author David Scholz * @author Robert Mischke */ public class SystemMonitoringServiceImplTest { private static final Log LOGGER = LogFactory.getLog(SystemMonitoringDataServiceImpl.class); private static final long MAX_DELAY = 9000; /** * Expected exception for {@link OperatingSystemException}. */ @Rule public ExpectedException exception = ExpectedException.none(); private SystemMonitoringDataServiceImpl systemDataService; private SystemMonitoringAggregationServiceImpl aggregationService; /** * Common setup. * * @throws IOException on unexpected failure. * @throws OperatingSystemException on unexpected failure. */ @Before public void setUp() throws IOException, OperatingSystemException { systemDataService = new SystemMonitoringDataServiceImpl(); systemDataService.activate(EasyMock.createStrictMock(BundleContext.class)); aggregationService = new SystemMonitoringAggregationServiceImpl(); aggregationService.bindSystemMonitoringDataService(systemDataService); aggregationService.bindObjectBindingsService(EasyMock.createNiceMock(ObjectBindingsService.class)); aggregationService.bindAsyncTaskService(EasyMock.createNiceMock(AsyncTaskService.class)); aggregationService.activate(EasyMock.createStrictMock(BundleContext.class)); } /** * Common cleanup. */ @After public void tearDown() { } /** * * Test if service returns a faultless {@link FullSystemAndProcessDataSnapshot}. * * @throws OperatingSystemException on unexpected test failure. * */ @Test public void testMonitoringDataModelContent() throws OperatingSystemException { FullSystemAndProcessDataSnapshot model = aggregationService.getCompleteSnapshot(); boolean success = model != null && model.getNodeSystemRAM() != 0 && model.getRceProcessesInfo() != null && model.getRceSubProcesses() != null; assertTrue(success); } /** * * Test the caching mechanism in {@link SystemMonitoringDataServiceImpl}. * * @throws ExecutionException on execution failure. * @throws InterruptedException on thread interruption. * */ @Test public void testCachingMechanism() throws InterruptedException, ExecutionException { aggregationService.clearFullSnapshotCache(); final AsyncTaskService asyncTaskService = ConcurrencyUtils.getAsyncTaskService(); Future<FullSystemAndProcessDataSnapshot> firstCall = asyncTaskService.submit(new Callable<FullSystemAndProcessDataSnapshot>() { @Override @TaskDescription("Fetching monitoring data model...") public FullSystemAndProcessDataSnapshot call() throws Exception { return aggregationService.getCompleteSnapshot(); } }); Future<FullSystemAndProcessDataSnapshot> secCall = asyncTaskService.submit(new Callable<FullSystemAndProcessDataSnapshot>() { @Override @TaskDescription("Fetching monitoring data model...") public FullSystemAndProcessDataSnapshot call() throws Exception { return aggregationService.getCompleteSnapshot(); } }); try { assertEquals(firstCall.get(MAX_DELAY, TimeUnit.MILLISECONDS), secCall.get(MAX_DELAY, TimeUnit.MILLISECONDS)); } catch (TimeoutException e) { fail("Timed out!"); } } /** * Test thread safety of kill method. * * @throws ExecuteException if process execution fails. * @throws IOException if opening file fails. */ @Test public void testKillMethodThreadSafety() throws ExecuteException, IOException { final String processMarkerString = startDummyProcess(); final RunnablesGroup group = ConcurrencyUtils.getFactory().createRunnablesGroup(); final int threadCount = 16; for (int i = 0; i <= threadCount; i++) { final Runnable killTask = new Runnable() { @Override @TaskDescription("Killing test process...") public void run() { killDummyProcess(processMarkerString, true); } }; group.add(killTask); } group.executeParallel(); } /** * * Test for null values. * * @throws OperatingSystemException on unexpected failure. * */ @Test public void testNullValues() throws OperatingSystemException { assertTrue(systemDataService.getChildProcessesAndIds(null).isEmpty()); exception.expect(OperatingSystemException.class); exception.expectMessage(OperatingSystemException.ErrorType.NO_SUCH_PROCESS.getMessage()); systemDataService.getProcessRAMUsage(null); systemDataService.getProcessCPUUsage(null); } /** * Test the two different kill signums of {@link SystemMonitoringDataServiceImpl#kill(Long, Boolean)}. * * @throws IOException on unexpected failure. * @throws ExecuteException on unexpected failure. */ @Test public void testKillSignums() throws ExecuteException, IOException { String process = startDummyProcess(); killDummyProcess(process, true); process = startDummyProcess(); killDummyProcess(process, false); } /** * Test if CPU used and idle values are valid percentages, represented as 0..1. * * @throws OperatingSystemException on unexpected failure. */ @Test public void testCpuValuesAreValidOnSingleFetch() throws OperatingSystemException { double totalCpu = systemDataService.getTotalCPUUsage(); assertIsValidZeroToOneValue(totalCpu); double idle = systemDataService.getReportedCPUIdle(); assertIsValidZeroToOneValue(idle); } /** * Verifies that CPU load values are never NaN, even if polled rapidly. * <p> * Test background: The SIGAR/Humidor libraries seem to return NaN when CPU usage is polled in rapid succession. To ensure that * application code always sees valid data, the {@link SystemMonitoringDataService#getTotalCPUUsage()} method should retry/wait * accordingly, which is verified by this test case. * * @throws OperatingSystemException on unexpected failure. */ @Test public void testCpuValuesAreValidOnFrequentPolling() throws OperatingSystemException { final int n = CommonTestOptions.selectStandardOrExtendedValue(100, 1000); for (int i = 0; i < n; i++) { final double totalCpu = systemDataService.getTotalCPUUsage(); assertIsValidZeroToOneValue(totalCpu); } } /** * Test total ram usage with some tolerance. * * TODO review; what exactly is compared against each other here? * * @throws OperatingSystemException on unexpected failure. */ @Test public void testRamGathering() throws OperatingSystemException { long ram = systemDataService.getTotalSystemRAM(); long totalUsedRam = aggregationService.getCompleteSnapshot().getNodeRAMUsage(); double usedRamSigar = systemDataService.getTotalUsedRAMPercentage() * ram; double usedRamModel = totalUsedRam; final float tolerance = 0.5f; boolean equal = Math.abs(usedRamSigar / usedRamModel - 1.0f) <= tolerance; assertTrue(equal); } /** * * Test process gathering over time. * * @throws OperatingSystemException on unexpected failure. * */ @Test public void testProcessGatheringOverTime() throws OperatingSystemException { final int loop = CommonTestOptions.selectStandardOrExtendedValue(10, 100); for (int i = 0; i < loop; i++) { Map<Long, String> map = systemDataService.getProcesses(); boolean success = !map.isEmpty() && map != null; assertTrue(success); } } /** * * Operating system. * * @author David Scholz */ public enum OSType { /** * Operating system. */ Windows, MacOS, Linux, Other }; private OSType getOperatingSystemType() { String os = System.getProperty("os.name").toLowerCase(); if (os.contains("linux")) { return OSType.Linux; } else if (os.contains("windows")) { return OSType.Windows; } else if (os.contains("mac os") || os.contains("macos") || os.contains("darwin")) { return OSType.MacOS; } else { return OSType.Other; } } private void assertIsValidZeroToOneValue(double value) { assertFalse(Double.isNaN(value)); assertTrue(value >= 0.0); assertTrue(value <= 1.0); } private String startDummyProcess() throws ExecuteException, IOException { String line = ""; CommandLine commandLine; DefaultExecutor executor = new DefaultExecutor(); DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler(); switch (getOperatingSystemType()) { case Windows: line = "ping -n 1000 127.0.0.1"; commandLine = CommandLine.parse(line); executor.execute(commandLine, handler); executor.setStreamHandler(new PumpStreamHandler(null, null, null)); line = "PING"; break; case Linux: line = "ping -n 1000 127.0.0.1"; commandLine = CommandLine.parse(line); executor.setStreamHandler(new PumpStreamHandler(null, null, null)); executor.execute(commandLine, handler); line = "ping"; break; case MacOS: Desktop.getDesktop().open(new File("Terminal.app")); break; default: LOGGER.error("Failed to determine operating system."); break; } return line; } private void killDummyProcess(String processMarkerString, boolean force) { Map<Long, String> processes = null; try { processes = systemDataService.getProcesses(); } catch (OperatingSystemException e1) { LOGGER.error(e1); } if (processes != null) { for (Map.Entry<Long, String> process : processes.entrySet()) { if (process.getValue().contains(processMarkerString)) { LOGGER.info("Found " + processMarkerString + " with id [" + process.getKey() + "] - killing it"); try { systemDataService.kill(process.getKey(), force); } catch (OperatingSystemException e) { if (e.getErrorType().equals(OperatingSystemException.ErrorType.ACCESS_DENIED)) { continue; } } } } } } }