Remove dependency on Sigar in favor of OSHI

patch by Claude Warren; reviewed by Stefan Miklosovic, Jacek Lewandowski, Michael Semb Wever for CASSANDRA-16565

Co-authored-by: Stefan Miklosovic <smiklosovic@apache.org>
This commit is contained in:
Claude Warren 2023-10-25 13:00:50 +02:00 committed by Stefan Miklosovic
parent 34fa4e279a
commit 5d46ff2796
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
26 changed files with 461 additions and 339 deletions

View File

@ -204,8 +204,6 @@
<files dir="${build.lib}" layout="{artifactId}-{version}-{classifier}.{extension}" scopes="compile,!provide,!system"/>
</resolve>
</retry>
<mkdir dir="${local.repository}/org/apache/cassandra/deps/sigar-bin"/>
<mkdir dir="${build.lib}/sigar-bin"/>
<mkdir dir="${build.lib}/x86_64"/>
<mkdir dir="${build.lib}/aarch64"/> <!-- uname -m on arm prints aarch64 instead of aarch_64 -->
@ -237,28 +235,6 @@
<file file="${local.repository}/org/apache/cassandra/deps/pure_sasl-0.6.2-py2-none-any.zip"/>
<file file="${local.repository}/org/apache/cassandra/deps/wcwidth-0.2.5-py2.py3-none-any.zip"/>
</copy>
<copy todir="${build.lib}/sigar-bin/" quiet="true">
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-amd64-freebsd-6.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-amd64-linux.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-amd64-solaris.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-ia64-hpux-11.sl"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-ia64-linux.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-pa-hpux-11.sl"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-ppc-aix-5.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-ppc-linux.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-ppc64-aix-5.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-ppc64-linux.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-ppc64le-linux.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-s390x-linux.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-sparc-solaris.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-sparc64-solaris.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-universal-macosx.dylib"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-universal64-macosx.dylib"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-x86-freebsd-5.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-x86-freebsd-6.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-x86-linux.so"/>
<file file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-x86-solaris.so"/>
</copy>
<!-- as resolver will copy all dependencies into lib dir, and we are copying jars to lib/{x86_64|aarch64} as well, we would have duplicities -->
<delete file="${build.lib}/AmazonCorrettoCryptoProvider-2.2.0-linux-x86_64.jar" failonerror="false"/>
@ -269,6 +245,7 @@
</target>
<target name="_resolver-dist-lib_get_files">
<mkdir dir="${local.repository}/org/apache/cassandra/deps" />
<!-- files.pythonhosted.org/packages -->
<get src="${artifact.python.pypi}/59/a0/cf4cd997e1750f0c2d91c6ea5abea218251c43c3581bcc2f118b00baf5cf/futures-2.1.6-py2.py3-none-any.whl" dest="${local.repository}/org/apache/cassandra/deps/futures-2.1.6-py2.py3-none-any.zip" usetimestamp="true" quiet="true" skipexisting="true"/>
<get src="${artifact.python.pypi}/37/b2/ef1124540ee2c0b417be8d0f74667957e6aa084a3f26621aa67e2e77f3fb/pure_sasl-0.6.2-py2-none-any.whl" dest="${local.repository}/org/apache/cassandra/deps/pure_sasl-0.6.2-py2-none-any.zip" usetimestamp="true" quiet="true" skipexisting="true"/>
@ -276,28 +253,6 @@
<!-- apache/cassandra/lib -->
<get src="${lib.download.base.url}/lib/geomet-0.1.0.zip" dest="${local.repository}/org/apache/cassandra/deps/geomet-0.1.0.zip" usetimestamp="true" quiet="true" skipexisting="true"/>
<get dest="${local.repository}/org/apache/cassandra/deps/sigar-bin/" quiet="true" usetimestamp="true" skipexisting="true">
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-amd64-freebsd-6.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-amd64-linux.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-amd64-solaris.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-ia64-hpux-11.sl"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-ia64-linux.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-pa-hpux-11.sl"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-ppc-aix-5.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-ppc-linux.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-ppc64-aix-5.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-ppc64-linux.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-ppc64le-linux.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-s390x-linux.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-sparc-solaris.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-sparc64-solaris.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-universal-macosx.dylib"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-universal64-macosx.dylib"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-x86-freebsd-5.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-x86-freebsd-6.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-x86-linux.so"/>
<url url="${lib.download.base.url}/lib/sigar-bin/libsigar-x86-solaris.so"/>
</get>
</target>
</project>

View File

@ -244,8 +244,8 @@
<!-- end of chronicle-queue -->
<dependency>
<groupId>org.fusesource</groupId>
<artifactId>sigar</artifactId>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>

View File

