| /* |
| * 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; |
| } |
| } |
| } |