/*
* Copyright 2016 the original author or authors.
*
* Licensed 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.gradle.process.internal.health.memory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MeminfoAvailableMemory implements AvailableMemory {
// /proc/meminfo is in kB since Linux 4.0, see https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/proc/task_mmu.c?id=39a8804455fb23f09157341d3ba7db6d7ae6ee76#n22
private static final Pattern MEMINFO_LINE_PATTERN = Pattern.compile("^\\D+(\\d+) kB$");
private static final String MEMINFO_FILE_PATH = "/proc/meminfo";
private final Matcher meminfoMatcher;
public MeminfoAvailableMemory() {
// Initialize Matchers once and then reset them for performance
meminfoMatcher = MEMINFO_LINE_PATTERN.matcher("");
}
@Override
public long get() throws UnsupportedOperationException {
List<String> meminfoOutputLines;
try {
meminfoOutputLines = Files.readLines(new File(MEMINFO_FILE_PATH), Charset.defaultCharset());
} catch (IOException e) {
throw new UnsupportedOperationException("Unable to read free memory from " + MEMINFO_FILE_PATH, e);
}
long freeMemoryFromProcMeminfo = parseFreeMemoryFromMeminfo(meminfoOutputLines);
if (freeMemoryFromProcMeminfo == -1) {
throw new UnsupportedOperationException("Unable to get free memory from " + MEMINFO_FILE_PATH);
}
return freeMemoryFromProcMeminfo;
}
/**
* Given output from /proc/meminfo, return available memory in bytes.
*/
@VisibleForTesting
long parseFreeMemoryFromMeminfo(final List<String> meminfoLines) {
final Meminfo meminfo = new Meminfo();
// Linux 4.x: MemAvailable
// Linux 3.x: MemFree + Buffers + Cached + SReclaimable - Mapped
for (String line : meminfoLines) {
if (line.startsWith("MemAvailable")) {
return parseMeminfoBytes(line);
} else if (line.startsWith("MemFree")) {
meminfo.setMemFree(parseMeminfoBytes(line));
} else if (line.startsWith("Buffers")) {
meminfo.setBuffers(parseMeminfoBytes(line));
} else if (line.startsWith("Cached")) {
meminfo.setCached(parseMeminfoBytes(line));
} else if (line.startsWith("SReclaimable")) {
meminfo.setReclaimable(parseMeminfoBytes(line));
} else if (line.startsWith("Mapped")) {
meminfo.setMapped(parseMeminfoBytes(line));
}
}
return meminfo.getAvailableBytes();
}
/**
* Given a line from /proc/meminfo, return number value representing number of bytes.
*
* @param line String line from /proc/meminfo. Example: "MemAvailable: 2109560 kB"
* @return number from value transformed to bytes or -1 if unparsable. Example: 2_160_189_440
*/
private long parseMeminfoBytes(final String line) {
Matcher matcher = meminfoMatcher.reset(line);
if (matcher.matches()) {
return Long.parseLong(matcher.group(1)) * 1024;
}
throw new UnsupportedOperationException("Unable to parse /proc/meminfo output to get available memory");
}
private class Meminfo {
private long memFree = -1;
private long buffers = -1;
private long cached = -1;
private long reclaimable = -1;
private long mapped = -1;
void setMemFree(long memFree) {
this.memFree = memFree;
}
long getAvailableBytes() {
if (memFree != -1 && buffers != -1 && cached != -1 && reclaimable != -1 && mapped != -1) {
return memFree + buffers + cached + reclaimable - mapped;
}
return -1;
}
public void setBuffers(long buffers) {
this.buffers = buffers;
}
public void setCached(long cached) {
this.cached = cached;
}
void setReclaimable(long reclaimable) {
this.reclaimable = reclaimable;
}
public void setMapped(long mapped) {
this.mapped = mapped;
}
}
}