@ -993,13 +993,21 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.fusesource</groupId>
<artifactId>sigar</artifactId>
<version>1.6.4</version>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.4.8</version>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-bom</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-parent</artifactId>
</exclusion>
</exclusions>
</dependency>

View File

@ -1,4 +1,5 @@
5.1
* Remove dependency on Sigar in favor of OSHI (CASSANDRA-16565)
* Simplify the bind marker and Term logic (CASSANDRA-18813)
* Limit cassandra startup to supported JDKs, allow higher JDKs by setting CASSANDRA_JDK_UNSUPPORTED (CASSANDRA-18688)
* Standardize nodetool tablestats formatting of data units (CASSANDRA-19104)

View File

@ -81,9 +81,6 @@ fi
# set JVM javaagent opts to avoid warnings/errors
JAVA_AGENT="$JAVA_AGENT -javaagent:$CASSANDRA_HOME/lib/jamm-0.4.0.jar"
# Added sigar-bin to the java.library.path CASSANDRA-7838
JAVA_OPTS="$JAVA_OPTS:-Djava.library.path=$CASSANDRA_HOME/lib/sigar-bin"
platform=$(uname -m)
if [ -d "$CASSANDRA_HOME"/lib/"$platform" ]; then
for jar in "$CASSANDRA_HOME"/lib/"$platform"/*.jar ; do

View File

@ -271,11 +271,6 @@ JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.password.file=/etc/cassandra/
#MX4J_ADDRESS="127.0.0.1"
#MX4J_PORT="8081"
# Cassandra uses SIGAR to capture OS metrics CASSANDRA-7838
# for SIGAR we have to set the java.library.path
# to the location of the native libraries.
JVM_OPTS="$JVM_OPTS -Djava.library.path=$CASSANDRA_HOME/lib/sigar-bin"
if [ "x$MX4J_ADDRESS" != "x" ]; then
if [[ "$MX4J_ADDRESS" == \-Dmx4jaddress* ]]; then
# Backward compatible with the older style #13578

View File

@ -30,6 +30,5 @@ tools/bin/hash_password usr/bin
tools/bin/sstablepartitions usr/bin
lib/*.jar usr/share/cassandra/lib
lib/*.zip usr/share/cassandra/lib
lib/sigar-bin/* usr/share/cassandra/lib/sigar-bin
lib/x86_64/* usr/share/cassandra/lib/x86_64
lib/aarch64/* usr/share/cassandra/lib/aarch64

View File

@ -149,7 +149,6 @@
-Dcassandra.logdir=$PROJECT_DIR$/data/logs
-Dcassandra.reads.thresholds.coordinator.defensive_checks_enabled=true
-Dcassandra.storagedir=$PROJECT_DIR$/data
-Djava.library.path=$PROJECT_DIR$/lib/sigar-bin
-Dlogback.configurationFile=file://$PROJECT_DIR$/conf/logback.xml
-XX:HeapDumpPath=build/test
-ea" />
@ -194,7 +193,6 @@
-Dcassandra.tolerate_sstable_size=true
-Dcassandra.use_nix_recursive_delete=true
-Dinvalid-legacy-sstable-root=$PROJECT_DIR$/test/data/invalid-legacy-sstables
-Djava.library.path=$PROJECT_DIR$/lib/sigar-bin
-Dlegacy-sstable-root=$PROJECT_DIR$/test/data/legacy-sstables
-Dlogback.configurationFile=file://$PROJECT_DIR$/test/conf/logback-test.xml
-Dmigration-sstable-root=$PROJECT_DIR$/test/data/migration-sstables
@ -231,7 +229,6 @@
-Dcassandra.reads.thresholds.coordinator.defensive_checks_enabled=true
-Dcassandra.storagedir=$PROJECT_DIR$/data
-Dcassandra.triggers_dir=$PROJECT_DIR$/conf/triggers
-Djava.library.path=$PROJECT_DIR$/lib/sigar-bin
-Dlogback.configurationFile=file://$PROJECT_DIR$/conf/logback.xml
-XX:HeapDumpPath=build/test
-Xmx1G

File diff suppressed because one or more lines are too long

View File

@ -27,5 +27,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd $DIR/../..
CLASSPATH=`for f in build/lib/jars/*.jar ; do echo -n '${project.dir}/'$f: ; done ; for f in build/test/lib/jars/*.jar ; do echo -n '${project.dir}/'$f: ; done ;`
sed -i '' 's/cassandra\.classpath\.jars\">.*<\/property>/cassandra\.classpath\.jars\">NEW_CLASSPATH<\/property>/' $DIR/project.xml
sed -i '' "s@NEW_CLASSPATH@"$CLASSPATH"@" $DIR/project.xml
sed 's/cassandra\.classpath\.jars\">.*<\/property>/cassandra\.classpath\.jars\">NEW_CLASSPATH<\/property>/' $DIR/project.xml > $DIR/project.xml.tmp
mv $DIR/project.xml.tmp $DIR/project.xml
sed "s@NEW_CLASSPATH@"$CLASSPATH"@" $DIR/project.xml > $DIR/project.xml.tmp
mv $DIR/project.xml.tmp $DIR/project.xml

View File

@ -103,7 +103,6 @@ sed -i 's/^# hints_directory:/hints_directory:/' conf/cassandra.yaml
rm -f bin/stop-server
rm -f bin/*.orig
rm -f bin/cassandra.in.sh
rm -f lib/sigar-bin/*winnt* # strip segfaults on dll..
rm -f tools/bin/cassandra.in.sh
# copy default configs

View File

@ -103,7 +103,6 @@ sed -i 's/^# hints_directory:/hints_directory:/' conf/cassandra.yaml
rm -f bin/stop-server
rm -f bin/*.orig
rm -f bin/cassandra.in.sh
rm -f lib/sigar-bin/*winnt* # strip segfaults on dll..
rm -f tools/bin/cassandra.in.sh
# copy default configs

View File

@ -567,7 +567,6 @@ public enum CassandraRelevantProperties
* can be also done manually for that particular case: {@code flush(SchemaConstants.SCHEMA_KEYSPACE_NAME);}. */
TEST_FLUSH_LOCAL_SCHEMA_CHANGES("cassandra.test.flush_local_schema_changes", "true"),
TEST_HARRY_SWITCH_AFTER("cassandra.test.harry.progression.switch-after", "1"),
TEST_IGNORE_SIGAR("cassandra.test.ignore_sigar"),
TEST_INVALID_LEGACY_SSTABLE_ROOT("invalid-legacy-sstable-root"),
TEST_JVM_DTEST_DISABLE_SSL("cassandra.test.disable_ssl"),
TEST_JVM_SHUTDOWN_MESSAGING_GRACEFULLY("cassandra.test.messagingService.gracefulShutdown", "false"),
@ -579,7 +578,6 @@ public enum CassandraRelevantProperties
TEST_REUSE_PREPARED("cassandra.test.reuse_prepared", "true"),
TEST_ROW_CACHE_SIZE("cassandra.test.row_cache_size"),
TEST_SERIALIZATION_WRITES("cassandra.test-serialization-writes"),
TEST_SIGAR_NATIVE_LOGGING("sigar.nativeLogging", "true"),
TEST_SIMULATOR_DEBUG("cassandra.test.simulator.debug"),
TEST_SIMULATOR_DETERMINISM_CHECK("cassandra.test.simulator.determinismcheck", "none"),
TEST_SIMULATOR_LIVENESS_CHECK("cassandra.test.simulator.livenesscheck", "true"),

