Skip to content

Commit 365c7ce

Browse files
committed
Workaround for JDK bug with total mem on Debian8 (elastic#68542)
1 parent 58fdadf commit 365c7ce

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
@@ -255,6 +255,71 @@ public void testCgroupProbeWithMissingMemory() {
255255
assertNull(cgroup);
256256
}
257257

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

0 commit comments

Comments
 (0)