Skip to content

Commit 2096050

Browse files
authored
Workaround for JDK bug with total mem on Debian8 (#68542)
1 parent 0f5af55 commit 2096050

File tree

2 files changed

+137
-2
lines changed

2 files changed

+137
-2
lines changed

server/src/main/java/org/elasticsearch/monitor/os/OsProbe.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,16 @@ public long getTotalPhysicalMemorySize() {
100100
return 0;
101101
}
102102
try {
103-
final long totalMem = (long) getTotalPhysicalMemorySize.invoke(osMxBean);
103+
long totalMem = (long) getTotalPhysicalMemorySize.invoke(osMxBean);
104104
if (totalMem < 0) {
105105
logger.debug("OS reported a negative total memory value [{}]", totalMem);
106106
return 0;
107107
}
108+
if (totalMem == 0 && isDebian8()) {
109+
// workaround for JDK bug on debian8: https://github.com/elastic/elasticsearch/issues/67089#issuecomment-756114654
110+
totalMem = getTotalMemFromProcMeminfo();
111+
}
112+
108113
return totalMem;
109114
} catch (Exception e) {
110115
logger.warn("exception retrieving total physical memory", e);
@@ -641,6 +646,55 @@ List<String> readOsRelease() throws IOException {
641646
}
642647
}
643648

649+
/**
650+
* Returns the lines from /proc/meminfo as a workaround for JDK bugs that prevent retrieval of total system memory
651+
* on some Linux variants such as Debian8.
652+
*/
653+
@SuppressForbidden(reason = "access /proc/meminfo")
654+
List<String> readProcMeminfo() throws IOException {
655+
final List<String> lines;
656+
if (Files.exists(PathUtils.get("/proc/meminfo"))) {
657+
lines = Files.readAllLines(PathUtils.get("/proc/meminfo"));
658+
assert lines != null && lines.isEmpty() == false;
659+
return lines;
660+
} else {
661+
return Collections.emptyList();
662+
}
663+
}
664+
665+
/**
666+
* Retrieves system total memory in bytes from /proc/meminfo
667+
*/
668+
long getTotalMemFromProcMeminfo() throws IOException {
669+
List<String> meminfoLines = readProcMeminfo();
670+
final List<String> memTotalLines = meminfoLines.stream().filter(line -> line.startsWith("MemTotal")).collect(Collectors.toList());
671+
assert memTotalLines.size() <= 1 : memTotalLines;
672+
if (memTotalLines.size() == 1) {
673+
final String memTotalLine = memTotalLines.get(0);
674+
int beginIdx = memTotalLine.indexOf("MemTotal:");
675+
int endIdx = memTotalLine.lastIndexOf(" kB");
676+
if (beginIdx + 9 < endIdx) {
677+
final String memTotalString = memTotalLine.substring(beginIdx + 9, endIdx).trim();
678+
try {
679+
long memTotalInKb = Long.parseLong(memTotalString);
680+
return memTotalInKb * 1024;
681+
} catch (NumberFormatException e) {
682+
logger.warn("Unable to retrieve total memory from meminfo line [" + memTotalLine + "]");
683+
return 0;
684+
}
685+
} else {
686+
logger.warn("Unable to retrieve total memory from meminfo line [" + memTotalLine + "]");
687+
return 0;
688+
}
689+
} else {
690+
return 0;
691+
}
692+
}
693+
694+
boolean isDebian8() throws IOException {
695+
return Constants.LINUX && getPrettyName().equals("Debian GNU/Linux 8 (jessie)");
696+
}
697+
644698
public OsStats osStats() {
645699
final OsStats.Cpu cpu = new OsStats.Cpu(getSystemCpuPercent(), getSystemLoadAverage());
646700
final OsStats.Mem mem = new OsStats.Mem(getTotalPhysicalMemorySize(), getFreePhysicalMemorySize());

server/src/test/java/org/elasticsearch/monitor/os/OsProbeTests.java

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,71 @@ public void testCgroupProbeWithMissingMemory() {
254254
assertNull(cgroup);
255255
}
256256

257+
public void testGetTotalMemFromProcMeminfo() throws Exception {
258+
// missing MemTotal line
259+
var meminfoLines = Arrays.asList(
260+
"MemFree: 8467692 kB",
261+
"MemAvailable: 39646240 kB",
262+
"Buffers: 4699504 kB",
263+
"Cached: 23290380 kB",
264+
"SwapCached: 0 kB",
265+
"Active: 43637908 kB",
266+
"Inactive: 8130280 kB"
267+
);
268+
OsProbe probe = buildStubOsProbe(true, "", List.of(), meminfoLines);
269+
assertThat(probe.getTotalMemFromProcMeminfo(), equalTo(0L));
270+
271+
// MemTotal line with invalid value
272+
meminfoLines = Arrays.asList(
273+
"MemTotal: invalid kB",
274+
"MemFree: 8467692 kB",
275+
"MemAvailable: 39646240 kB",
276+
"Buffers: 4699504 kB",
277+
"Cached: 23290380 kB",
278+
"SwapCached: 0 kB",
279+
"Active: 43637908 kB",
280+
"Inactive: 8130280 kB"
281+
);
282+
probe = buildStubOsProbe(true, "", List.of(), meminfoLines);
283+
assertThat(probe.getTotalMemFromProcMeminfo(), equalTo(0L));
284+
285+
// MemTotal line with invalid unit
286+
meminfoLines = Arrays.asList(
287+
"MemTotal: 39646240 MB",
288+
"MemFree: 8467692 kB",
289+
"MemAvailable: 39646240 kB",
290+
"Buffers: 4699504 kB",
291+
"Cached: 23290380 kB",
292+
"SwapCached: 0 kB",
293+
"Active: 43637908 kB",
294+
"Inactive: 8130280 kB"
295+
);
296+
probe = buildStubOsProbe(true, "", List.of(), meminfoLines);
297+
assertThat(probe.getTotalMemFromProcMeminfo(), equalTo(0L));
298+
299+
// MemTotal line with random valid value
300+
long memTotalInKb = randomLongBetween(1, Long.MAX_VALUE / 1024L);
301+
meminfoLines = Arrays.asList(
302+
"MemTotal: " + memTotalInKb + " kB",
303+
"MemFree: 8467692 kB",
304+
"MemAvailable: 39646240 kB",
305+
"Buffers: 4699504 kB",
306+
"Cached: 23290380 kB",
307+
"SwapCached: 0 kB",
308+
"Active: 43637908 kB",
309+
"Inactive: 8130280 kB"
310+
);
311+
probe = buildStubOsProbe(true, "", List.of(), meminfoLines);
312+
assertThat(probe.getTotalMemFromProcMeminfo(), equalTo(memTotalInKb * 1024L));
313+
}
314+
315+
public void testGetTotalMemoryOnDebian8() throws Exception {
316+
// tests the workaround for JDK bug on debian8: https://github.com/elastic/elasticsearch/issues/67089#issuecomment-756114654
317+
final OsProbe osProbe = new OsProbe();
318+
assumeTrue("runs only on Debian 8", osProbe.isDebian8());
319+
assertThat(osProbe.getTotalPhysicalMemorySize(), greaterThan(0L));
320+
}
321+
257322
private static List<String> getProcSelfGroupLines(String hierarchy) {
258323
return Arrays.asList(
259324
"10:freezer:/",
@@ -282,12 +347,14 @@ private static OsProbe buildStubOsProbe(final boolean areCgroupStatsAvailable, f
282347
* @param areCgroupStatsAvailable whether or not cgroup data is available. Normally OsProbe establishes this for itself.
283348
* @param hierarchy a mock value used to generate a cgroup hierarchy.
284349
* @param procSelfCgroupLines the lines that will be used as the content of <code>/proc/self/cgroup</code>
350+
* @param procMeminfoLines lines that will be used as the content of <code>/proc/meminfo</code>
285351
* @return a test instance
286352
*/
287353
private static OsProbe buildStubOsProbe(
288354
final boolean areCgroupStatsAvailable,
289355
final String hierarchy,
290-
List<String> procSelfCgroupLines
356+
List<String> procSelfCgroupLines,
357+
List<String> procMeminfoLines
291358
) {
292359
return new OsProbe() {
293360
@Override
@@ -338,6 +405,20 @@ String readSysFsCgroupMemoryUsageInBytes(String controlGroup) {
338405
boolean areCgroupStatsAvailable() {
339406
return areCgroupStatsAvailable;
340407
}
408+
409+
@Override
410+
List<String> readProcMeminfo() throws IOException {
411+
return procMeminfoLines;
412+
}
341413
};
342414
}
415+
416+
private static OsProbe buildStubOsProbe(
417+
final boolean areCgroupStatsAvailable,
418+
final String hierarchy,
419+
List<String> procSelfCgroupLines
420+
) {
421+
return buildStubOsProbe(areCgroupStatsAvailable, hierarchy, procSelfCgroupLines, List.of());
422+
}
423+
343424
}

0 commit comments

Comments
 (0)