View File

@ -73,7 +73,6 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JavaUtils;
import org.apache.cassandra.utils.NativeLibrary;
import org.apache.cassandra.utils.SigarLibrary;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_JMX_LOCAL_PORT;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_PORT;
@ -89,7 +88,7 @@ import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
* Each individual test is modelled as an implementation of StartupCheck, these are run
* at the start of CassandraDaemon#setup() before any local state is mutated. The default
* checks are a mix of informational tests (inspectJvmOptions), initialization
* (initSigarLibrary, checkCacheServiceInitialization) and invariant checking
* (checkProcessEnvironment, checkCacheServiceInitialization) and invariant checking
* (checkValidLaunchDate, checkSystemKeyspaceState, checkSSTablesFormat).
*
* In addition, if checkSystemKeyspaceState determines that the release version has
@ -138,7 +137,7 @@ public class StartupChecks
checkJMXProperties,
inspectJvmOptions,
checkNativeLibraryInitialization,
initSigarLibrary,
checkProcessEnvironment,
checkMaxMapCount,
checkReadAheadKbSetting,
checkDataDirs,
@ -417,14 +416,17 @@ public class StartupChecks
}
};
public static final StartupCheck initSigarLibrary = new StartupCheck()
public static final StartupCheck checkProcessEnvironment = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options)
{
if (options.isDisabled(getStartupCheckType()))
return;
SigarLibrary.instance.warnIfRunningInDegradedMode();
Optional<String> degradations = FBUtilities.getSystemInfo().isDegraded();
if (degradations.isPresent())
logger.warn("Cassandra server running in degraded mode. " + degradations.get());
else
logger.info("Checked OS settings and found them configured for optimal performance.");
}
};

View File

