blob: e49dff3b9a340714b5daae7ddb92675aad46eeb9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.internal.statistics;
import static java.io.File.separator;
import static java.lang.Byte.MAX_VALUE;
import static java.lang.System.currentTimeMillis;
import static java.lang.System.getProperty;
import static java.lang.System.setProperty;
import static java.util.Arrays.fill;
import static org.apache.geode.distributed.ConfigurationProperties.ARCHIVE_DISK_SPACE_LIMIT;
import static org.apache.geode.distributed.ConfigurationProperties.ARCHIVE_FILE_SIZE_LIMIT;
import static org.apache.geode.distributed.ConfigurationProperties.ENABLE_TIME_STATISTICS;
import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_ARCHIVE_FILE;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLE_RATE;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLING_ENABLED;
import static org.apache.geode.internal.GemFireVersion.getBuildId;
import static org.apache.geode.internal.GemFireVersion.getGemFireVersion;
import static org.apache.geode.internal.GemFireVersion.getSourceDate;
import static org.apache.geode.internal.cache.control.HeapMemoryMonitor.getTenuredMemoryPoolMXBean;
import static org.apache.geode.internal.cache.control.HeapMemoryMonitor.getTenuredPoolStatistics;
import static org.apache.geode.internal.net.SocketCreator.getHostName;
import static org.apache.geode.internal.net.SocketCreator.getLocalHost;
import static org.apache.geode.internal.statistics.HostStatSampler.TEST_FILE_SIZE_LIMIT_IN_KB_PROPERTY;
import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestName;
import org.apache.geode.Statistics;
import org.apache.geode.StatisticsType;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.internal.statistics.GemFireStatSampler.LocalStatListenerImpl;
import org.apache.geode.internal.statistics.platform.ProcessStats;
import org.apache.geode.internal.stats50.VMStats50;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.test.junit.categories.StatisticsTest;
/**
* Integration tests for {@link GemFireStatSampler}.
*
* @since GemFire 7.0
*/
@Category({StatisticsTest.class})
public class GemFireStatSamplerIntegrationTest extends StatSamplerTestCase {
private static final Logger logger = LogService.getLogger();
private static final int STAT_SAMPLE_RATE = 1000;
private InternalDistributedSystem system;
private File testDir;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public TestName testName = new TestName();
@Before
public void setUp() throws Exception {
testDir = temporaryFolder.getRoot();
assertThat(testDir).exists();
}
/**
* Removes the loner DistributedSystem at the end of each test.
*/
@After
public void tearDown() throws Exception {
System.clearProperty(GemFireStatSampler.TEST_FILE_SIZE_LIMIT_IN_KB_PROPERTY);
disconnect();
}
@Test
public void testInitialization() throws Exception {
connect(createGemFireProperties());
GemFireStatSampler statSampler = getGemFireStatSampler();
assertThat(statSampler.waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
assertThat(statSampler.getArchiveFileSizeLimit())
.as("archive file size limit")
.isZero();
assertThat(statSampler.getArchiveDiskSpaceLimit())
.as("archive disk space limit")
.isZero();
assertThat(statSampler.getSampleRate())
.as("sample rate")
.isEqualTo(STAT_SAMPLE_RATE);
assertThat(statSampler.isSamplingEnabled())
.as("sampling is enabled")
.isTrue();
int statsCount = statSampler.getStatisticsManager().getStatisticsCount();
assertThat(statSampler.getStatistics().length)
.as("statistics length")
.isEqualTo(statsCount);
assertThat(statSampler.getSystemStartTime())
.as("system start time")
.isLessThanOrEqualTo(currentTimeMillis());
assertThat(statSampler.getSystemDirectoryPath())
.as("system directory path")
.isEqualTo(getHostName(getLocalHost()));
assertThat(statSampler.getVMStats())
.as("vm stats")
.isInstanceOf(VMStats50.class);
/*
* NOTE: VMStats50 is not an instance of Statistics but instead its instance contains 3
* instances of Statistics: 1) vmStats 2) heapMemStats 3) nonHeapMemStats
*/
Method getProcessStats = getGemFireStatSampler().getClass().getMethod("getProcessStats");
assertThat(getProcessStats)
.withFailMessage("gemfire stat sampler has no getProcessStats method")
.isNotNull();
}
@Test
public void testBasicProcessStats() throws Exception {
final String osName = getProperty("os.name", "unknown");
assumeThat(osName)
.as("os name")
.doesNotContain("Windows");
connect(createGemFireProperties());
GemFireStatSampler statSampler = getGemFireStatSampler();
assertThat(statSampler.waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
ProcessStats processStats = statSampler.getProcessStats();
AllStatistics allStats = new AllStatistics(statSampler);
if (osName.startsWith("Linux")) {
assertThat(processStats)
.withFailMessage("ProcessStats were not created on" + osName)
.isNotNull();
assertThat(OsStatisticsProvider.build().osStatsSupported())
.as("os stats are available on Linux")
.isTrue();
assertThat(allStats.containsStatisticsType("LinuxProcessStats"))
.as("Linux stats include statistics type named LinuxProcessStats")
.isTrue();
assertThat(allStats.containsStatisticsType("LinuxSystemStats"))
.as("Linux stats include statistics type named LinuxSystemStats")
.isTrue();
} else {
assertThat(processStats)
.withFailMessage("ProcessStats were created on" + osName)
.isNull();
}
String productDesc = statSampler.getProductDescription();
assertThat(productDesc)
.as("product description")
.contains(getGemFireVersion())
.contains(getBuildId())
.contains(getSourceDate());
}
/**
* Tests that the configured archive file is created and exists.
*/
@Test
public void testArchiveFileExists() throws Exception {
final String dir = testDir.getAbsolutePath();
final String archiveFileName = dir + separator + testName.getMethodName() + ".gfs";
final File archiveFile1 =
new File(dir + separator + testName.getMethodName() + ".gfs");
Properties props = createGemFireProperties();
props.setProperty(STATISTIC_ARCHIVE_FILE, archiveFileName);
connect(props);
GemFireStatSampler statSampler = getGemFireStatSampler();
assertThat(statSampler.waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
final File archiveFile = statSampler.getArchiveFileName();
assertThat(archiveFile).isNotNull();
assertThat(archiveFile)
.as("archive file")
.isEqualTo(archiveFile1);
waitForFileToExist(archiveFile, 5000, 10);
assertThat(archiveFile.getName())
.as("archive file name")
.isSubstringOf(archiveFileName);
}
/**
* Tests the statistics sample rate within an acceptable margin of error.
*/
@Test
public void testSampleRate() throws Exception {
connect(createGemFireProperties());
GemFireStatSampler statSampler = getGemFireStatSampler();
assertThat(statSampler.waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
assertThat(statSampler.getSampleRate())
.as("sample rate")
.isEqualTo(STAT_SAMPLE_RATE);
assertThat(getStatisticsManager().getStatListModCount())
.as("stat list mod count")
.isNotZero();
List<Statistics> statistics = getStatisticsManager().getStatsList();
assertThat(statistics).isNotNull();
assertThat(statistics.size())
.as("statistics size")
.isNotZero();
StatisticsType statSamplerType = getStatisticsManager().findType("StatSampler");
Statistics[] statsArray = getStatisticsManager().findStatisticsByType(statSamplerType);
assertThat(statsArray.length)
.as("stats array length")
.isEqualTo(1);
final Statistics statSamplerStats = statsArray[0];
final int initialSampleCount = statSamplerStats.getInt("sampleCount");
final int expectedSampleCount = initialSampleCount + 2;
waitForExpectedStatValue(statSamplerStats, "sampleCount", expectedSampleCount, 5000, 10);
}
/**
* Adds a LocalStatListener for an individual stat. Validates that it receives notifications.
* Removes the listener and validates that it was in fact removed and no longer receives
* notifications.
*/
@Test
public void testLocalStatListener() throws Exception {
connect(createGemFireProperties());
GemFireStatSampler statSampler = getGemFireStatSampler();
assertThat(statSampler.waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
Method getLocalListeners = getGemFireStatSampler().getClass().getMethod("getLocalListeners");
assertThat(getLocalListeners).isNotNull();
Method addLocalStatListener = getGemFireStatSampler().getClass()
.getMethod("addLocalStatListener", LocalStatListener.class, Statistics.class, String.class);
assertThat(addLocalStatListener).isNotNull();
Method removeLocalStatListener = getGemFireStatSampler().getClass()
.getMethod("removeLocalStatListener", LocalStatListener.class);
assertThat(removeLocalStatListener).isNotNull();
assertThat(statSampler.getLocalListeners())
.as("local listeners before adding first listener")
.isEmpty();
// add a listener for sampleCount stat in StatSampler statistics
StatisticsType statSamplerType = getStatisticsManager().findType("StatSampler");
Statistics[] statsArray = getStatisticsManager().findStatisticsByType(statSamplerType);
assertThat(statsArray.length)
.as("stats array length")
.isEqualTo(1);
final Statistics statSamplerStats = statsArray[0];
final String statName = "sampleCount";
final AtomicInteger sampleCountValue = new AtomicInteger(0);
final AtomicInteger sampleCountChanged = new AtomicInteger(0);
LocalStatListener listener = value -> {
sampleCountValue.set((int) value);
sampleCountChanged.incrementAndGet();
};
statSampler.addLocalStatListener(listener, statSamplerStats, statName);
assertThat(statSampler.getLocalListeners())
.as("local listeners after adding 1 listener")
.hasSize(1);
// there's a level of indirection here and some protected member fields
LocalStatListenerImpl lsli = statSampler.getLocalListeners().iterator().next();
assertThat(lsli.stat.getName())
.as("listener's first stat's name")
.isEqualTo("sampleCount");
await("listener to update several times").untilAsserted(
() -> assertThat(sampleCountChanged).hasValueGreaterThanOrEqualTo(4));
// validate that the listener fired and updated the value
assertThat(sampleCountValue.get())
.as("sample count value after the listener has fired")
.isGreaterThan(0);
// remove the listener
statSampler.removeLocalStatListener(listener);
final int expectedSampleCountValue = sampleCountValue.get();
final int expectedSampleCountChanged = sampleCountChanged.get();
assertThat(statSampler.getLocalListeners())
.as("local listeners after removing the listener")
.isEmpty();
// wait for 2 stat samples to occur
waitForStatSample(statSamplerStats, expectedSampleCountValue, 5000, 10);
// validate that the listener did not fire
assertThat(sampleCountValue.get())
.as("sample count value after the listener was removed")
.isEqualTo(expectedSampleCountValue);
assertThat(sampleCountChanged.get())
.as("sample count changed after the listener was removed")
.isEqualTo(expectedSampleCountChanged);
}
/**
* Invokes stop() and then validates that the sampler did in fact stop.
*/
@Test
public void testStop() throws Exception {
connect(createGemFireProperties());
GemFireStatSampler statSampler = getGemFireStatSampler();
assertThat(statSampler.waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
// validate the stat sampler is running
StatisticsType statSamplerType = getStatisticsManager().findType("StatSampler");
Statistics[] statsArray = getStatisticsManager().findStatisticsByType(statSamplerType);
assertThat(statsArray.length)
.as("stats array length")
.isEqualTo(1);
final Statistics statSamplerStats = statsArray[0];
final int initialSampleCount = statSamplerStats.getInt("sampleCount");
final int expectedSampleCount = initialSampleCount + 2;
waitForStatSample(statSamplerStats, expectedSampleCount, 20000, 10);
// stop the stat sampler
statSampler.stop();
// validate the stat sampler has stopped
final int stoppedSampleCount = statSamplerStats.getInt("sampleCount");
// the following should timeout rather than complete
assertStatValueDoesNotChange(statSamplerStats, "sampleCount", stoppedSampleCount, 5000, 10);
assertThat(statSamplerStats.getInt("sampleCount"))
.as("value of sample count stat after timing out")
.isEqualTo(stoppedSampleCount);
}
/**
* Verifies that archive rolling works correctly when archive-file-size-limit is specified.
*/
@Test
public void testArchiveRolling() throws Exception {
final String dirName = testDir.getAbsolutePath() + separator + testName;
new File(dirName).mkdirs();
final String archiveFileName = dirName + separator + testName + ".gfs";
final File archiveFile = new File(archiveFileName);
final File archiveFile1 = new File(dirName + separator + testName + "-01-01.gfs");
final File archiveFile2 = new File(dirName + separator + testName + "-01-02.gfs");
final File archiveFile3 = new File(dirName + separator + testName + "-01-03.gfs");
// set the system property to use KB instead of MB for file size
setProperty(TEST_FILE_SIZE_LIMIT_IN_KB_PROPERTY, "true");
Properties props = createGemFireProperties();
props.setProperty(ARCHIVE_FILE_SIZE_LIMIT, "1");
props.setProperty(ARCHIVE_DISK_SPACE_LIMIT, "0");
props.setProperty(STATISTIC_ARCHIVE_FILE, archiveFileName);
connect(props);
assertThat(getGemFireStatSampler().waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
await().untilAsserted(
() -> {
SampleCollector sampleCollector = getSampleCollector();
assertThat(sampleCollector)
.as("sample collector")
.isNotNull();
assertThat(sampleCollector.getStatArchiveHandler())
.as("stat archive handler")
.isNotNull();
});
StatArchiveHandler statArchiveHandler = getSampleCollector().getStatArchiveHandler();
StatArchiveHandlerConfig config = statArchiveHandler.getStatArchiveHandlerConfig();
assertThat(config.getArchiveFileSizeLimit())
.as("archive file size limit")
.isEqualTo(1024);
waitForFileToExist(archiveFile, 4000, 10);
waitForFileToExist(archiveFile1, 4000, 10);
waitForFileToExist(archiveFile2, 4000, 10);
waitForFileToExist(archiveFile3, 4000, 10);
}
/**
* Verifies that archive removal works correctly when archive-disk-space-limit is specified.
*/
@Test
public void testArchiveRemoval() throws Exception {
final String dirName = testDir.getAbsolutePath();// + File.separator + this.testName;
new File(dirName).mkdirs();
final String archiveFileName = dirName + separator + testName + ".gfs";
final File archiveFile = new File(archiveFileName);
final File archiveFile1 = new File(dirName + separator + testName + "-01-01.gfs");
final File archiveFile2 = new File(dirName + separator + testName + "-01-02.gfs");
final File archiveFile3 = new File(dirName + separator + testName + "-01-03.gfs");
final File archiveFile4 = new File(dirName + separator + testName + "-01-04.gfs");
final int sampleRate = 1000;
setProperty(TEST_FILE_SIZE_LIMIT_IN_KB_PROPERTY, "true");
Properties props = createGemFireProperties();
props.setProperty(STATISTIC_ARCHIVE_FILE, archiveFileName);
props.setProperty(ARCHIVE_FILE_SIZE_LIMIT, "2");
props.setProperty(ARCHIVE_DISK_SPACE_LIMIT, "14");
props.setProperty(STATISTIC_SAMPLE_RATE, String.valueOf(sampleRate));
connect(props);
assertThat(getGemFireStatSampler().waitForInitialization(5000))
.as("initialized within 5 seconds")
.isTrue();
final AtomicBoolean rolloverArchiveFile1 = new AtomicBoolean(false);
final AtomicBoolean rolloverArchiveFile2 = new AtomicBoolean(false);
final AtomicBoolean rolloverArchiveFile3 = new AtomicBoolean(false);
final AtomicBoolean rolloverArchiveFile4 = new AtomicBoolean(false);
final AtomicBoolean currentArchiveFile = new AtomicBoolean(false);
await("current archive file and four rollover archive files")
.untilAsserted(() -> {
currentArchiveFile.lazySet(currentArchiveFile.get() || archiveFile.exists());
rolloverArchiveFile1.lazySet(rolloverArchiveFile1.get() || archiveFile1.exists());
rolloverArchiveFile2.lazySet(rolloverArchiveFile2.get() || archiveFile2.exists());
rolloverArchiveFile3.lazySet(rolloverArchiveFile3.get() || archiveFile3.exists());
rolloverArchiveFile4.lazySet(rolloverArchiveFile4.get() || archiveFile4.exists());
assertThat(rolloverArchiveFile1.get()
&& rolloverArchiveFile2.get()
&& rolloverArchiveFile3.get()
&& rolloverArchiveFile4.get()
&& currentArchiveFile.get())
.as("Waiting for archive files to exist:"
+ " currentArchiveFile=" + currentArchiveFile
+ " rolloverArchiveFile1=" + rolloverArchiveFile1
+ " rolloverArchiveFile2=" + rolloverArchiveFile2
+ " rolloverArchiveFile3=" + rolloverArchiveFile3
+ " rolloverArchiveFile4=" + rolloverArchiveFile4)
.isTrue();
});
waitForFileToDelete(archiveFile1, 10 * sampleRate, 10);
}
@Test
public void testLocalStatListenerRegistration() throws Exception {
connect(createGemFireProperties());
final GemFireStatSampler statSampler = getGemFireStatSampler();
statSampler.waitForInitialization(5000);
final AtomicBoolean flag = new AtomicBoolean(false);
final LocalStatListener listener = value -> flag.set(true);
final String tenuredPoolName = getTenuredMemoryPoolMXBean().getName();
logger.info("TenuredPoolName: {}", tenuredPoolName);
Statistics tenuredPoolStatistics =
await("tenured pool statistics " + tenuredPoolName + " is not null")
.until(() -> getTenuredPoolStatistics(system.getStatisticsManager()), Objects::nonNull);
statSampler.addLocalStatListener(listener, tenuredPoolStatistics, "currentUsedMemory");
assertThat(statSampler.getLocalListeners().size() > 0)
.as("expected at least one stat listener, found " + statSampler.getLocalListeners().size())
.isTrue();
long maxTenuredMemory = getTenuredMemoryPoolMXBean().getUsage().getMax();
byte[] bytes = new byte[(int) (maxTenuredMemory * 0.01)];
fill(bytes, MAX_VALUE);
await("listener to be triggered").untilTrue(flag);
}
@Override
protected StatisticsManager getStatisticsManager() {
return system.getStatisticsManager();
}
private GemFireStatSampler getGemFireStatSampler() {
return system.getStatSampler();
}
private SampleCollector getSampleCollector() {
return getGemFireStatSampler().getSampleCollector();
}
private Properties createGemFireProperties() {
Properties props = new Properties();
props.setProperty(STATISTIC_SAMPLING_ENABLED, "true"); // TODO: test true/false
props.setProperty(ENABLE_TIME_STATISTICS, "true"); // TODO: test true/false
props.setProperty(STATISTIC_SAMPLE_RATE, String.valueOf(STAT_SAMPLE_RATE));
props.setProperty(ARCHIVE_FILE_SIZE_LIMIT, "0");
props.setProperty(ARCHIVE_DISK_SPACE_LIMIT, "0");
props.setProperty(MCAST_PORT, "0");
props.setProperty(LOCATORS, "");
return props;
}
/**
* Creates a fresh loner DistributedSystem for each test. Note that the DistributedSystem is the
* StatisticsManager/Factory/etc.
*/
@SuppressWarnings("deprecation")
private void connect(Properties props) {
system = (InternalDistributedSystem) DistributedSystem.connect(props);
}
@SuppressWarnings("deprecation")
private void disconnect() {
if (system != null) {
system.disconnect();
system = null;
}
}
}