/**
* Oshi (https://github.com/oshi/oshi)
*
* Copyright (c) 2010 - 2017 The Oshi Project Team
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Maintainers:
* dblock[at]dblock[dot]org
* widdis[at]gmail[dot]com
* enrico.bianchi[at]gmail[dot]com
*
* Contributors:
* https://github.com/oshi/oshi/graphs/contributors
*/
package oshi.hardware.platform.unix.freebsd;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import oshi.hardware.common.AbstractCentralProcessor;
import oshi.jna.platform.unix.CLibrary.Timeval;
import oshi.jna.platform.unix.freebsd.Libc;
import oshi.jna.platform.unix.freebsd.Libc.CpTime;
import oshi.util.ExecutingCommand;
import oshi.util.FileUtil;
import oshi.util.ParseUtil;
import oshi.util.platform.unix.freebsd.BsdSysctlUtil;
/**
* A CPU
*
* @author widdis[at]gmail[dot]com
*/
public class FreeBsdCentralProcessor extends AbstractCentralProcessor {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(FreeBsdCentralProcessor.class);
private static final Pattern CPUMASK = Pattern.compile(".*<cpu\\s.*mask=\"(?:0x)?(\\p{XDigit}+)\".*>.*</cpu>.*");
private static final Pattern CPUINFO = Pattern
.compile("Origin=\"([^\"]*)\".*Id=(\\S+).*Family=(\\S+).*Model=(\\S+).*Stepping=(\\S+).*");
private static final Pattern CPUINFO2 = Pattern.compile("Features=(\\S+)<.*");
private static final long BOOTTIME;
static {
Timeval tv = new Timeval();
if (!BsdSysctlUtil.sysctl("kern.boottime", tv) || tv.tv_sec == 0) {
// Usually this works. If it doesn't, fall back to text parsing.
// Boot time will be the first consecutive string of digits.
BOOTTIME = ParseUtil.parseLongOrDefault(
ExecutingCommand.getFirstAnswer("sysctl -n kern.boottime").split(",")[0].replaceAll("\\D", ""),
System.currentTimeMillis() / 1000);
} else {
// tv now points to a 128-bit timeval structure for boot time.
// First 8 bytes are seconds, second 8 bytes are microseconds
// (we ignore)
BOOTTIME = tv.tv_sec;
}
}
/**
* Create a Processor
*/
public FreeBsdCentralProcessor() {
super();
// Initialize class variables
initVars();
// Initialize tick arrays
initTicks();
LOG.debug("Initialized Processor");
}
private void initVars() {
setName(BsdSysctlUtil.sysctl("hw.model", ""));
// This is apparently the only reliable source for this stuff on
// FreeBSD...
long processorID = 0L;
List<String> cpuInfo = FileUtil.readFile("/var/run/dmesg.boot");
for (String line : cpuInfo) {
line = line.trim();
// Prefer hw.model to this one
if (line.startsWith("CPU:") && getName().isEmpty()) {
setName(line.replace("CPU:", "").trim());
} else if (line.startsWith("Origin=")) {
Matcher m = CPUINFO.matcher(line);
if (m.matches()) {
setVendor(m.group(1));
processorID |= Long.decode(m.group(2));
setFamily(Integer.decode(m.group(3)).toString());
setModel(Integer.decode(m.group(4)).toString());
setStepping(Integer.decode(m.group(5)).toString());
}
} else if (line.startsWith("Features=")) {
Matcher m = CPUINFO2.matcher(line);
if (m.matches()) {
processorID |= Long.decode(m.group(1)) << 32;
}
// No further interest in this file
break;
}
}
setCpu64(ExecutingCommand.getFirstAnswer("uname -m").trim().contains("64"));
setProcessorID(getProcessorID(processorID));
}
/**
* Updates logical and physical processor counts from psrinfo
*/
@Override
protected void calculateProcessorCounts() {
String[] topology = BsdSysctlUtil.sysctl("kern.sched.topology_spec", "").split("\\n|\\r");
long physMask = 0;
long virtMask = 0;
long lastMask = 0;
for (String topo : topology) {
if (topo.contains("<cpu")) {
// Find <cpu> tag and extract bits
Matcher m = CPUMASK.matcher(topo);
if (m.matches()) {
// Add this processor mask to cpus. Regex guarantees parsing
lastMask = Long.parseLong(m.group(1), 16);
physMask |= lastMask;
virtMask |= lastMask;
}
} else if (topo.contains("<flags>")
&& (topo.contains("HTT") || topo.contains("SMT") || topo.contains("THREAD"))) {
// These are virtual cpus, remove processor mask from physical
physMask &= ~lastMask;
}
}
this.logicalProcessorCount = Long.bitCount(virtMask);
this.physicalProcessorCount = Long.bitCount(physMask);
if (this.logicalProcessorCount < 1) {
LOG.error("Couldn't find logical processor count. Assuming 1.");
this.logicalProcessorCount = 1;
}
if (this.physicalProcessorCount < 1) {
LOG.error("Couldn't find physical processor count. Assuming 1.");
this.physicalProcessorCount = 1;
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized long[] getSystemCpuLoadTicks() {
long[] ticks = new long[TickType.values().length];
CpTime cpTime = new CpTime();
BsdSysctlUtil.sysctl("kern.cp_time", cpTime);
ticks[TickType.USER.getIndex()] = cpTime.cpu_ticks[Libc.CP_USER];
ticks[TickType.NICE.getIndex()] = cpTime.cpu_ticks[Libc.CP_NICE];
ticks[TickType.SYSTEM.getIndex()] = cpTime.cpu_ticks[Libc.CP_SYS];
ticks[TickType.IRQ.getIndex()] = cpTime.cpu_ticks[Libc.CP_INTR];
ticks[TickType.IDLE.getIndex()] = cpTime.cpu_ticks[Libc.CP_IDLE];
return ticks;
}
/**
* {@inheritDoc}
*/
@Override
public double[] getSystemLoadAverage(int nelem) {
if (nelem < 1 || nelem > 3) {
throw new IllegalArgumentException("Must include from one to three elements.");
}
double[] average = new double[nelem];
int retval = Libc.INSTANCE.getloadavg(average, nelem);
if (retval < nelem) {
for (int i = Math.max(retval, 0); i < average.length; i++) {
average[i] = -1d;
}
}
return average;
}
/**
* {@inheritDoc}
*/
@Override
public long[][] getProcessorCpuLoadTicks() {
long[][] ticks = new long[this.logicalProcessorCount][TickType.values().length];
// Allocate memory for array of CPTime
int offset = new CpTime().size();
int size = offset * this.logicalProcessorCount;
Pointer p = new Memory(size);
String name = "kern.cp_times";
// Fetch
if (0 != Libc.INSTANCE.sysctlbyname(name, p, new IntByReference(size), null, 0)) {
LOG.error("Failed syctl call: {}, Error code: {}", name, Native.getLastError());
return ticks;
}
// p now points to the data; need to copy each element
for (int cpu = 0; cpu < this.logicalProcessorCount; cpu++) {
ticks[cpu][TickType.USER.getIndex()] = p.getLong((long) offset * cpu + Libc.CP_USER * Libc.UINT64_SIZE);
ticks[cpu][TickType.NICE.getIndex()] = p.getLong((long) offset * cpu + Libc.CP_NICE * Libc.UINT64_SIZE);
ticks[cpu][TickType.SYSTEM.getIndex()] = p.getLong((long) offset * cpu + Libc.CP_SYS * Libc.UINT64_SIZE);
ticks[cpu][TickType.IRQ.getIndex()] = p.getLong((long) offset * cpu + Libc.CP_INTR * Libc.UINT64_SIZE);
ticks[cpu][TickType.IDLE.getIndex()] = p.getLong((long) offset * cpu + Libc.CP_IDLE * Libc.UINT64_SIZE);
}
return ticks;
}
/**
* {@inheritDoc}
*/
@Override
public long getSystemUptime() {
return System.currentTimeMillis() / 1000 - BOOTTIME;
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public String getSystemSerialNumber() {
return new FreeBsdComputerSystem().getSerialNumber();
}
/**
* Fetches the ProcessorID from dmidecode (if possible with root
* permissions), otherwise uses the values from /var/run/dmesg.boot
*
* @param processorID
* @return The ProcessorID string
*/
private String getProcessorID(long processorID) {
boolean procInfo = false;
String marker = "Processor Information";
for (String checkLine : ExecutingCommand.runNative("dmidecode -t system")) {
if (!procInfo && checkLine.contains(marker)) {
marker = "ID:";
procInfo = true;
} else if (procInfo && checkLine.contains(marker)) {
return checkLine.split(marker)[1].trim();
}
}
// If we've gotten this far, dmidecode failed. Used the passed-in values
return String.format("%016X", processorID);
}
}