@ -47,7 +47,6 @@ import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Properties;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@ -138,7 +137,7 @@ public class FBUtilities
private static int availableProcessors = CASSANDRA_AVAILABLE_PROCESSORS.getInt(DatabaseDescriptor.getAvailableProcessors());
private static volatile Supplier<Semver> kernelVersionSupplier = Suppliers.memoize(FBUtilities::getKernelVersionFromUname);
private static volatile Supplier<SystemInfo> systemInfoSupplier = Suppliers.memoize(SystemInfo::new);
public static void setAvailableProcessors(int value)
{
@ -146,9 +145,9 @@ public class FBUtilities
}
@VisibleForTesting
public static void setKernelVersionSupplier(Supplier<Semver> supplier)
public static void setSystemInfoSupplier(Supplier<SystemInfo> supplier)
{
kernelVersionSupplier = supplier;
systemInfoSupplier = supplier;
}
public static int getAvailableProcessors()
@ -1449,50 +1448,11 @@ public class FBUtilities
public static Semver getKernelVersion()
{
return kernelVersionSupplier.get();
return systemInfoSupplier.get().getKernelVersion();
}
@VisibleForTesting
static Semver getKernelVersionFromUname()
public static SystemInfo getSystemInfo()
{
// TODO rewrite this method with Oshi when it is eventually included in the project
if (!isLinux)
return null;
try
{
String output = exec(Map.of(), Duration.ofSeconds(5), 1024, 1024, "uname", "-r");
if (output.isEmpty())
throw new RuntimeException("Error while trying to get kernel version, 'uname -r' returned empty output");
return parseKernelVersion(output);
}
catch (IOException | TimeoutException e)
{
throw new RuntimeException("Error while trying to get kernel version", e);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
@VisibleForTesting
static Semver parseKernelVersion(String versionString)
{
Preconditions.checkNotNull(versionString, "kernel version cannot be null");
try (Scanner scanner = new Scanner(versionString))
{
while (scanner.hasNextLine())
{
String version = scanner.nextLine().trim();
if (version.isEmpty())
continue;
return new Semver(version, Semver.SemverType.LOOSE);
}
}
throw new IllegalArgumentException("Error while trying to parse kernel version - no version found");
return systemInfoSupplier.get();
}
}

View File

@ -1,187 +0,0 @@
/*
* 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.cassandra.utils;
import org.hyperic.sigar.*;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.apache.cassandra.config.CassandraRelevantProperties;
@Shared
public class SigarLibrary
{
private Logger logger = LoggerFactory.getLogger(SigarLibrary.class);
public static final SigarLibrary instance = new SigarLibrary();
private Sigar sigar;
private FileSystemMap mounts = null;
private boolean initialized = false;
private long INFINITY = -1;
private long EXPECTED_MIN_NOFILE = 10000l; // number of files that can be opened
private long EXPECTED_NPROC = 32768l; // number of processes
private long EXPECTED_AS = INFINITY; // address space
// TODO: Determine memlock limits if possible
// TODO: Determine if file system is remote or local
// TODO: Determine if disk latency is within acceptable limits
private SigarLibrary()
{
logger.info("Initializing SIGAR library");
try
{
sigar = new Sigar();
mounts = sigar.getFileSystemMap();
initialized = true;
}
catch (SigarException e)
{
logger.info("Could not initialize SIGAR library {} ", e.getMessage());
}
catch (UnsatisfiedLinkError linkError)
{
logger.info("Could not initialize SIGAR library {} ", linkError.getMessage());
}
}
/**
*
* @return true or false indicating if sigar was successfully initialized
*/
public boolean initialized()
{
return initialized;
}
private boolean hasAcceptableProcNumber()
{
try
{
long fileMax = sigar.getResourceLimit().getProcessesMax();
if (fileMax >= EXPECTED_NPROC || fileMax == INFINITY)
{
return true;
}
else
{
return false;
}
}
catch (SigarException sigarException)
{
logger.warn("Could not determine if max processes was acceptable. Error message: {}", sigarException);
return false;
}
}
private boolean hasAcceptableFileLimits()
{
try
{
long fileMax = sigar.getResourceLimit().getOpenFilesMax();
if (fileMax >= EXPECTED_MIN_NOFILE || fileMax == INFINITY)
{
return true;
}
else
{
return false;
}
}
catch (SigarException sigarException)
{
logger.warn("Could not determine if max open file handle limit is correctly configured. Error message: {}", sigarException);
return false;
}
}
private boolean hasAcceptableAddressSpace()
{
try
{
long fileMax = sigar.getResourceLimit().getVirtualMemoryMax();
if (fileMax == EXPECTED_AS)
{
return true;
}
else
{
return false;
}
}
catch (SigarException sigarException)
{
logger.warn("Could not determine if VirtualMemoryMax was acceptable. Error message: {}", sigarException);
return false;
}
}
private boolean isSwapEnabled()
{
try
{
Swap swap = sigar.getSwap();
long swapSize = swap.getTotal();
if (swapSize > 0)
{
return true;
}
else
{
return false;
}
}
catch (SigarException sigarException)
{
logger.warn("Could not determine if swap configuration is acceptable. Error message: {}", sigarException);
return false;
}
}
public long getPid()
{
return initialized ? sigar.getPid() : -1;
}
public void warnIfRunningInDegradedMode()
{
if (initialized)
{
boolean swapEnabled = isSwapEnabled();
boolean goodAddressSpace = hasAcceptableAddressSpace();
boolean goodFileLimits = hasAcceptableFileLimits();
boolean goodProcNumber = hasAcceptableProcNumber();
if (swapEnabled || !goodAddressSpace || !goodFileLimits || !goodProcNumber || CassandraRelevantProperties.TEST_IGNORE_SIGAR.getBoolean())
{
logger.warn("Cassandra server running in degraded mode. Is swap disabled? : {}, Address space adequate? : {}, " +
" nofile limit adequate? : {}, nproc limit adequate? : {} ", !swapEnabled, goodAddressSpace,
goodFileLimits, goodProcNumber );
}
else
{
logger.info("Checked OS settings and found them configured for optimal performance.");
}
}
else
{
logger.info("Sigar could not be initialized, test for checking degraded mode omitted.");
}
}
}

