Server monitoring is critical for maintaining system health, optimizing performance, and troubleshooting issues. While tools like Prometheus, Grafana, or Nagios offer robust solutions, there are scenarios where a lightweight, custom Java-based monitor is preferred—e.g., for embedded systems, low-resource environments, or integrating monitoring directly into a Java application.

In this blog, we’ll build a simple yet powerful server monitoring tool using Java to track CPU usagedisk metrics, and network IO on a Linux (Fedora) system. We’ll leverage the OSHI (Operating System and Hardware Information) library, a cross-platform API that abstracts low-level system details, making it easy to fetch hardware and OS metrics without writing OS-specific code.

By the end, you’ll have a Java application that periodically collects and displays key system metrics, with code you can extend for alerts, logging, or integration with dashboards.

Discover more

Linux kernel

File system

scripting

Scripting

Linux Kernel

Linux

Bash

Kernel

open-source

Compilers

Discover more

shell

Kernel

Scripting language

File System

Linux

Linux Kernel

open-source

Bash

Open source

kernel

Table of Contents#

  1. Prerequisites
  2. Setting Up the Project
  3. Monitoring CPU Usage
    • 3.1 CPU Cores and Load Average
    • 3.2 Per-Core CPU Usage
  4. Monitoring Disk Usage
    • 4.1 Disk Space (Total/Used/Free)
    • 4.2 Disk IO (Read/Write Rates)
  5. Monitoring Network IO
    • 5.1 Network Interfaces and Throughput
    • 5.2 Calculating Network Rates
  6. Putting It All Together: A Simple Monitor App
  7. Troubleshooting
  8. Conclusion & Enhancements
  9. References

Discover more

shell

Scripting

Linux kernel

Shell

file system

Open source

Kernel

open-source

Scripting language

Bash

Prerequisites#

Before starting, ensure you have the following tools installed on your Fedora system:

  • Java Development Kit (JDK): Version 11 or higher. Install via:
    sudo dnf install java-17-openjdk-devel  # For JDK 17 (LTS)  
  • Maven: For project management and dependencies. Install via:
    sudo dnf install maven  
  • Basic Linux Knowledge: Familiarity with concepts like CPU ticks, disk partitions, and network interfaces will help.

Setting Up the Project#

We’ll use Maven to create a new Java project and include OSHI (Operating System and Hardware Information) as a dependency. OSHI simplifies accessing system metrics by abstracting OS-specific APIs (e.g., /proc on Linux, WMI on Windows).

Step 1: Create a Maven Project#

Run the following command to generate a basic Maven project:

mvn archetype:generate -DgroupId=com.servermonitor -DartifactId=system-monitor -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false  

This creates a system-monitor directory with a standard Maven structure.

Step 2: Add OSHI Dependency#

Open pom.xml and add OSHI to the <dependencies> section. As of 2024, the latest stable version is 6.4.0:

<dependencies>      <!-- OSHI: System metrics library -->      <dependency>          <groupId>com.github.oshi</groupId>          <artifactId>oshi-core</artifactId>          <version>6.4.0</version>      </dependency>      <!-- SLF4J Simple (for logging; optional but helpful) -->      <dependency>          <groupId>org.slf4j</groupId>          <artifactId>slf4j-simple</artifactId>          <version>2.0.9</version>      </dependency>  </dependencies>  

OSHI requires SLF4J for logging, so we include slf4j-simple for simplicity.

Monitoring CPU Usage#

CPU metrics help identify bottlenecks (e.g., high usage, uneven core distribution). We’ll track:

  • Total CPU usage percentage
  • CPU core count (physical/logical)
  • System load average (1/5/15-minute)
  • Per-core CPU usage

3.1 CPU Cores and Load Average#

OSHI’s SystemInfo class provides access to hardware and OS details. Use CentralProcessor to fetch CPU data:

import oshi.SystemInfo;  import oshi.hardware.CentralProcessor;   public class CpuMonitor {      private final CentralProcessor cpu;       public CpuMonitor() {          SystemInfo systemInfo = new SystemInfo();          this.cpu = systemInfo.getHardware().getProcessor();      }       // Get CPU core info      public void printCpuCores() {          System.out.println("CPU Cores:");          System.out.println("  Physical: " + cpu.getPhysicalProcessorCount());          System.out.println("  Logical: " + cpu.getLogicalProcessorCount());      }       // Get system load average (1/5/15 min)      public void printLoadAverage() {          double[] loadAvg = cpu.getSystemLoadAverage(3); // 3 samples: 1,5,15 min          System.out.println("\nLoad Average:");          System.out.printf("  1 min: %.2f | 5 min: %.2f | 15 min: %.2f%n",                  loadAvg[0], loadAvg[1], loadAvg[2]);      }  }  

3.2 Total and Per-Core CPU Usage#

CPU usage is calculated by comparing "ticks" (time spent in user, system, idle, etc.) over a period. OSHI provides getSystemCpuLoad() for total usage, but we’ll also calculate per-core usage manually:

// Inside CpuMonitor class  private long[] prevTicks;   // Initialize previous ticks for usage calculation  public void initCpuTicks() {      prevTicks = cpu.getSystemCpuLoadTicks();  }   // Calculate total CPU usage (%) over a interval  public double getTotalCpuUsage() {      long[] currTicks = cpu.getSystemCpuLoadTicks();      double usage = cpu.getSystemCpuLoadBetweenTicks(prevTicks) * 100;      prevTicks = currTicks; // Update for next calculation      return usage;  }   // Print per-core CPU usage (%)  public void printPerCoreUsage() {      System.out.println("\nPer-Core Usage (%):");      double[] coreLoads = cpu.getProcessorCpuLoadBetweenTicks(); // Uses prev ticks      for (int i = 0; i < coreLoads.length; i++) {          System.out.printf("  Core %d: %.1f%%%n", i, coreLoads[i] * 100);      }  }  

How it works:

  • getSystemCpuLoadTicks() returns an array of ticks for user, nice, system, idle, etc.
  • getSystemCpuLoadBetweenTicks(prevTicks) computes usage as (total - idle) / total over the interval.

Monitoring Disk Usage#

Disk metrics include space usage (total/used/free) and IO performance (read/write rates, operations per second).

4.1 Disk Space (Total/Used/Free)#

OSHI’s FileSystem class lists mounted filesystems and their usage. We’ll focus on local filesystems (e.g., //home):

import oshi.hardware.HardwareAbstractionLayer;  import oshi.software.os.FileSystem;  import oshi.software.os.OSFileStore;   public class DiskMonitor {      private final FileSystem fileSystem;       public DiskMonitor() {          SystemInfo systemInfo = new SystemInfo();          this.fileSystem = systemInfo.getOperatingSystem().getFileSystem();      }       // Print disk space for local filesystems      public void printDiskSpace() {          System.out.println("\nDisk Space Usage:");          for (OSFileStore store : fileSystem.getFileStores()) {              // Skip non-local or temporary filesystems              if (store.getType().equals("tmpfs") || store.getMount().startsWith("//")) {                  continue;              }              long total = store.getTotalSpace();              long used = store.getTotalSpace() - store.getFreeSpace();              long free = store.getFreeSpace();               System.out.printf("  Mount: %s%n", store.getMount());              System.out.printf("  Total: %.2f GB | Used: %.2f GB (%.1f%%) | Free: %.2f GB%n",                      total / (1e9), used / (1e9), (used * 100.0) / total, free / (1e9));          }      }  }  

4.2 Disk IO (Read/Write Rates)#

To track disk IO, use HWDiskStore to get cumulative read/write bytes and operations. Calculate rates by sampling over time:

import oshi.hardware.HWDiskStore;   public class DiskMonitor {      // ... (previous code)       private HWDiskStore[] prevDiskStats;       // Initialize disk stats for IO calculation      public void initDiskStats() {          HardwareAbstractionLayer hal = new SystemInfo().getHardware();          prevDiskStats = hal.getDiskStores();      }       // Print disk IO rates (per second)      public void printDiskIoRates(int intervalSec) {          HardwareAbstractionLayer hal = new SystemInfo().getHardware();          HWDiskStore[] currDiskStats = hal.getDiskStores();           System.out.println("\nDisk IO Rates:");          for (int i = 0; i < currDiskStats.length; i++) {              HWDiskStore prev = prevDiskStats[i];              HWDiskStore curr = currDiskStats[i];               long readBytes = curr.getReadBytes() - prev.getReadBytes();              long writeBytes = curr.getWriteBytes() - prev.getWriteBytes();              long readOps = curr.getReads() - prev.getReads();              long writeOps = curr.getWrites() - prev.getWrites();               System.out.printf("  Disk: %s%n", curr.getName());              System.out.printf("  Read: %.2f MB/s | Write: %.2f MB/s%n",                      readBytes / (1e6 * intervalSec), writeBytes / (1e6 * intervalSec));              System.out.printf("  Read Ops: %d/s | Write Ops: %d/s%n",                      readOps / intervalSec, writeOps / intervalSec);          }          prevDiskStats = currDiskStats; // Update for next interval      }  }  

Monitoring Network IO#

Network metrics include throughput (bytes sent/received per second) and interface status (e.g., up/down).

5.1 Network Interfaces and Throughput#

OSHI’s NetworkIF class provides network interface details. We’ll track non-loopback interfaces (e.g., eth0wlan0):

import oshi.hardware.NetworkIF;  import java.util.List;   public class NetworkMonitor {      private List<NetworkIF> prevNetStats;       public NetworkMonitor() {          SystemInfo systemInfo = new SystemInfo();          this.prevNetStats = systemInfo.getHardware().getNetworkIFs();          // Initialize stats (first read)          for (NetworkIF iface : prevNetStats) {              iface.updateAttributes();          }      }       // Print network throughput (bytes sent/received per second)      public void printNetworkRates(int intervalSec) {          SystemInfo systemInfo = new SystemInfo();          List<NetworkIF> currNetStats = systemInfo.getHardware().getNetworkIFs();           System.out.println("\nNetwork Throughput:");          for (NetworkIF curr : currNetStats) {              // Skip loopback and down interfaces              if (curr.isLoopback() || !curr.isUp()) {                  continue;              }               // Find previous stats for this interface              NetworkIF prev = prevNetStats.stream()                      .filter(p -> p.getName().equals(curr.getName()))                      .findFirst().orElse(null);               if (prev == null) continue;               long recvBytes = curr.getBytesRecv() - prev.getBytesRecv();              long sentBytes = curr.getBytesSent() - prev.getBytesSent();               System.out.printf("  Interface: %s%n", curr.getName());              System.out.printf("  Received: %.2f MB/s | Sent: %.2f MB/s%n",                      recvBytes / (1e6 * intervalSec), sentBytes / (1e6 * intervalSec));          }          prevNetStats = currNetStats; // Update for next interval      }  }  

Putting It All Together: A Simple Monitor App#

Combine the monitors into a single application that runs in a loop, collecting metrics every 5 seconds:

public class SystemMonitorApp {      private static final int INTERVAL_SEC = 5; // Collect metrics every 5s       public static void main(String[] args) throws InterruptedException {          CpuMonitor cpuMonitor = new CpuMonitor();          DiskMonitor diskMonitor = new DiskMonitor();          NetworkMonitor networkMonitor = new NetworkMonitor();           // Initialize CPU and disk ticks for usage calculation          cpuMonitor.initCpuTicks();          diskMonitor.initDiskStats();           while (true) {              System.out.println("\n=== Metrics Collected at " + java.time.LocalTime.now() + " ===");               // CPU Metrics              cpuMonitor.printCpuCores();              cpuMonitor.printLoadAverage();              System.out.printf("Total CPU Usage: %.1f%%%n", cpuMonitor.getTotalCpuUsage());              cpuMonitor.printPerCoreUsage();               // Disk Metrics              diskMonitor.printDiskSpace();              diskMonitor.printDiskIoRates(INTERVAL_SEC);               // Network Metrics              networkMonitor.printNetworkRates(INTERVAL_SEC);               Thread.sleep(INTERVAL_SEC * 1000); // Wait for next interval          }      }  }  

Run the Application#

  1. Navigate to the project directory:

    cd system-monitor  
  2. Run with Maven:

    mvn exec:java -Dexec.mainClass="com.servermonitor.SystemMonitorApp"  

Sample Output:

=== Metrics Collected at 14:30:00 ===  
CPU Cores:  
  Physical: 4 | Logical: 8  
Load Average:  
  1 min: 0.85 | 5 min: 0.72 | 15 min: 0.68  
Total CPU Usage: 23.5%  
Per-Core Usage (%):  
  Core 0: 18.2% | Core 1: 25.1% | ...  

Disk Space Usage:  
  Mount: /  
  Total: 465.76 GB | Used: 120.45 GB (25.9%) | Free: 345.31 GB  

Disk IO Rates:  
  Disk: sda  
  Read: 0.52 MB/s | Write: 2.10 MB/s  
  Read Ops: 12/s | Write Ops: 35/s  

Network Throughput:  
  Interface: eth0  
  Received: 1.25 MB/s | Sent: 0.82 MB/s  

Troubleshooting#

  • OSHI Not Detecting Hardware: Ensure you’re using the latest OSHI version. Some metrics require read access to /proc (default on Linux).
  • High CPU Usage in Monitor: The monitor itself uses minimal resources, but reduce the interval (e.g., 10 seconds) if needed.
  • Missing Dependencies: Run mvn clean install to resolve missing libraries.

Conclusion & Enhancements#

This blog covered building a Java monitor for CPU, disk, and network metrics on Fedora Linux using OSHI. You can extend it by:

  • Sending metrics to a database (e.g., InfluxDB) or dashboard (Grafana).
  • Adding alerts (e.g., email/Slack when CPU > 90%).
  • Supporting Windows/macOS (OSHI is cross-platform).
  • Adding memory usage (use oshi.hardware.GlobalMemory).

Discover more

Scripting language

Kernel

Bash

compiler

Compiler

Linux kernel

shell

Scripting

kernel

scripting

References#

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