blob: c8ff21a37200ffb52af5d624d9b6bfc1bfe1aff3 [file] [log] [blame]
/**
* 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.hadoop.util;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.util.Shell.ShellCommandExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Plugin to calculate resource information on Linux systems.
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class SysInfoLinux extends SysInfo {
private static final Logger LOG = LoggerFactory.getLogger(SysInfoLinux.class);
/**
* proc's meminfo virtual file has keys-values in the format
* "key:[ \t]*value[ \t]kB".
*/
private static final String PROCFS_MEMFILE = "/proc/meminfo";
private static final Pattern PROCFS_MEMFILE_FORMAT =
Pattern.compile("^([a-zA-Z_()]*):[ \t]*([0-9]*)[ \t]*(kB)?");
// We need the values for the following keys in meminfo
private static final String MEMTOTAL_STRING = "MemTotal";
private static final String SWAPTOTAL_STRING = "SwapTotal";
private static final String MEMFREE_STRING = "MemFree";
private static final String SWAPFREE_STRING = "SwapFree";
private static final String INACTIVE_STRING = "Inactive";
private static final String INACTIVEFILE_STRING = "Inactive(file)";
private static final String HARDWARECORRUPTED_STRING = "HardwareCorrupted";
private static final String HUGEPAGESTOTAL_STRING = "HugePages_Total";
private static final String HUGEPAGESIZE_STRING = "Hugepagesize";
/**
* Patterns for parsing /proc/cpuinfo.
*/
private static final String PROCFS_CPUINFO = "/proc/cpuinfo";
private static final Pattern PROCESSOR_FORMAT =
Pattern.compile("^processor[ \t]:[ \t]*([0-9]*)");
private static final Pattern FREQUENCY_FORMAT =
Pattern.compile("^cpu MHz[ \t]*:[ \t]*([0-9.]*)");
private static final Pattern PHYSICAL_ID_FORMAT =
Pattern.compile("^physical id[ \t]*:[ \t]*([0-9]*)");
private static final Pattern CORE_ID_FORMAT =
Pattern.compile("^core id[ \t]*:[ \t]*([0-9]*)");
/**
* Pattern for parsing /proc/stat.
*/
private static final String PROCFS_STAT = "/proc/stat";
private static final Pattern CPU_TIME_FORMAT =
Pattern.compile("^cpu[ \t]*([0-9]*)" +
"[ \t]*([0-9]*)[ \t]*([0-9]*)[ \t].*");
private CpuTimeTracker cpuTimeTracker;
/**
* Pattern for parsing /proc/net/dev.
*/
private static final String PROCFS_NETFILE = "/proc/net/dev";
private static final Pattern PROCFS_NETFILE_FORMAT =
Pattern .compile("^[ \t]*([a-zA-Z]+[0-9]*):" +
"[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)" +
"[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)" +
"[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)" +
"[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+).*");
/**
* Pattern for parsing /proc/diskstats.
*/
private static final String PROCFS_DISKSFILE = "/proc/diskstats";
private static final Pattern PROCFS_DISKSFILE_FORMAT =
Pattern.compile("^[ \t]*([0-9]+)[ \t]*([0-9 ]+)" +
"(?!([a-zA-Z]+[0-9]+))([a-zA-Z]+)" +
"[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)" +
"[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)" +
"[ \t]*([0-9]+)[ \t]*([0-9]+)[ \t]*([0-9]+)");
/**
* Pattern for parsing /sys/block/partition_name/queue/hw_sector_size.
*/
private static final Pattern PROCFS_DISKSECTORFILE_FORMAT =
Pattern.compile("^([0-9]+)");
private String procfsMemFile;
private String procfsCpuFile;
private String procfsStatFile;
private String procfsNetFile;
private String procfsDisksFile;
private long jiffyLengthInMillis;
private long ramSize = 0;
private long swapSize = 0;
private long ramSizeFree = 0; // free ram space on the machine (kB)
private long swapSizeFree = 0; // free swap space on the machine (kB)
private long inactiveSize = 0; // inactive memory (kB)
private long inactiveFileSize = -1; // inactive cache memory, -1 if not there
private long hardwareCorruptSize = 0; // RAM corrupt and not available
private long hugePagesTotal = 0; // # of hugepages reserved
private long hugePageSize = 0; // # size of each hugepage
/* number of logical processors on the system. */
private int numProcessors = 0;
/* number of physical cores on the system. */
private int numCores = 0;
private long cpuFrequency = 0L; // CPU frequency on the system (kHz)
private long numNetBytesRead = 0L; // aggregated bytes read from network
private long numNetBytesWritten = 0L; // aggregated bytes written to network
private long numDisksBytesRead = 0L; // aggregated bytes read from disks
private long numDisksBytesWritten = 0L; // aggregated bytes written to disks
private boolean readMemInfoFile = false;
private boolean readCpuInfoFile = false;
/* map for every disk its sector size */
private HashMap<String, Integer> perDiskSectorSize = null;
public static final long PAGE_SIZE = getConf("PAGESIZE");
public static final long JIFFY_LENGTH_IN_MILLIS =
Math.max(Math.round(1000D / getConf("CLK_TCK")), -1);
private static long getConf(String attr) {
if(Shell.LINUX) {
try {
ShellCommandExecutor shellExecutorClk = new ShellCommandExecutor(
new String[] {"getconf", attr });
shellExecutorClk.execute();
return Long.parseLong(shellExecutorClk.getOutput().replace("\n", ""));
} catch (IOException|NumberFormatException e) {
return -1;
}
}
return -1;
}
/**
* Get current time.
* @return Unix time stamp in millisecond
*/
long getCurrentTime() {
return System.currentTimeMillis();
}
public SysInfoLinux() {
this(PROCFS_MEMFILE, PROCFS_CPUINFO, PROCFS_STAT,
PROCFS_NETFILE, PROCFS_DISKSFILE, JIFFY_LENGTH_IN_MILLIS);
}
/**
* Constructor which allows assigning the /proc/ directories. This will be
* used only in unit tests.
* @param procfsMemFile fake file for /proc/meminfo
* @param procfsCpuFile fake file for /proc/cpuinfo
* @param procfsStatFile fake file for /proc/stat
* @param procfsNetFile fake file for /proc/net/dev
* @param procfsDisksFile fake file for /proc/diskstats
* @param jiffyLengthInMillis fake jiffy length value
*/
@VisibleForTesting
public SysInfoLinux(String procfsMemFile,
String procfsCpuFile,
String procfsStatFile,
String procfsNetFile,
String procfsDisksFile,
long jiffyLengthInMillis) {
this.procfsMemFile = procfsMemFile;
this.procfsCpuFile = procfsCpuFile;
this.procfsStatFile = procfsStatFile;
this.procfsNetFile = procfsNetFile;
this.procfsDisksFile = procfsDisksFile;
this.jiffyLengthInMillis = jiffyLengthInMillis;
this.cpuTimeTracker = new CpuTimeTracker(jiffyLengthInMillis);
this.perDiskSectorSize = new HashMap<String, Integer>();
}
/**
* Read /proc/meminfo, parse and compute memory information only once.
*/
private void readProcMemInfoFile() {
readProcMemInfoFile(false);
}
/**
*
* Wrapper for Long.parseLong() that returns zero if the value is
* invalid. Under some circumstances, swapFree in /proc/meminfo can
* go negative, reported as a very large decimal value.
*/
private long safeParseLong(String strVal) {
long parsedVal;
try {
parsedVal = Long.parseLong(strVal);
} catch (NumberFormatException nfe) {
parsedVal = 0;
}
return parsedVal;
}
/**
* Read /proc/meminfo, parse and compute memory information.
* @param readAgain if false, read only on the first time
*/
private void readProcMemInfoFile(boolean readAgain) {
if (readMemInfoFile && !readAgain) {
return;
}
// Read "/proc/memInfo" file
BufferedReader in;
InputStreamReader fReader;
try {
fReader = new InputStreamReader(
new FileInputStream(procfsMemFile), Charset.forName("UTF-8"));
in = new BufferedReader(fReader);
} catch (FileNotFoundException f) {
// shouldn't happen....
LOG.warn("Couldn't read " + procfsMemFile
+ "; can't determine memory settings");
return;
}
Matcher mat;
try {
String str = in.readLine();
while (str != null) {
mat = PROCFS_MEMFILE_FORMAT.matcher(str);
if (mat.find()) {
if (mat.group(1).equals(MEMTOTAL_STRING)) {
ramSize = Long.parseLong(mat.group(2));
} else if (mat.group(1).equals(SWAPTOTAL_STRING)) {
swapSize = Long.parseLong(mat.group(2));
} else if (mat.group(1).equals(MEMFREE_STRING)) {
ramSizeFree = safeParseLong(mat.group(2));
} else if (mat.group(1).equals(SWAPFREE_STRING)) {
swapSizeFree = safeParseLong(mat.group(2));
} else if (mat.group(1).equals(INACTIVE_STRING)) {
inactiveSize = Long.parseLong(mat.group(2));
} else if (mat.group(1).equals(INACTIVEFILE_STRING)) {
inactiveFileSize = Long.parseLong(mat.group(2));
} else if (mat.group(1).equals(HARDWARECORRUPTED_STRING)) {
hardwareCorruptSize = Long.parseLong(mat.group(2));
} else if (mat.group(1).equals(HUGEPAGESTOTAL_STRING)) {
hugePagesTotal = Long.parseLong(mat.group(2));
} else if (mat.group(1).equals(HUGEPAGESIZE_STRING)) {
hugePageSize = Long.parseLong(mat.group(2));
}
}
str = in.readLine();
}
} catch (IOException io) {
LOG.warn("Error reading the stream " + io);
} finally {
// Close the streams
try {
fReader.close();
try {
in.close();
} catch (IOException i) {
LOG.warn("Error closing the stream " + in);
}
} catch (IOException i) {
LOG.warn("Error closing the stream " + fReader);
}
}
readMemInfoFile = true;
}
/**
* Read /proc/cpuinfo, parse and calculate CPU information.
*/
private void readProcCpuInfoFile() {
// This directory needs to be read only once
if (readCpuInfoFile) {
return;
}
HashSet<String> coreIdSet = new HashSet<>();
// Read "/proc/cpuinfo" file
BufferedReader in;
InputStreamReader fReader;
try {
fReader = new InputStreamReader(
new FileInputStream(procfsCpuFile), Charset.forName("UTF-8"));
in = new BufferedReader(fReader);
} catch (FileNotFoundException f) {
// shouldn't happen....
LOG.warn("Couldn't read " + procfsCpuFile + "; can't determine cpu info");
return;
}
Matcher mat;
try {
numProcessors = 0;
numCores = 1;
String currentPhysicalId = "";
String str = in.readLine();
while (str != null) {
mat = PROCESSOR_FORMAT.matcher(str);
if (mat.find()) {
numProcessors++;
}
mat = FREQUENCY_FORMAT.matcher(str);
if (mat.find()) {
cpuFrequency = (long)(Double.parseDouble(mat.group(1)) * 1000); // kHz
}
mat = PHYSICAL_ID_FORMAT.matcher(str);
if (mat.find()) {
currentPhysicalId = str;
}
mat = CORE_ID_FORMAT.matcher(str);
if (mat.find()) {
coreIdSet.add(currentPhysicalId + " " + str);
numCores = coreIdSet.size();
}
str = in.readLine();
}
} catch (IOException io) {
LOG.warn("Error reading the stream " + io);
} finally {
// Close the streams
try {
fReader.close();
try {
in.close();
} catch (IOException i) {
LOG.warn("Error closing the stream " + in);
}
} catch (IOException i) {
LOG.warn("Error closing the stream " + fReader);
}
}
readCpuInfoFile = true;
}
/**
* Read /proc/stat file, parse and calculate cumulative CPU.
*/
private void readProcStatFile() {
// Read "/proc/stat" file
BufferedReader in;
InputStreamReader fReader;
try {
fReader = new InputStreamReader(
new FileInputStream(procfsStatFile), Charset.forName("UTF-8"));
in = new BufferedReader(fReader);
} catch (FileNotFoundException f) {
// shouldn't happen....
return;
}
Matcher mat;
try {
String str = in.readLine();
while (str != null) {
mat = CPU_TIME_FORMAT.matcher(str);
if (mat.find()) {
long uTime = Long.parseLong(mat.group(1));
long nTime = Long.parseLong(mat.group(2));
long sTime = Long.parseLong(mat.group(3));
cpuTimeTracker.updateElapsedJiffies(
BigInteger.valueOf(uTime + nTime + sTime),
getCurrentTime());
break;
}
str = in.readLine();
}
} catch (IOException io) {
LOG.warn("Error reading the stream " + io);
} finally {
// Close the streams
try {
fReader.close();
try {
in.close();
} catch (IOException i) {
LOG.warn("Error closing the stream " + in);
}
} catch (IOException i) {
LOG.warn("Error closing the stream " + fReader);
}
}
}
/**
* Read /proc/net/dev file, parse and calculate amount
* of bytes read and written through the network.
*/
private void readProcNetInfoFile() {
numNetBytesRead = 0L;
numNetBytesWritten = 0L;
// Read "/proc/net/dev" file
BufferedReader in;
InputStreamReader fReader;
try {
fReader = new InputStreamReader(
new FileInputStream(procfsNetFile), Charset.forName("UTF-8"));
in = new BufferedReader(fReader);
} catch (FileNotFoundException f) {
return;
}
Matcher mat;
try {
String str = in.readLine();
while (str != null) {
mat = PROCFS_NETFILE_FORMAT.matcher(str);
if (mat.find()) {
assert mat.groupCount() >= 16;
// ignore loopback interfaces
if (mat.group(1).equals("lo")) {
str = in.readLine();
continue;
}
numNetBytesRead += Long.parseLong(mat.group(2));
numNetBytesWritten += Long.parseLong(mat.group(10));
}
str = in.readLine();
}
} catch (IOException io) {
LOG.warn("Error reading the stream " + io);
} finally {
// Close the streams
try {
fReader.close();
try {
in.close();
} catch (IOException i) {
LOG.warn("Error closing the stream " + in);
}
} catch (IOException i) {
LOG.warn("Error closing the stream " + fReader);
}
}
}
/**
* Read /proc/diskstats file, parse and calculate amount
* of bytes read and written from/to disks.
*/
private void readProcDisksInfoFile() {
numDisksBytesRead = 0L;
numDisksBytesWritten = 0L;
// Read "/proc/diskstats" file
BufferedReader in;
try {
in = new BufferedReader(new InputStreamReader(
new FileInputStream(procfsDisksFile), Charset.forName("UTF-8")));
} catch (FileNotFoundException f) {
return;
}
Matcher mat;
try {
String str = in.readLine();
while (str != null) {
mat = PROCFS_DISKSFILE_FORMAT.matcher(str);
if (mat.find()) {
String diskName = mat.group(4);
assert diskName != null;
// ignore loop or ram partitions
if (diskName.contains("loop") || diskName.contains("ram")) {
str = in.readLine();
continue;
}
Integer sectorSize;
synchronized (perDiskSectorSize) {
sectorSize = perDiskSectorSize.get(diskName);
if (null == sectorSize) {
// retrieve sectorSize
// if unavailable or error, assume 512
sectorSize = readDiskBlockInformation(diskName, 512);
perDiskSectorSize.put(diskName, sectorSize);
}
}
String sectorsRead = mat.group(7);
String sectorsWritten = mat.group(11);
if (null == sectorsRead || null == sectorsWritten) {
return;
}
numDisksBytesRead += Long.parseLong(sectorsRead) * sectorSize;
numDisksBytesWritten += Long.parseLong(sectorsWritten) * sectorSize;
}
str = in.readLine();
}
} catch (IOException e) {
LOG.warn("Error reading the stream " + procfsDisksFile, e);
} finally {
// Close the streams
try {
in.close();
} catch (IOException e) {
LOG.warn("Error closing the stream " + procfsDisksFile, e);
}
}
}
/**
* Read /sys/block/diskName/queue/hw_sector_size file, parse and calculate
* sector size for a specific disk.
* @return sector size of specified disk, or defSector
*/
int readDiskBlockInformation(String diskName, int defSector) {
assert perDiskSectorSize != null && diskName != null;
String procfsDiskSectorFile =
"/sys/block/" + diskName + "/queue/hw_sector_size";
BufferedReader in;
try {
in = new BufferedReader(new InputStreamReader(
new FileInputStream(procfsDiskSectorFile),
Charset.forName("UTF-8")));
} catch (FileNotFoundException f) {
return defSector;
}
Matcher mat;
try {
String str = in.readLine();
while (str != null) {
mat = PROCFS_DISKSECTORFILE_FORMAT.matcher(str);
if (mat.find()) {
String secSize = mat.group(1);
if (secSize != null) {
return Integer.parseInt(secSize);
}
}
str = in.readLine();
}
return defSector;
} catch (IOException|NumberFormatException e) {
LOG.warn("Error reading the stream " + procfsDiskSectorFile, e);
return defSector;
} finally {
// Close the streams
try {
in.close();
} catch (IOException e) {
LOG.warn("Error closing the stream " + procfsDiskSectorFile, e);
}
}
}
/** {@inheritDoc} */
@Override
public long getPhysicalMemorySize() {
readProcMemInfoFile();
return (ramSize
- hardwareCorruptSize
- (hugePagesTotal * hugePageSize)) * 1024;
}
/** {@inheritDoc} */
@Override
public long getVirtualMemorySize() {
return getPhysicalMemorySize() + (swapSize * 1024);
}
/** {@inheritDoc} */
@Override
public long getAvailablePhysicalMemorySize() {
readProcMemInfoFile(true);
long inactive = inactiveFileSize != -1
? inactiveFileSize
: inactiveSize;
return (ramSizeFree + inactive) * 1024;
}
/** {@inheritDoc} */
@Override
public long getAvailableVirtualMemorySize() {
return getAvailablePhysicalMemorySize() + (swapSizeFree * 1024);
}
/** {@inheritDoc} */
@Override
public int getNumProcessors() {
readProcCpuInfoFile();
return numProcessors;
}
/** {@inheritDoc} */
@Override
public int getNumCores() {
readProcCpuInfoFile();
return numCores;
}
/** {@inheritDoc} */
@Override
public long getCpuFrequency() {
readProcCpuInfoFile();
return cpuFrequency;
}
/** {@inheritDoc} */
@Override
public long getCumulativeCpuTime() {
readProcStatFile();
return cpuTimeTracker.getCumulativeCpuTime();
}
/** {@inheritDoc} */
@Override
public float getCpuUsagePercentage() {
readProcStatFile();
float overallCpuUsage = cpuTimeTracker.getCpuTrackerUsagePercent();
if (overallCpuUsage != CpuTimeTracker.UNAVAILABLE) {
overallCpuUsage = overallCpuUsage / getNumProcessors();
}
return overallCpuUsage;
}
/** {@inheritDoc} */
@Override
public float getNumVCoresUsed() {
readProcStatFile();
float overallVCoresUsage = cpuTimeTracker.getCpuTrackerUsagePercent();
if (overallVCoresUsage != CpuTimeTracker.UNAVAILABLE) {
overallVCoresUsage = overallVCoresUsage / 100F;
}
return overallVCoresUsage;
}
/** {@inheritDoc} */
@Override
public long getNetworkBytesRead() {
readProcNetInfoFile();
return numNetBytesRead;
}
/** {@inheritDoc} */
@Override
public long getNetworkBytesWritten() {
readProcNetInfoFile();
return numNetBytesWritten;
}
@Override
public long getStorageBytesRead() {
readProcDisksInfoFile();
return numDisksBytesRead;
}
@Override
public long getStorageBytesWritten() {
readProcDisksInfoFile();
return numDisksBytesWritten;
}
/**
* Test the {@link SysInfoLinux}.
*
* @param args - arguments to this calculator test
*/
public static void main(String[] args) {
SysInfoLinux plugin = new SysInfoLinux();
System.out.println("Physical memory Size (bytes) : "
+ plugin.getPhysicalMemorySize());
System.out.println("Total Virtual memory Size (bytes) : "
+ plugin.getVirtualMemorySize());
System.out.println("Available Physical memory Size (bytes) : "
+ plugin.getAvailablePhysicalMemorySize());
System.out.println("Total Available Virtual memory Size (bytes) : "
+ plugin.getAvailableVirtualMemorySize());
System.out.println("Number of Processors : " + plugin.getNumProcessors());
System.out.println("CPU frequency (kHz) : " + plugin.getCpuFrequency());
System.out.println("Cumulative CPU time (ms) : " +
plugin.getCumulativeCpuTime());
System.out.println("Total network read (bytes) : "
+ plugin.getNetworkBytesRead());
System.out.println("Total network written (bytes) : "
+ plugin.getNetworkBytesWritten());
System.out.println("Total storage read (bytes) : "
+ plugin.getStorageBytesRead());
System.out.println("Total storage written (bytes) : "
+ plugin.getStorageBytesWritten());
try {
// Sleep so we can compute the CPU usage
Thread.sleep(500L);
} catch (InterruptedException e) {
// do nothing
}
System.out.println("CPU usage % : " + plugin.getCpuUsagePercentage());
}
@VisibleForTesting
void setReadCpuInfoFile(boolean readCpuInfoFileValue) {
this.readCpuInfoFile = readCpuInfoFileValue;
}
public long getJiffyLengthInMillis() {
return this.jiffyLengthInMillis;
}
}