View File

@ -0,0 +1,228 @@
/*
* 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.cassandra.utils;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import com.vdurmont.semver4j.Semver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import oshi.PlatformEnum;
import static java.lang.String.format;
import static java.util.Optional.empty;
import static java.util.Optional.of;
/**
* An abstraction of System information, this class provides access to system information without specifying how
* it is retrieved.
*/
public class SystemInfo
{
// TODO: Determine memlock limits if possible
// TODO: Determine if file system is remote or local
// TODO: Determine if disk latency is within acceptable limits
private static final Logger logger = LoggerFactory.getLogger(SystemInfo.class);
private static final long INFINITY = -1L;
static final long EXPECTED_MIN_NUMBER_OF_OPENED_FILES = 10000L; // number of files that can be opened
static final long EXPECTED_MIN_NUMBER_OF_PROCESSES = 32768L; // number of processes
static final long EXPECTED_ADDRESS_SPACE = 0x7FFFFFFFL; // address space
static final String OPEN_FILES_VIOLATION_MESSAGE = format("Minimum value for max open files should be >= %s. ", EXPECTED_MIN_NUMBER_OF_OPENED_FILES);
static final String NUMBER_OF_PROCESSES_VIOLATION_MESSAGE = format("Number of processes should be >= %s. ", EXPECTED_MIN_NUMBER_OF_PROCESSES);
static final String ADDRESS_SPACE_VIOLATION_MESSAGE = format("Amount of available address space should be >= %s. ", EXPECTED_ADDRESS_SPACE);
static final String SWAP_VIOLATION_MESSAGE = "Swap should be disabled. ";
/**
* The default number of processes that are reported if the actual value can not be retrieved.
*/
private static final long DEFAULT_MAX_PROCESSES = 1024;
private static final Pattern SPACES_PATTERN = Pattern.compile("\\s+");
/**
* The oshi.SystemInfo has the following note:
* Platform-specific Hardware and Software objects are retrieved via memoized suppliers. To conserve memory at the
* cost of additional processing time, create a new version of SystemInfo() for subsequent calls. To conserve
* processing time at the cost of additional memory usage, re-use the same {@link SystemInfo} object for future
* queries.
* <p>
* We are opting for minimal memory footprint.
*/
private final oshi.SystemInfo si;
public SystemInfo()
{
si = new oshi.SystemInfo();
}
/**
* @return The PlatformEnum for the current platform. (e.g. Linux, Windows, AIX, etc.)
*/
public PlatformEnum platform()
{
return oshi.SystemInfo.getCurrentPlatform();
}
/**
* Gets the maximum number of processes the user can create.
* Note: if not on a Linux system this always return the
*
* @return The maximum number of processes.
* @see #DEFAULT_MAX_PROCESSES
*/
public long getMaxProcess()
{
// this check only works on Linux systems. Errors fall through to return default.
if (platform() == PlatformEnum.LINUX)
{
String path = format("/proc/%s/limits", getPid());
try
{
List<String> lines = FileUtils.readLines(new File(path));
for (String line : lines)
{
if (line.startsWith("Max processes"))
{
String[] parts = SPACES_PATTERN.split(line);
if (parts.length < 3)
continue;
String limit = parts[2];
return "unlimited".equals(limit) ? INFINITY : Long.parseLong(limit);
}
}
logger.error("'Max processes' not found in {}", path);
}
catch (Exception t)
{
logger.error(format("Unable to read %s", path), t);
}
}
/* return the default value for non-Linux systems or parsing error.
* Can not return 0 as we know there is at least 1 process (this one) and
* -1 historically represents infinity.
*/
return DEFAULT_MAX_PROCESSES;
}
/**
* @return The maximum number of open files allowd to the current process/user.
*/
public long getMaxOpenFiles()
{
// ulimit -H -n
return si.getOperatingSystem().getCurrentProcess().getHardOpenFileLimit();
}
/**
* Gets the Virtual Memory Size (VSZ). Includes all memory that the process can access,
* including memory that is swapped out and memory that is from shared libraries.
*
* @return The amount of virtual memory allowed to be allocatedby the current process/user.
*/
public long getVirtualMemoryMax()
{
return si.getOperatingSystem().getCurrentProcess().getVirtualSize();
}
/**
* @return The amount of swap space allocated on the system.
*/
public long getSwapSize()
{
return si.getHardware().getMemory().getVirtualMemory().getSwapTotal();
}
/**
* @return the PID of the current system.
*/
public long getPid()
{
return si.getOperatingSystem().getProcessId();
}
/**
* @return the Semver for the kernel version of the OS.
*/
public Semver getKernelVersion()
{
return new Semver(si.getOperatingSystem().getVersionInfo().getBuildNumber(), Semver.SemverType.LOOSE);
}
/**
* Tests if the system is running in degraded mode.
*
* @return non-empty optional with degradation messages if the system is in degraded mode, empty optional otherwise.
*/
public Optional<String> isDegraded()
{
Supplier<String> expectedNumProc = () -> {
// only check proc on nproc linux
if (platform() == PlatformEnum.LINUX)
return invalid(getMaxProcess(), EXPECTED_MIN_NUMBER_OF_PROCESSES) ? NUMBER_OF_PROCESSES_VIOLATION_MESSAGE
: null;
else
return format("System is running %s, Linux OS is recommended. ", platform());
};
Supplier<String> swapShouldBeDisabled = () -> (getSwapSize() > 0) ? SWAP_VIOLATION_MESSAGE : null;
Supplier<String> expectedAddressSpace = () -> invalid(getVirtualMemoryMax(), EXPECTED_ADDRESS_SPACE)
? ADDRESS_SPACE_VIOLATION_MESSAGE
: null;
Supplier<String> expectedMinNoFile = () -> invalid(getMaxOpenFiles(), EXPECTED_MIN_NUMBER_OF_OPENED_FILES)
? OPEN_FILES_VIOLATION_MESSAGE
: null;
StringBuilder sb = new StringBuilder();
for (Supplier<String> check : List.of(expectedNumProc, swapShouldBeDisabled, expectedAddressSpace, expectedMinNoFile))
Optional.ofNullable(check.get()).map(sb::append);
String message = sb.toString();
return message.isEmpty() ? empty() : of(message);
}
/**
* Checks if a value is invalid.
* <p>
* Value is invalid if it is smaller than {@code min} and it is not {@code INFINITY},
* here represented as a value of -1;
*
* @param value the value to check
* @param min the minimum value
* @return true if value is valid
*/
private boolean invalid(long value, long min)
{
return value < min && value != INFINITY;
}
}

