/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.voltdb.VoltDB.Configuration;
import org.voltdb.client.Client;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientUtils;
import org.voltdb.common.Constants;
import org.voltdb.compiler.VoltProjectBuilder;
import org.voltdb.snmp.DummySnmpTrapSender;
import org.voltdb.utils.FakeStatsProducer;
import org.voltdb.utils.MiscUtils;
import org.voltdb.utils.PlatformProperties;
import org.voltdb.utils.SystemStatsCollector;
import org.voltdb.utils.SystemStatsCollector.Datum;
import junit.framework.Assert;
import junit.framework.TestCase;
public class TestMemoryResourceMonitor extends TestCase
{
private static final int MONITORING_INTERVAL = 2;
private static final int DEFAULT_MONITORING_INTERVAL = 60;
private ServerThread m_localServer;
private VoltDB.Configuration m_config;
private Client m_client;
private TestStatsProducer m_mockStatsProducer;
public void setUpServer(String rssLimit, boolean setMonitoringInterval) throws Exception
{
VoltProjectBuilder builder = new VoltProjectBuilder();
if (rssLimit!=null) {
builder.setRssLimit(rssLimit);
}
if (setMonitoringInterval) {
builder.setResourceCheckInterval(MONITORING_INTERVAL);
}
boolean success = builder.compile(Configuration.getPathToCatalogForTest("resourcemonitor.jar"), 1, 1, 0);
assert(success);
MiscUtils.copyFile(builder.getPathToDeployment(), Configuration.getPathToCatalogForTest("resourcemonitor.xml"));
m_config = new VoltDB.Configuration();
m_config.m_pathToCatalog = Configuration.getPathToCatalogForTest("resourcemonitor.jar");
m_config.m_pathToDeployment = Configuration.getPathToCatalogForTest("resourcemonitor.xml");
m_localServer = new ServerThread(m_config);
m_localServer.start();
m_localServer.waitForInitialization();
m_mockStatsProducer = new TestStatsProducer();
SystemStatsCollector.setFakeStatsProducer(m_mockStatsProducer);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
m_client = ClientFactory.createClient();
m_client.createConnection("localhost:" + m_config.m_adminPort);
}
@Override
public void tearDown() throws Exception {
if (m_client != null) {
m_client.close();
}
if (m_localServer != null) {
m_localServer.shutdown();
}
}
public void testBadRssValues() throws Exception {
String[] badValues = { "xx%",
"-1%",
"100%",
"150%",
"40.3%",
"-20",
"abc"
};
HealthMonitor monitor = new HealthMonitor(null, new DummySnmpTrapSender());
for (int i=0; i<badValues.length; i++) {
try {
monitor.getMemoryLimitSize(badValues[i]);
fail("Must have failed for bad memory limit value " + badValues[i]);
} catch(IllegalArgumentException e) {
// expected
}
}
}
public void testGoodRssValues() throws Exception {
long totalSize = PlatformProperties.getPlatformProperties().ramInMegabytes*1048576L;
Map<String, Double> configToExpectedRss = new HashMap<>();
configToExpectedRss.put("0%", 0.0);
configToExpectedRss.put("1%", totalSize/100.0);
configToExpectedRss.put("15%", totalSize*15/100.0);
configToExpectedRss.put("40", 40.0*1073741824L);
configToExpectedRss.put("1.5", 1.5*1073741824L);
HealthMonitor monitor = new HealthMonitor(null, new DummySnmpTrapSender());
for (String str : configToExpectedRss.keySet()) {
Assert.assertEquals(configToExpectedRss.get(str), monitor.getMemoryLimitSize(str));
}
}
public void testDefaultRssLimit() throws Exception
{
setUpServer(null, true);
m_mockStatsProducer.m_rss = Double.valueOf(PlatformProperties.getPlatformProperties().ramInMegabytes*1048576L*79/100.0).longValue();
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
}
public void testExceedDefaultLimit() throws Exception
{
setUpServer(null, true);
m_mockStatsProducer.m_rss = Double.valueOf(PlatformProperties.getPlatformProperties().ramInMegabytes*1048576L*85/100.0).longValue();
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.PAUSED, VoltDB.instance().getMode());
}
// Disabling this test because it takes long. Enable it and run manually to test.
public void notestNoMonitoringInterval() throws Exception
{
setUpServer("0.5", false);
// Wait for monitoring interval time and verify server is still in running mode
m_mockStatsProducer.m_rss = 2048L*1024*1024;
resumeAndWait(DEFAULT_MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
}
public void testLimitNotExceeded() throws Exception
{
setUpServer("0.5", true);
m_mockStatsProducer.m_rss = 10*1024*1024;
assertEquals(m_mockStatsProducer.m_rss, SystemStatsCollector.getRSSMB());
// Wait for monitoring interval time and verify server is still in running mode
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
}
public void testLimitNotExceededPerc() throws Exception
{
setUpServer("90%", true);
m_mockStatsProducer.m_rss = Double.valueOf(PlatformProperties.getPlatformProperties().ramInMegabytes*1048576L*80/100.0).longValue();
assertEquals(m_mockStatsProducer.m_rss, SystemStatsCollector.getRSSMB());
// Wait for monitoring interval time and verify server is still in running mode
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
}
public void testLimitExceededWithResumePauseAgain() throws Exception
{
setUpServer("0.5", true);
// Go above limit, wait for more than configured amt of time and verify server is paused
m_mockStatsProducer.m_rss = 512*1024*1024;
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.PAUSED, VoltDB.instance().getMode());
// Resume and verify that server again goes into paused.
m_client.callProcedure("@Resume");
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.PAUSED, VoltDB.instance().getMode());
}
public void testLimitExceededWithResume() throws Exception
{
setUpServer("0.5", true);
// Go above limit, wait for more than configured amt of time and verify server is paused
m_mockStatsProducer.m_rss = 512*1024*1024;
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.PAUSED, VoltDB.instance().getMode());
// Don't go above limit in mock, resume and make sure server does not go back into paused.
m_mockStatsProducer.m_rss = 1024*1024;
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
}
public void testLimitExceededWithResumePerc() throws Exception
{
setUpServer("90%", true);
// Go above limit, wait for more than configured amt of time and verify server is paused
m_mockStatsProducer.m_rss = Double.valueOf(PlatformProperties.getPlatformProperties().ramInMegabytes*1048576L*95/100.0).longValue();
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.PAUSED, VoltDB.instance().getMode());
// Don't go above limit in mock, resume and make sure server does not go back into paused.
m_mockStatsProducer.m_rss = Double.valueOf(PlatformProperties.getPlatformProperties().ramInMegabytes*1048576L*80/100.0).longValue();
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
}
public void testCatalogUpdate_PauseAfterUpdate() throws Exception
{
setUpServer(null, true); // set up server with no rss limit
m_mockStatsProducer.m_rss = 2048L*1024*1024;
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
// update server with rss limit
String newDepFile = getDeploymentPathWithRss("1");
String depBytes = new String(ClientUtils.fileToBytes(new File(newDepFile)), Constants.UTF8ENCODING);
VoltTable[] results = m_client.callProcedure("@UpdateApplicationCatalog", null, depBytes).getResults();
assertTrue(results.length == 1);
Thread.sleep(5000); // wait to make sure new deployment file takes effect
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.PAUSED, VoltDB.instance().getMode());
}
public void testCatalogUpdate_ResumeAfterUpdate() throws Exception
{
setUpServer("0.5", true); // set up server with rss limit
m_mockStatsProducer.m_rss = 2048L*1024*1024;
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.PAUSED, VoltDB.instance().getMode());
// update server with rss limit
String newDepFile = getDeploymentPathWithRss("0");
String depBytes = new String(ClientUtils.fileToBytes(new File(newDepFile)), Constants.UTF8ENCODING);
VoltTable[] results = m_client.callProcedure("@UpdateApplicationCatalog", null, depBytes).getResults();
assertTrue(results.length == 1);
Thread.sleep(5000); // wait to make sure new deployment file takes effect
resumeAndWait(MONITORING_INTERVAL+1);
assertEquals(OperationMode.RUNNING, VoltDB.instance().getMode());
}
private String getDeploymentPathWithRss(String rssLimit) throws Exception
{
VoltProjectBuilder builder = new VoltProjectBuilder();
builder.setRssLimit(rssLimit);
builder.setResourceCheckInterval(MONITORING_INTERVAL);
boolean success = builder.compile(Configuration.getPathToCatalogForTest("updatedresourcemonitor.jar"), 1, 1, 0);
assert(success);
MiscUtils.copyFile(builder.getPathToDeployment(), Configuration.getPathToCatalogForTest("updatedresourcemonitor.xml"));
return Configuration.getPathToCatalogForTest("updatedresourcemonitor.xml");
}
// time in seconds
private void resumeAndWait(long time) throws Exception
{
// first wait for system stats collector interval of 5 seconds
// to make sure that a stats collection is run.
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis()-startTime < 5000) {
try { Thread.sleep(5000); } catch(InterruptedException e) { }
}
m_client.callProcedure("@Resume");
// now sleep for specified time
startTime = System.currentTimeMillis();
while (System.currentTimeMillis()-startTime < time*1000) {
try { Thread.sleep(time*1000); } catch(InterruptedException e) { }
}
}
private static class TestStatsProducer implements FakeStatsProducer
{
volatile long m_rss;
@Override
public Datum getCurrentStatsData()
{
return new Datum(m_rss);
}
}
}