Major improvements: multi-stage Docker, security hardening, host metrics, tests
- Multi-stage Docker build: Dockerfile compiles frontend (Node.js) and backend (Gradle) inside Docker; no local Java/Node.js required - Security hardening: replaced full host root mount with specific directory mounts (/proc, /sys, /etc, /var, /home, /opt, /tmp, /srv, /usr) - Host metrics consistency: LinuxMemoryService and CpuDetailsService now read from /host/proc instead of container OSHI - Concurrency: replaced synchronized with AtomicReference in 3 services - Global error handling: added @RestControllerAdvice for JSON error responses - Unit tests: added 32 tests for /proc parsers, CPU usage, process uptime - Docker security: non-root user, read-only FS, no-new-privileges, healthcheck - Debug logging: added log.debug() in silent catch blocks - Documentation: updated README, INSTALLATION, DEVELOPMENT, CONFIGURATION, ARCHITECTURE, CHANGELOG Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -16,18 +16,19 @@ import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Service
|
||||
public class SystemMetricsService {
|
||||
|
||||
private static final Path HOST_ROOT = Path.of("/host/root");
|
||||
private static final Path HOST_PROC = HOST_ROOT.resolve("proc");
|
||||
private static final Path HOST_PROC = Path.of("/host/proc");
|
||||
private static final Path HOST_ETC = Path.of("/host/etc");
|
||||
|
||||
private final OperatingSystem _operatingSystem;
|
||||
private final CentralProcessor _processor;
|
||||
private final GlobalMemory _memory;
|
||||
|
||||
private CpuTicks _previousHostCpuTicks;
|
||||
private final AtomicReference<CpuTicks> _previousHostCpuTicks = new AtomicReference<>();
|
||||
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(SystemMetricsService.class);
|
||||
@@ -40,16 +41,17 @@ public class SystemMetricsService {
|
||||
_processor = hardware.getProcessor();
|
||||
_memory = hardware.getMemory();
|
||||
|
||||
_previousHostCpuTicks = readHostCpuTicks();
|
||||
_previousHostCpuTicks.set(readHostCpuTicks());
|
||||
}
|
||||
|
||||
public synchronized SystemMetricsResponse getMetrics() {
|
||||
public SystemMetricsResponse getMetrics() {
|
||||
CpuTicks currentTicks = readHostCpuTicks();
|
||||
double cpuUsage = 0.0;
|
||||
|
||||
if (currentTicks != null && _previousHostCpuTicks != null) {
|
||||
long totalDelta = currentTicks.total() - _previousHostCpuTicks.total();
|
||||
long idleDelta = currentTicks.idle() - _previousHostCpuTicks.idle();
|
||||
CpuTicks previous = _previousHostCpuTicks.get();
|
||||
if (currentTicks != null && previous != null) {
|
||||
long totalDelta = currentTicks.total() - previous.total();
|
||||
long idleDelta = currentTicks.idle() - previous.idle();
|
||||
|
||||
if (totalDelta > 0 && idleDelta >= 0 && idleDelta <= totalDelta) {
|
||||
cpuUsage = (double) (totalDelta - idleDelta) / totalDelta;
|
||||
@@ -57,7 +59,7 @@ public class SystemMetricsService {
|
||||
}
|
||||
|
||||
if (currentTicks != null) {
|
||||
_previousHostCpuTicks = currentTicks;
|
||||
_previousHostCpuTicks.set(currentTicks);
|
||||
}
|
||||
|
||||
Map<String, Long> hostMemory = readHostMemory();
|
||||
@@ -165,8 +167,8 @@ public class SystemMetricsService {
|
||||
values.put(key, Long.parseLong(parts[0]) * 1024L);
|
||||
}
|
||||
}
|
||||
} catch (IOException | NumberFormatException ignored) {
|
||||
// Use OSHI values when host meminfo is unavailable.
|
||||
} catch (IOException | NumberFormatException e) {
|
||||
log.debug("Failed to read host memory info: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return values;
|
||||
@@ -189,8 +191,8 @@ public class SystemMetricsService {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
// Fall back to OSHI.
|
||||
} catch (IOException e) {
|
||||
log.debug("Failed to read host processor count: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return _processor.getLogicalProcessorCount();
|
||||
@@ -198,7 +200,7 @@ public class SystemMetricsService {
|
||||
|
||||
private String readHostHostname() {
|
||||
try {
|
||||
Path file = HOST_ROOT.resolve("etc/hostname");
|
||||
Path file = HOST_ETC.resolve("hostname");
|
||||
|
||||
if (Files.isRegularFile(file)) {
|
||||
String hostname = Files.readString(file).trim();
|
||||
@@ -207,8 +209,8 @@ public class SystemMetricsService {
|
||||
return hostname;
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
// Fall back to OSHI.
|
||||
} catch (IOException e) {
|
||||
log.debug("Failed to read host hostname: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return _operatingSystem.getNetworkParams().getHostName();
|
||||
@@ -218,7 +220,7 @@ public class SystemMetricsService {
|
||||
Map<String, String> values = new HashMap<>();
|
||||
|
||||
try {
|
||||
Path file = HOST_ROOT.resolve("etc/os-release");
|
||||
Path file = HOST_ETC.resolve("os-release");
|
||||
|
||||
if (!Files.isRegularFile(file)) {
|
||||
return values;
|
||||
@@ -242,8 +244,8 @@ public class SystemMetricsService {
|
||||
|
||||
values.put(key, value);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
// Fall back to OSHI.
|
||||
} catch (IOException e) {
|
||||
log.debug("Failed to read host os-release: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return values;
|
||||
|
||||
Reference in New Issue
Block a user