бла бла бла

This commit is contained in:
2026-10-28 15:50:34 +01:00
parent 2e8428de4e
commit fb27de5e7f
21 changed files with 858 additions and 541 deletions
@@ -1,66 +1,89 @@
package com.htopweb.system;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.OperatingSystem;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@Service
public class SystemMetricsService {
private final SystemInfo _systemInfo;
private final HardwareAbstractionLayer _hardware;
private static final Path HOST_ROOT = Path.of("/host/root");
private static final Path HOST_PROC = HOST_ROOT.resolve("proc");
private final OperatingSystem _operatingSystem;
private final CentralProcessor _processor;
private final GlobalMemory _memory;
private long[] _previousCpuTicks;
private CpuTicks _previousHostCpuTicks;
private static final Logger log =
LoggerFactory.getLogger(SystemMetricsService.class);
public SystemMetricsService() {
_systemInfo = new SystemInfo();
_hardware = _systemInfo.getHardware();
_operatingSystem = _systemInfo.getOperatingSystem();
_processor = _hardware.getProcessor();
_memory = _hardware.getMemory();
SystemInfo systemInfo = new SystemInfo();
HardwareAbstractionLayer hardware = systemInfo.getHardware();
_previousCpuTicks = _processor.getSystemCpuLoadTicks();
_operatingSystem = systemInfo.getOperatingSystem();
_processor = hardware.getProcessor();
_memory = hardware.getMemory();
_previousHostCpuTicks = readHostCpuTicks();
}
public synchronized SystemMetricsResponse getMetrics() {
long[] currentCpuTicks = _processor.getSystemCpuLoadTicks();
CpuTicks currentTicks = readHostCpuTicks();
double cpuUsage = 0.0;
double cpuUsage = _processor.getSystemCpuLoadBetweenTicks(
_previousCpuTicks,
currentCpuTicks
);
if (currentTicks != null && _previousHostCpuTicks != null) {
long totalDelta = currentTicks.total() - _previousHostCpuTicks.total();
long idleDelta = currentTicks.idle() - _previousHostCpuTicks.idle();
_previousCpuTicks = currentCpuTicks;
if (Double.isNaN(cpuUsage) || Double.isInfinite(cpuUsage)) {
cpuUsage = 0.0;
if (totalDelta > 0 && idleDelta >= 0 && idleDelta <= totalDelta) {
cpuUsage = (double) (totalDelta - idleDelta) / totalDelta;
}
}
// Task Manager displays system CPU usage in the 0–100% range.
cpuUsage = Math.max(0.0, Math.min(1.0, cpuUsage));
if (currentTicks != null) {
_previousHostCpuTicks = currentTicks;
}
Map<String, Long> hostMemory = readHostMemory();
long totalMemory = hostMemory.getOrDefault("MemTotal", _memory.getTotal());
long availableMemory = hostMemory.getOrDefault(
"MemAvailable",
_memory.getAvailable()
);
availableMemory = Math.max(0L, Math.min(totalMemory, availableMemory));
long totalMemory = _memory.getTotal();
long availableMemory = _memory.getAvailable();
long usedMemory = Math.max(0L, totalMemory - availableMemory);
double memoryUsage = totalMemory > 0
? (double) usedMemory / totalMemory * 100.0
: 0.0;
Map<String, String> osRelease = readHostOsRelease();
return new SystemMetricsResponse(
_operatingSystem.getFamily(),
_operatingSystem.getVersionInfo().getVersion(),
_operatingSystem.getNetworkParams().getHostName(),
getHostOsFamily(osRelease),
getHostOsVersion(osRelease),
readHostHostname(),
_processor.getProcessorIdentifier().getName(),
_processor.getLogicalProcessorCount(),
getLogicalProcessorCount(),
round(cpuUsage * 100.0),
totalMemory,
availableMemory,
@@ -69,7 +92,187 @@ public class SystemMetricsService {
);
}
private CpuTicks readHostCpuTicks() {
Path statFile = HOST_PROC.resolve("stat");
try {
if (!Files.isRegularFile(statFile)
|| !Files.isReadable(statFile)) {
log.warn("Host CPU statistics are unavailable: {}", statFile);
return null;
}
for (String line : Files.readAllLines(statFile)) {
if (!line.startsWith("cpu ")) {
continue;
}
String[] fields = line.trim().split("\\s+");
if (fields.length < 5) {
log.warn("Invalid host CPU statistics in {}", statFile);
return null;
}
long total = 0L;
for (int i = 1; i < fields.length; i++) {
total += Long.parseLong(fields[i]);
}
long idle = Long.parseLong(fields[4]);
if (fields.length > 5) {
idle += Long.parseLong(fields[5]);
}
return new CpuTicks(total, idle);
}
log.warn("Aggregate CPU line not found in {}", statFile);
} catch (IOException | NumberFormatException e) {
log.warn("Failed to read host CPU statistics: {}", e.getMessage());
}
return null;
}
private Map<String, Long> readHostMemory() {
Map<String, Long> values = new HashMap<>();
Path meminfoFile = HOST_PROC.resolve("meminfo");
try {
if (!Files.isRegularFile(meminfoFile)) {
return values;
}
for (String line : Files.readAllLines(meminfoFile)) {
int separator = line.indexOf(':');
if (separator < 0) {
continue;
}
String key = line.substring(0, separator).trim();
if (!key.equals("MemTotal") && !key.equals("MemAvailable")) {
continue;
}
String[] parts = line.substring(separator + 1).trim().split("\\s+");
if (parts.length > 0) {
// Linux meminfo values are reported in kB.
values.put(key, Long.parseLong(parts[0]) * 1024L);
}
}
} catch (IOException | NumberFormatException ignored) {
// Use OSHI values when host meminfo is unavailable.
}
return values;
}
private int getLogicalProcessorCount() {
Path statFile = HOST_PROC.resolve("stat");
try {
if (Files.isRegularFile(statFile)) {
int count = 0;
for (String line : Files.readAllLines(statFile)) {
if (line.matches("^cpu\\d+\\s+.*")) {
count++;
}
}
if (count > 0) {
return count;
}
}
} catch (IOException ignored) {
// Fall back to OSHI.
}
return _processor.getLogicalProcessorCount();
}
private String readHostHostname() {
try {
Path file = HOST_ROOT.resolve("etc/hostname");
if (Files.isRegularFile(file)) {
String hostname = Files.readString(file).trim();
if (!hostname.isBlank()) {
return hostname;
}
}
} catch (IOException ignored) {
// Fall back to OSHI.
}
return _operatingSystem.getNetworkParams().getHostName();
}
private Map<String, String> readHostOsRelease() {
Map<String, String> values = new HashMap<>();
try {
Path file = HOST_ROOT.resolve("etc/os-release");
if (!Files.isRegularFile(file)) {
return values;
}
for (String line : Files.readAllLines(file)) {
int separator = line.indexOf('=');
if (separator <= 0 || line.startsWith("#")) {
continue;
}
String key = line.substring(0, separator).trim();
String value = line.substring(separator + 1).trim();
if (value.length() >= 2
&& ((value.startsWith("\"") && value.endsWith("\""))
|| (value.startsWith("'") && value.endsWith("'")))) {
value = value.substring(1, value.length() - 1);
}
values.put(key, value);
}
} catch (IOException ignored) {
// Fall back to OSHI.
}
return values;
}
private String getHostOsFamily(Map<String, String> osRelease) {
String name = osRelease.get("NAME");
return name != null && !name.isBlank()
? name
: _operatingSystem.getFamily();
}
private String getHostOsVersion(Map<String, String> osRelease) {
String version = osRelease.get("VERSION");
if (version == null || version.isBlank()) {
version = osRelease.get("VERSION_ID");
}
return version != null && !version.isBlank()
? version
: _operatingSystem.getVersionInfo().getVersion();
}
private double round(double value) {
return Math.round(value * 10.0) / 10.0;
}
private record CpuTicks(long total, long idle) {
}
}
@@ -4,9 +4,14 @@ package com.htopweb.system;
import oshi.SystemInfo;
import oshi.software.os.OSProcess;
import oshi.software.os.OperatingSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -15,15 +20,24 @@ import java.util.Map;
@Service
public class SystemProcessesService {
private static final Logger log =
LoggerFactory.getLogger(SystemProcessesService.class);
private static final Path HOST_PROC = Path.of("/host/root/proc");
private static final Path PROC = Path.of("/proc");
private final OperatingSystem _operatingSystem;
private final int _logicalProcessorCount;
private final long _clockTicksPerSecond;
private final long _pageSizeBytes;
private final Map<Integer, OSProcess> _previousProcesses =
private final Map<Integer, ProcessSnapshot> _previousProcesses =
new HashMap<>();
private long _previousSampleNanos = System.nanoTime();
public SystemProcessesService() {
SystemInfo systemInfo = new SystemInfo();
_operatingSystem = systemInfo.getOperatingSystem();
_logicalProcessorCount = Math.max(
1,
@@ -31,55 +45,246 @@ public class SystemProcessesService {
.getProcessor()
.getLogicalProcessorCount()
);
_clockTicksPerSecond = readPositiveSystemValue(
"getconf CLK_TCK", 100L
);
_pageSizeBytes = readPositiveSystemValue(
"getconf PAGESIZE", 4096L
);
}
public synchronized List<ProcessInfoResponse> getProcesses() {
List<OSProcess> processes = _operatingSystem.getProcesses();
Map<Integer, OSProcess> currentProcesses = new HashMap<>();
Path procRoot = Files.isDirectory(HOST_PROC) ? HOST_PROC : PROC;
List<ProcessInfoResponse> result = processes.stream()
.map(process -> {
int pid = process.getProcessID();
currentProcesses.put(pid, process);
if (!Files.isReadable(procRoot)) {
log.warn("Process information is unavailable: {}", procRoot);
return getProcessesFromOshi();
}
OSProcess previous = _previousProcesses.get(pid);
double cpuUsage = 0.0;
long nowNanos = System.nanoTime();
double elapsedSeconds =
(nowNanos - _previousSampleNanos) / 1_000_000_000.0;
_previousSampleNanos = nowNanos;
if (pid != 0
&& previous != null
&& previous.getStartTime() == process.getStartTime()) {
long bootTimeSeconds = readBootTime(procRoot);
Map<Integer, ProcessSnapshot> currentProcesses = new HashMap<>();
List<ProcessInfoResponse> result = new ArrayList<>();
cpuUsage = process.getProcessCpuLoadBetweenTicks(previous)
* 100.0 / _logicalProcessorCount;
try (var entries = Files.list(procRoot)) {
entries.filter(path ->
path.getFileName().toString().matches("\\d+"))
.forEach(path -> {
try {
int pid = Integer.parseInt(
path.getFileName().toString()
);
cpuUsage = Math.max(0.0, Math.min(100.0, cpuUsage));
}
ProcessSnapshot current = readProcess(
path, pid, bootTimeSeconds
);
return new ProcessInfoResponse(
pid,
process.getName(),
process.getState().name(),
Math.round(cpuUsage * 10.0) / 10.0,
process.getResidentMemory(),
process.getVirtualSize(),
Math.max(
0L,
(System.currentTimeMillis()
- process.getStartTime()) / 1000
),
process.getThreadCount()
);
})
.sorted(
Comparator.comparingDouble(
ProcessInfoResponse::cpuUsagePercent
).reversed()
)
.toList();
if (current == null) {
return;
}
currentProcesses.put(pid, current);
ProcessSnapshot previous =
_previousProcesses.get(pid);
double cpuUsage = 0.0;
if (previous != null
&& previous.startTimeTicks()
== current.startTimeTicks()
&& elapsedSeconds > 0.0) {
long deltaTicks =
current.cpuTicks() - previous.cpuTicks();
if (deltaTicks >= 0) {
cpuUsage = deltaTicks
/ (double) _clockTicksPerSecond
/ elapsedSeconds
* 100.0
/ _logicalProcessorCount;
}
}
cpuUsage = Math.max(
0.0, Math.min(100.0, cpuUsage)
);
result.add(new ProcessInfoResponse(
pid,
current.name(),
current.state(),
Math.round(cpuUsage * 10.0) / 10.0,
current.residentMemoryBytes(),
current.virtualMemoryBytes(),
current.uptimeSeconds(),
current.threadCount()
));
} catch (IOException | NumberFormatException
| SecurityException ignored) {
// A process may exit or become inaccessible.
}
});
} catch (IOException e) {
log.warn("Cannot read process list from {}: {}",
procRoot, e.getMessage());
return getProcessesFromOshi();
}
_previousProcesses.clear();
_previousProcesses.putAll(currentProcesses);
result.sort(Comparator.comparingDouble(
ProcessInfoResponse::cpuUsagePercent
).reversed());
return result;
}
private ProcessSnapshot readProcess(
Path processPath,
int pid,
long bootTimeSeconds
) throws IOException {
String stat = Files.readString(processPath.resolve("stat")).trim();
int open = stat.indexOf('(');
int close = stat.lastIndexOf(')');
if (open < 0 || close <= open) {
return null;
}
String name = stat.substring(open + 1, close);
String[] fields = stat.substring(close + 2).trim().split("\\s+");
// Array begins at Linux stat field 3 (state).
if (fields.length < 22) {
return null;
}
String state = fields[0];
long userTicks = Long.parseLong(fields[11]); // field 14
long systemTicks = Long.parseLong(fields[12]); // field 15
long startTicks = Long.parseLong(fields[19]); // field 22
long virtualBytes = Long.parseLong(fields[20]); // field 23
long residentPages = Long.parseLong(fields[21]); // field 24
int threadCount = readThreadCount(processPath.resolve("status"));
long uptimeSeconds = 0L;
if (bootTimeSeconds > 0L) {
long processStartSeconds = bootTimeSeconds
+ startTicks / _clockTicksPerSecond;
uptimeSeconds = Math.max(
0L, System.currentTimeMillis() / 1000L
- processStartSeconds
);
}
return new ProcessSnapshot(
pid,
name,
state,
userTicks + systemTicks,
startTicks,
Math.max(0L, virtualBytes),
Math.max(0L, residentPages) * _pageSizeBytes,
uptimeSeconds,
threadCount
);
}
private int readThreadCount(Path statusFile) {
try {
for (String line : Files.readAllLines(statusFile)) {
if (line.startsWith("Threads:")) {
return Integer.parseInt(
line.substring("Threads:".length()).trim()
);
}
}
} catch (IOException | NumberFormatException ignored) {
// The process may exit while status is being read.
}
return 0;
}
private long readBootTime(Path procRoot) {
try {
for (String line : Files.readAllLines(procRoot.resolve("stat"))) {
if (line.startsWith("btime ")) {
return Long.parseLong(line.substring(6).trim());
}
}
} catch (IOException | NumberFormatException ignored) {
// Boot time unavailable.
}
return 0L;
}
private long readPositiveSystemValue(String command, long fallback) {
try {
Process process = new ProcessBuilder(
"/bin/sh", "-c", command
).redirectErrorStream(true).start();
String output = new String(
process.getInputStream().readAllBytes()
).trim();
if (process.waitFor() == 0) {
long value = Long.parseLong(output);
if (value > 0L) {
return value;
}
}
} catch (Exception e) {
log.debug("Cannot read system value for {}: {}", command,
e.getMessage());
}
return fallback;
}
private List<ProcessInfoResponse> getProcessesFromOshi() {
return _operatingSystem.getProcesses().stream()
.map(process -> new ProcessInfoResponse(
process.getProcessID(),
process.getName(),
process.getState().name(),
0.0,
process.getResidentMemory(),
process.getVirtualSize(),
Math.max(0L, (System.currentTimeMillis()
- process.getStartTime()) / 1000L),
process.getThreadCount()
))
.sorted(Comparator.comparingLong(
ProcessInfoResponse::residentMemoryBytes
).reversed())
.toList();
}
private record ProcessSnapshot(
int pid,
String name,
String state,
long cpuTicks,
long startTimeTicks,
long virtualMemoryBytes,
long residentMemoryBytes,
long uptimeSeconds,
int threadCount
) {
}
}
@@ -1,54 +1,131 @@
package com.htopweb.system.linux;
import oshi.SystemInfo;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@Service
public class LinuxDiskService {
private final OperatingSystem _operatingSystem;
private static final Logger log =
LoggerFactory.getLogger(LinuxDiskService.class);
public LinuxDiskService() {
SystemInfo systemInfo = new SystemInfo();
_operatingSystem = systemInfo.getOperatingSystem();
}
private static final Path HOST_ROOT = Path.of("/host/root");
private static final Path HOST_MOUNTS =
HOST_ROOT.resolve("proc/1/mounts");
private static final Set<String> EXCLUDED_TYPES = Set.of(
"proc", "sysfs", "devtmpfs", "devpts", "tmpfs",
"cgroup", "cgroup2", "overlay", "squashfs",
"tracefs", "securityfs", "debugfs", "pstore",
"configfs", "fusectl", "mqueue", "hugetlbfs",
"rpc_pipefs", "autofs"
);
public List<LinuxDiskResponse> getDisks() {
return _operatingSystem.getFileSystem()
.getFileStores()
.stream()
.filter(store -> store.getMount().startsWith("/"))
.filter(store -> store.getTotalSpace() > 0)
.map(this::toResponse)
.sorted(Comparator.comparing(LinuxDiskResponse::mountPoint))
.toList();
if (!Files.isReadable(HOST_MOUNTS)) {
log.error("Host mounts file is not readable: {}", HOST_MOUNTS);
return List.of();
}
try {
List<LinuxDiskResponse> disks = Files.readAllLines(HOST_MOUNTS)
.stream()
.map(line -> line.trim().split("\\s+"))
.filter(fields -> fields.length >= 3)
.filter(fields -> fields[0].startsWith("/dev/"))
.filter(fields -> !EXCLUDED_TYPES.contains(fields[2]))
.map(this::toHostResponse)
.filter(Objects::nonNull)
.collect(java.util.stream.Collectors.toMap(
LinuxDiskResponse::mountPoint,
disk -> disk,
(first, second) -> first
))
.values()
.stream()
.sorted(Comparator.comparing(
LinuxDiskResponse::mountPoint
))
.toList();
log.debug("Found {} host disks", disks.size());
return disks;
} catch (IOException e) {
log.error("Failed to read host mounts: {}", e.getMessage(), e);
return List.of();
}
}
private LinuxDiskResponse toResponse(OSFileStore store) {
long total = Math.max(0L, store.getTotalSpace());
long usable = Math.max(
0L,
Math.min(total, store.getUsableSpace())
);
long used = Math.max(0L, total - usable);
private LinuxDiskResponse toHostResponse(String[] fields) {
String device = unescapeMountField(fields[0]);
String hostMount = unescapeMountField(fields[1]);
String type = fields[2];
double usage = total > 0
? Math.round((double) used / total * 1000.0) / 10.0
: 0.0;
Path accessiblePath = HOST_ROOT.resolve(
hostMount.startsWith("/")
? hostMount.substring(1)
: hostMount
).normalize();
return new LinuxDiskResponse(
store.getName(),
store.getMount(),
store.getType(),
total,
usable,
used,
usage
);
if (!accessiblePath.startsWith(HOST_ROOT)
|| !Files.exists(accessiblePath)) {
log.warn("Host mount path is unavailable: {}", accessiblePath);
return null;
}
try {
FileStore store = Files.getFileStore(accessiblePath);
long total = Math.max(0L, store.getTotalSpace());
long usable = Math.max(
0L,
Math.min(total, store.getUsableSpace())
);
if (total == 0L) {
log.warn("Host disk has zero capacity: {}", hostMount);
return null;
}
long used = total - usable;
double usage = Math.round(
(double) used / total * 1000.0
) / 10.0;
return new LinuxDiskResponse(
device,
hostMount,
type,
total,
usable,
used,
usage
);
} catch (IOException | SecurityException e) {
log.warn(
"Cannot read capacity for host mount {}: {}",
hostMount,
e.getMessage()
);
return null;
}
}
}
private String unescapeMountField(String value) {
return value.replace("\\040", " ")
.replace("\\011", "\t")
.replace("\\012", "\n")
.replace("\\134", "\\");
}
}
@@ -1,14 +1,9 @@
package com.htopweb.system.linux;
import java.util.List;
public record LinuxNetworkResponse(
String name,
String displayName,
String macAddress,
List<String> ipv4Addresses,
List<String> ipv6Addresses,
long bytesReceived,
long bytesSent,
long packetsReceived,
@@ -1,86 +1,186 @@
package com.htopweb.system.linux;
import oshi.SystemInfo;
import oshi.hardware.NetworkIF;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream;
@Service
public class LinuxNetworkService {
private final SystemInfo _systemInfo;
private static final Logger log =
LoggerFactory.getLogger(LinuxNetworkService.class);
private static final Path HOST_ROOT = Path.of("/host/root");
private static final Path HOST_NET_DEV =
HOST_ROOT.resolve("proc/1/net/dev");
private static final Path HOST_NET_CLASS =
HOST_ROOT.resolve("sys/class/net");
private static final Set<String> EXCLUDED_INTERFACES =
Set.of("lo", "docker0");
private final ConcurrentMap<String, NetworkSnapshot> _previousSnapshots =
new ConcurrentHashMap<>();
public LinuxNetworkService() {
_systemInfo = new SystemInfo();
}
public List<LinuxNetworkResponse> getInterfaces() {
return _systemInfo.getHardware()
.getNetworkIFs()
.stream()
.map(this::toResponse)
.toList();
if (!Files.isReadable(HOST_NET_DEV)) {
log.error("Host network counters unavailable: {}", HOST_NET_DEV);
return List.of();
}
try {
List<LinuxNetworkResponse> result = new ArrayList<>();
for (String line : Files.readAllLines(HOST_NET_DEV)) {
int separator = line.indexOf(':');
if (separator < 0) {
continue;
}
String name = line.substring(0, separator).trim();
if (name.isBlank() || !shouldIncludeInterface(name)) {
continue;
}
String[] values = line.substring(separator + 1)
.trim()
.split("\\s+");
if (values.length < 16) {
continue;
}
result.add(toResponse(
name,
parseCounter(values[0]),
parseCounter(values[1]),
parseCounter(values[2]),
parseCounter(values[8]),
parseCounter(values[9]),
parseCounter(values[10])
));
}
return result;
} catch (IOException | NumberFormatException e) {
log.error("Failed to read host network counters: {}",
e.getMessage(), e);
return List.of();
}
}
private LinuxNetworkResponse toResponse(NetworkIF networkIF) {
networkIF.updateAttributes();
private boolean shouldIncludeInterface(String name) {
if (EXCLUDED_INTERFACES.contains(name)
|| name.startsWith("veth")
|| name.startsWith("br-")
|| name.startsWith("docker")) {
return false;
}
String name = networkIF.getName();
long received = Math.max(0L, networkIF.getBytesRecv());
long sent = Math.max(0L, networkIF.getBytesSent());
Path interfacePath = HOST_NET_CLASS.resolve(name);
// Physical network interfaces normally expose a "device" symlink.
if (Files.exists(interfacePath.resolve("device"))) {
return true;
}
// Keep useful virtual interfaces such as WireGuard and tunnels.
return Files.exists(interfacePath.resolve("wireless"))
|| Files.exists(interfacePath.resolve("tun_flags"))
|| name.startsWith("wg")
|| name.startsWith("tun")
|| name.startsWith("tap")
|| name.startsWith("bond")
|| name.startsWith("br");
}
private LinuxNetworkResponse toResponse(
String name,
long received,
long packetsReceived,
long inboundErrors,
long sent,
long packetsSent,
long outboundErrors
) {
long now = System.nanoTime();
NetworkSnapshot current = new NetworkSnapshot(received, sent, now);
NetworkSnapshot previous = _previousSnapshots.put(name, current);
NetworkSnapshot current =
new NetworkSnapshot(received, sent, now);
NetworkSnapshot previous =
_previousSnapshots.put(name, current);
double receiveBytesPerSecond = 0.0;
double sendBytesPerSecond = 0.0;
if (previous != null && now > previous.timestampNanos()) {
double elapsedSeconds =
double elapsed =
(now - previous.timestampNanos()) / 1_000_000_000.0;
if (received >= previous.bytesReceived()) {
receiveBytesPerSecond =
(received - previous.bytesReceived()) / elapsedSeconds;
(received - previous.bytesReceived()) / elapsed;
}
if (sent >= previous.bytesSent()) {
sendBytesPerSecond =
(sent - previous.bytesSent()) / elapsedSeconds;
(sent - previous.bytesSent()) / elapsed;
}
}
double receiveMbps = receiveBytesPerSecond * 8.0 / 1_000_000.0;
double sendMbps = sendBytesPerSecond * 8.0 / 1_000_000.0;
Path interfacePath = HOST_NET_CLASS.resolve(name);
String alias = readFirstLine(interfacePath.resolve("ifalias"));
String displayName = alias.isBlank() ? name : alias;
return new LinuxNetworkResponse(
name,
networkIF.getDisplayName(),
networkIF.getMacaddr(),
Arrays.asList(networkIF.getIPv4addr()),
Arrays.asList(networkIF.getIPv6addr()),
displayName,
received,
sent,
Math.max(0L, networkIF.getPacketsRecv()),
Math.max(0L, networkIF.getPacketsSent()),
Math.max(0L, networkIF.getInErrors()),
Math.max(0L, networkIF.getOutErrors()),
packetsReceived,
packetsSent,
inboundErrors,
outboundErrors,
roundOne(receiveBytesPerSecond),
roundOne(sendBytesPerSecond),
roundOne(receiveMbps),
roundOne(sendMbps)
roundOne(receiveBytesPerSecond * 8.0 / 1_000_000.0),
roundOne(sendBytesPerSecond * 8.0 / 1_000_000.0)
);
}
private long parseCounter(String value) {
return Math.max(0L, Long.parseLong(value));
}
private String readFirstLine(Path path) {
if (!Files.isReadable(path)) {
return "";
}
try (Stream<String> lines = Files.lines(path)) {
return lines.findFirst().orElse("").trim();
} catch (IOException e) {
log.debug("Cannot read {}: {}", path, e.getMessage());
return "";
}
}
private double roundOne(double value) {
return Math.round(value * 10.0) / 10.0;
}