View File

@ -43,7 +43,7 @@
<logger name="org.apache.cassandra.net.Message" level="ERROR"/>
<logger name="org.reflections.Reflections" level="ERROR"/>
<logger name="org.apache.hadoop" level="WARN"/>
<logger name="org.apache.cassandra.utils.SigarLibrary" level="ERROR"/>
<logger name="org.apache.cassandra.utils.SystemInfo" level="ERROR"/>
<logger name="org.apache.cassandra.utils.FBUtilities" level="ERROR"/>
<logger name="org.apache.cassandra.config.DatabaseDescriptor" level="ERROR"/>
<logger name="org.apache.cassandra.service.StartupChecks" level="ERROR"/>

View File

@ -41,7 +41,7 @@ import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.shared.JMXUtil;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.SigarLibrary;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.JMX;
@ -91,8 +91,7 @@ public class ResourceLeakTest extends TestBaseImpl
*/
private static Long getProcessId()
{
// Once Java 9 is ready the process API should provide a better way to get the process ID.
long pid = SigarLibrary.instance.getPid();
long pid = FBUtilities.getSystemInfo().getPid();
if (pid >= 0)
return Long.valueOf(pid);

View File

@ -81,7 +81,6 @@ public class TestBaseImpl extends DistributedTestBase
{
ICluster.setup();
SKIP_GC_INSPECTOR.setBoolean(true);
System.setProperty("sigar.nativeLogging", "false");
}
@Override

View File

@ -71,7 +71,6 @@ public class UpgradeTestBase extends DistributedTestBase
{
ICluster.setup();
SKIP_GC_INSPECTOR.setBoolean(true);
System.setProperty("sigar.nativeLogging", "false");
}

View File

@ -74,7 +74,6 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.PAXOS_REPA
import static org.apache.cassandra.config.CassandraRelevantProperties.RING_DELAY;
import static org.apache.cassandra.config.CassandraRelevantProperties.SHUTDOWN_ANNOUNCE_DELAY_IN_MS;
import static org.apache.cassandra.config.CassandraRelevantProperties.SYSTEM_AUTH_DEFAULT_RF;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_IGNORE_SIGAR;
import static org.apache.cassandra.config.CassandraRelevantProperties.DISABLE_GOSSIP_ENDPOINT_REMOVAL;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_JVM_DTEST_DISABLE_SSL;
import static org.apache.cassandra.simulator.debug.Reconcile.reconcileWith;
@ -121,7 +120,6 @@ public class SimulationRunner
DISABLE_SSTABLE_ACTIVITY_TRACKING.setBoolean(false);
DETERMINISM_SSTABLE_COMPRESSION_DEFAULT.setBoolean(false); // compression causes variation in file size for e.g. UUIDs, IP addresses, random file paths
CONSISTENT_DIRECTORY_LISTINGS.setBoolean(true);
TEST_IGNORE_SIGAR.setBoolean(true);
SYSTEM_AUTH_DEFAULT_RF.setInt(3);
DISABLE_GOSSIP_ENDPOINT_REMOVAL.setBoolean(true);
MEMTABLE_OVERHEAD_SIZE.setInt(100);

View File

@ -276,6 +276,7 @@ public class DatabaseDescriptorRefTest
"org.apache.cassandra.utils.CloseableIterator",
"org.apache.cassandra.utils.FBUtilities",
"org.apache.cassandra.utils.FBUtilities$1",
"org.apache.cassandra.utils.SystemInfo",
"org.apache.cassandra.utils.Pair",
"org.apache.cassandra.utils.binlog.BinLogOptions",
"org.apache.cassandra.utils.concurrent.RefCounted",

View File

@ -57,6 +57,7 @@ import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.service.DataResurrectionCheck.Heartbeat;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.SystemInfo;
import static java.util.Collections.singletonList;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_INVALID_LEGACY_SSTABLE_ROOT;
@ -312,14 +313,21 @@ public class StartupChecksTest
String savedCommitLogLocation = DatabaseDescriptor.getCommitLogLocation();
DiskAccessMode savedCommitLogWriteDiskAccessMode = DatabaseDescriptor.getCommitLogWriteDiskAccessMode();
Semver savedKernelVersion = FBUtilities.getKernelVersion();
SystemInfo savedSystemInfo = FBUtilities.getSystemInfo();
try
{
DatabaseDescriptor.setCommitLogLocation(commitLogLocation);
DatabaseDescriptor.setCommitLogWriteDiskAccessMode(diskAccessMode);
DatabaseDescriptor.initializeCommitLogDiskAccessMode();
assertThat(DatabaseDescriptor.getCommitLogWriteDiskAccessMode()).isEqualTo(diskAccessMode);
FBUtilities.setKernelVersionSupplier(() -> kernelVersion);
FBUtilities.setSystemInfoSupplier(() -> new SystemInfo()
{
@Override
public Semver getKernelVersion()
{
return kernelVersion;
}
});
withPathOverriddingFileSystem(Map.of(commitLogLocation, fsType), () -> {
if (expectToFail)
assertThatExceptionOfType(StartupException.class).isThrownBy(() -> StartupChecks.checkKernelBug1057843.execute(options));
@ -333,7 +341,7 @@ public class StartupChecksTest
DatabaseDescriptor.setCommitLogLocation(savedCommitLogLocation);
DatabaseDescriptor.setCommitLogWriteDiskAccessMode(savedCommitLogWriteDiskAccessMode);
DatabaseDescriptor.initializeCommitLogDiskAccessMode();
FBUtilities.setKernelVersionSupplier(() -> savedKernelVersion);
FBUtilities.setSystemInfoSupplier(() -> savedSystemInfo);
}
}

View File

@ -59,9 +59,7 @@ import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.OrderPreservingPartitioner;
import org.apache.cassandra.dht.RandomPartitioner;
import static org.apache.cassandra.utils.FBUtilities.parseKernelVersion;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@ -409,20 +407,6 @@ public class FBUtilitiesTest
Assert.assertEquals("Infinity", FBUtilities.prettyPrintAverage(Double.POSITIVE_INFINITY));
}
@Test
public void testParseKernelVersion()
{
assertThat(parseKernelVersion("4.4.0-21-generic").toString()).isEqualTo("4.4.0-21-generic");
assertThat(parseKernelVersion("4.4.0-pre21-generic").toString()).isEqualTo("4.4.0-pre21-generic");
assertThat(parseKernelVersion("4.4-pre21-generic").toString()).isEqualTo("4.4-pre21-generic");
assertThat(parseKernelVersion("4.4.0-21-generic\n").toString()).isEqualTo("4.4.0-21-generic");
assertThat(parseKernelVersion("\n4.4.0-21-generic\n").toString()).isEqualTo("4.4.0-21-generic");
assertThat(parseKernelVersion("\n 4.4.0-21-generic \n").toString()).isEqualTo("4.4.0-21-generic");
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> parseKernelVersion("\n \n"))
.withMessageContaining("no version found");
}
@Test
public void testGetKernelVersion()
{

View File

@ -0,0 +1,180 @@
/*
* 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.cassandra.utils;
import java.util.Optional;
import org.junit.Assume;
import org.junit.Test;
import com.vdurmont.semver4j.Semver;
import oshi.PlatformEnum;
import static org.apache.cassandra.utils.SystemInfo.ADDRESS_SPACE_VIOLATION_MESSAGE;
import static org.apache.cassandra.utils.SystemInfo.NUMBER_OF_PROCESSES_VIOLATION_MESSAGE;
import static org.apache.cassandra.utils.SystemInfo.OPEN_FILES_VIOLATION_MESSAGE;
import static org.apache.cassandra.utils.SystemInfo.SWAP_VIOLATION_MESSAGE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SystemInfoTest
{
private static class TestSystemInfo extends SystemInfo
{
@Override
public PlatformEnum platform()
{
return PlatformEnum.LINUX;
}
@Override
public long getMaxProcess()
{
return EXPECTED_MIN_NUMBER_OF_PROCESSES;
}
@Override
public long getMaxOpenFiles()
{
return EXPECTED_MIN_NUMBER_OF_OPENED_FILES;
}
@Override
public long getVirtualMemoryMax()
{
return EXPECTED_ADDRESS_SPACE;
}
@Override
public long getSwapSize()
{
return 0;
}
}
@Test
public void testSystemInfo()
{
SystemInfo oldSystemInfo = FBUtilities.getSystemInfo();
try
{
// valid testing system info does not violate anything
TestSystemInfo testSystemInfo = new TestSystemInfo();
assertFalse(testSystemInfo.isDegraded().isPresent());
// platform
assertDegradation(new TestSystemInfo()
{
@Override
public PlatformEnum platform()
{
return PlatformEnum.FREEBSD;
}
}, "System is running FREEBSD, Linux OS is recommended. ");
// swap
assertDegradation(new TestSystemInfo()
{
@Override
public long getSwapSize()
{
return 100;
}
}, SWAP_VIOLATION_MESSAGE);
// address space
assertDegradation(new TestSystemInfo()
{
@Override
public long getVirtualMemoryMax()
{
return 1234;
}
}, ADDRESS_SPACE_VIOLATION_MESSAGE);
// expected minimal number of opened files
assertDegradation(new TestSystemInfo()
{
@Override
public long getMaxOpenFiles()
{
return 10;
}
}, OPEN_FILES_VIOLATION_MESSAGE);
// expected number of processes
assertDegradation(new TestSystemInfo()
{
@Override
public long getMaxProcess()
{
return 5;
}
}, NUMBER_OF_PROCESSES_VIOLATION_MESSAGE);
// test multiple violations
assertDegradation(new TestSystemInfo()
{
@Override
public PlatformEnum platform()
{
return PlatformEnum.FREEBSD;
}
@Override
public long getSwapSize()
{
return 10;
}
}, "System is running FREEBSD, Linux OS is recommended. " + SWAP_VIOLATION_MESSAGE);
}
finally
{
FBUtilities.setSystemInfoSupplier(() -> oldSystemInfo);
}
}
@Test
public void testGetKernelVersion()
{
Assume.assumeTrue(FBUtilities.isLinux);
Semver kernelVersion = FBUtilities.getSystemInfo().getKernelVersion();
assertThat(kernelVersion).isGreaterThan(new Semver("0.0.0", Semver.SemverType.LOOSE))
.isLessThan(new Semver("100.0.0", Semver.SemverType.LOOSE));
}
private void assertDegradation(final SystemInfo systemInfo, String expectedDegradation)
{
FBUtilities.setSystemInfoSupplier(() -> systemInfo);
Optional<String> degradations = FBUtilities.getSystemInfo().isDegraded();
assertTrue(degradations.isPresent());
assertEquals(expectedDegradation, degradations.get());
}
}