Cleanup load average handling

This commit cleans up the code handling load averages in OsProbe:
 - remove support for BSD; we do not support this OS
 - add Javadocs
 - strengthen assertions and testing
 - add debug logging for exceptional situation

Relates #21037
This commit is contained in:
Jason Tedor 2016-10-20 15:39:46 -04:00 committed by GitHub
parent e5d9f393f1
commit 3c7c8723ff
3 changed files with 76 additions and 33 deletions

View File

@ -19,9 +19,13 @@
package org.elasticsearch.monitor.os; package org.elasticsearch.monitor.os;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.monitor.Probes; import org.elasticsearch.monitor.Probes;
import java.io.IOException; import java.io.IOException;
@ -108,44 +112,68 @@ public class OsProbe {
} }
/** /**
* Returns the system load averages * The system load averages as an array.
*
* On Windows, this method returns {@code null}.
*
* On Linux, this method should return the 1, 5, and 15-minute load
* averages. If obtaining these values from {@code /proc/loadavg}
* fails, the method will fallback to obtaining the 1-minute load
* average.
*
* On macOS, this method should return the 1-minute load average.
*
* @return the available system load averages or {@code null}
*/ */
public double[] getSystemLoadAverage() { final double[] getSystemLoadAverage() {
if (Constants.LINUX || Constants.FREE_BSD) { if (Constants.WINDOWS) {
final String procLoadAvg = Constants.LINUX ? "/proc/loadavg" : "/compat/linux/proc/loadavg"; return null;
double[] loadAverage = readProcLoadavg(procLoadAvg); } else if (Constants.LINUX) {
if (loadAverage != null) { final String procLoadAvg = readProcLoadavg();
return loadAverage; if (procLoadAvg != null) {
assert procLoadAvg.matches("(\\d+\\.\\d+\\s+){3}\\d+/\\d+\\s+\\d+");
final String[] fields = procLoadAvg.split("\\s+");
try {
return new double[]{Double.parseDouble(fields[0]), Double.parseDouble(fields[1]), Double.parseDouble(fields[2])};
} catch (final NumberFormatException e) {
logger.debug((Supplier<?>) () -> new ParameterizedMessage("error parsing /proc/loadavg [{}]", procLoadAvg), e);
}
} }
// fallback // fallback
} }
if (Constants.WINDOWS) {
return null;
}
if (getSystemLoadAverage == null) { if (getSystemLoadAverage == null) {
return null; return null;
} }
try { try {
double oneMinuteLoadAverage = (double) getSystemLoadAverage.invoke(osMxBean); final double oneMinuteLoadAverage = (double) getSystemLoadAverage.invoke(osMxBean);
return new double[] { oneMinuteLoadAverage >= 0 ? oneMinuteLoadAverage : -1, -1, -1 }; return new double[] { oneMinuteLoadAverage >= 0 ? oneMinuteLoadAverage : -1, -1, -1 };
} catch (Exception e) { } catch (final Exception e) {
logger.debug("error obtaining system load average", e);
return null; return null;
} }
} }
@SuppressForbidden(reason = "access /proc") /**
private static double[] readProcLoadavg(String procLoadavg) { * The line from {@code /proc/loadavg}. The first three fields are
* the load averages averaged over 1, 5, and 15 minutes. The fourth
* field is two numbers separated by a slash, the first is the
* number of currently runnable scheduling entities, the second is
* the number of scheduling entities on the system. The fifth field
* is the PID of the most recently created process.
*
* @return the line from {@code /proc/loadavg} or {@code null}
*/
@SuppressForbidden(reason = "access /proc/loadavg")
String readProcLoadavg() {
try { try {
List<String> lines = Files.readAllLines(PathUtils.get(procLoadavg)); final List<String> lines = Files.readAllLines(PathUtils.get("/proc/loadavg"));
if (!lines.isEmpty()) { assert lines != null && lines.size() == 1;
String[] fields = lines.get(0).split("\\s+"); return lines.get(0);
return new double[] { Double.parseDouble(fields[0]), Double.parseDouble(fields[1]), Double.parseDouble(fields[2]) }; } catch (final IOException e) {
} logger.debug("error reading /proc/loadavg", e);
} catch (IOException e) { return null;
// do not fail Elasticsearch if something unexpected
// happens here
} }
return null;
} }
public short getSystemCpuPercent() { public short getSystemCpuPercent() {
@ -160,9 +188,11 @@ public class OsProbe {
return OsProbeHolder.INSTANCE; return OsProbeHolder.INSTANCE;
} }
private OsProbe() { OsProbe() {
} }
private final Logger logger = ESLoggerFactory.getLogger(getClass());
public OsInfo osInfo(long refreshInterval, int allocatedProcessors) { public OsInfo osInfo(long refreshInterval, int allocatedProcessors) {
return new OsInfo(refreshInterval, Runtime.getRuntime().availableProcessors(), return new OsInfo(refreshInterval, Runtime.getRuntime().availableProcessors(),
allocatedProcessors, Constants.OS_NAME, Constants.OS_ARCH, Constants.OS_VERSION); allocatedProcessors, Constants.OS_NAME, Constants.OS_ARCH, Constants.OS_VERSION);

View File

@ -116,9 +116,6 @@ grant {
// load averages on Linux // load averages on Linux
permission java.io.FilePermission "/proc/loadavg", "read"; permission java.io.FilePermission "/proc/loadavg", "read";
// load averages on FreeBSD
permission java.io.FilePermission "/compat/linux/proc/loadavg", "read";
// read max virtual memory areas // read max virtual memory areas
permission java.io.FilePermission "/proc/sys/vm/max_map_count", "read"; permission java.io.FilePermission "/proc/sys/vm/max_map_count", "read";

View File

@ -66,12 +66,6 @@ public class OsProbeTests extends ESTestCase {
assertThat(loadAverage[0], greaterThanOrEqualTo((double) 0)); assertThat(loadAverage[0], greaterThanOrEqualTo((double) 0));
assertThat(loadAverage[1], greaterThanOrEqualTo((double) 0)); assertThat(loadAverage[1], greaterThanOrEqualTo((double) 0));
assertThat(loadAverage[2], greaterThanOrEqualTo((double) 0)); assertThat(loadAverage[2], greaterThanOrEqualTo((double) 0));
} else if (Constants.FREE_BSD) {
// five- and fifteen-minute load averages not available if linprocfs is not mounted at /compat/linux/proc
assertNotNull(loadAverage);
assertThat(loadAverage[0], greaterThanOrEqualTo((double) 0));
assertThat(loadAverage[1], anyOf(equalTo((double) -1), greaterThanOrEqualTo((double) 0)));
assertThat(loadAverage[2], anyOf(equalTo((double) -1), greaterThanOrEqualTo((double) 0)));
} else if (Constants.MAC_OS_X) { } else if (Constants.MAC_OS_X) {
// one minute load average is available, but 10-minute and 15-minute load averages are not // one minute load average is available, but 10-minute and 15-minute load averages are not
assertNotNull(loadAverage); assertNotNull(loadAverage);
@ -109,4 +103,26 @@ public class OsProbeTests extends ESTestCase {
assertThat(stats.getSwap().getUsed().getBytes(), equalTo(0L)); assertThat(stats.getSwap().getUsed().getBytes(), equalTo(0L));
} }
} }
public void testGetSystemLoadAverage() {
assumeTrue("test runs on Linux only", Constants.LINUX);
final OsProbe probe = new OsProbe() {
@Override
String readProcLoadavg() {
return "1.51 1.69 1.99 3/417 23251";
}
};
final double[] systemLoadAverage = probe.getSystemLoadAverage();
assertNotNull(systemLoadAverage);
assertThat(systemLoadAverage.length, equalTo(3));
// avoid silliness with representing doubles
assertThat(systemLoadAverage[0], equalTo(Double.parseDouble("1.51")));
assertThat(systemLoadAverage[1], equalTo(Double.parseDouble("1.69")));
assertThat(systemLoadAverage[2], equalTo(Double.parseDouble("1.99")));
}
} }