简介
oshi是Java的免费基于JNA的(本机)操作系统和硬件信息库。它不需要安装任何其他本机库,并且旨在提供一种跨平台的实现来检索系统信息,例如操作系统版本,进程,内存和CPU使用率,磁盘和分区,设备,传感器等。本文主要介绍 SpringBoot 集成 oshi 实现对服务器及应用的监控。
特性
使用oshi我们可以对应用程序进行监控,可以对应用程序所在的服务器资源进行监控,可以监控到很多的指标,如下:
1 计算机系统和固件,底板
2 操作系统和版本/内部版本
3 物理(核心)和逻辑(超线程)CPU,处理器组,NUMA节点
4 系统和每个处理器的负载百分比和滴答计数器
5 CPU正常运行时间,进程和线程
6 进程正常运行时间,CPU,内存使用率,用户/组,命令行
7 已使用/可用的物理和虚拟内存
8 挂载的文件系统(类型,可用空间和总空间)
9 磁盘驱动器(型号,序列号,大小)和分区
10 网络接口(IP,带宽输入/输出)
11 电池状态(电量百分比,剩余时间,电量使用情况统计信息)
12 连接的显示器(带有EDID信息)
13 USB设备
14 传感器(温度,风扇速度,电压)
运行
使用 Java 程序集成 oshi 后,对指标数据进行处理后应用图表如图:
竞品分析
目前 Java 获取系统CPU和内存使用率大致有三种方法,准确率与适用性分析如下:
三种主流方法具体实现方式对比如下:
三种方法测试的比较:
依赖
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>3.9.1</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.3.1</version>
</dependency>
我们在引入了以上配置文件后,先不要着急写代码,先检查一下项目中引入进来的jar包;打开IDEA的External Libraries功能(在项目Project的最下方),检查一下已经引入的jar包,需要保证的是引入的net.java.dev.jna:jna 包引入的不等于4.2.2,否则在调用方法进行查询的时候就会报错
java.lang.NoSuchMethodError:
com.sun.jna.platform.win32.WinNT$FILE_NOTIFY_INFORMATION.createFieldsOrder
([Ljava/lang/String;)Ljava/util/List;
主要工具类:
package com.test.modules.monitor.server;
import com.google.common.collect.Lists;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class ServerOS {
public static Map getInfo(){
Map map = new HashMap();
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
CentralProcessor processor = hal.getProcessor(); //获取cpu信息
map.put("cpu", setCpuInfo(processor));
GlobalMemory memory = hal.getMemory(); //获取内存信息
map.put("mem", setMemInfo(memory));
map.put("sys", setSysInfo()); //服务器信息
map.put("jvm", setJvmInfo()); //jvm信息
OperatingSystem op = si.getOperatingSystem();
map.put("file",setSysFiles(op)); //磁盘信息
map.put("ip", IpUtils.getHostIp());
map.put("hostname", IpUtils.getHostName());
return map;
}
/**
* cpu信息
* @param processor
*/
private static Map setCpuInfo(CentralProcessor processor) { // CPU信息
long[] prevTicks = processor.getSystemCpuLoadTicks();
Util.sleep(1000);
long[] ticks = processor.getSystemCpuLoadTicks();
double nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
double irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
double softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
double steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
/**
* CPU系统使用率
*/
double sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
/**
* CPU用户使用率
*/
double used = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
/**
* CPU当前等待率
*/
double iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
/**
* CPU当前空闲率
*/
double free = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
/**
* CPU总的使用率
*/
double total = used + nice + sys + free + iowait + irq + softirq + steal;
/**
* 核心数
*/
int cpuNum = processor.getLogicalProcessorCount();
/**
* CPU当前等待率
*/
Map cpu = new HashMap<String,String>();
cpu.put("cpucard", processor);//cpu信息
cpu.put("num", processor.getLogicalProcessorCount());//cpu核心数
cpu.put("used", MathUtils.round(MathUtils.mul((sys+used) / total, 100), 2));//cpu 用户使用率;
cpu.put("free", MathUtils.round(MathUtils.mul(free / total, 100), 2));//cpu 空闲率
return cpu;
}
/**
* 内存信息
*/
private static Map setMemInfo(GlobalMemory memory) {
/**
* 内存总量
*/
double total = +memory.getTotal();
/**
* 已用内存
*/
double used = memory.getTotal() - memory.getAvailable();
/**
* 剩余内存
*/
double free = memory.getAvailable();
Map mem = new HashMap();
mem.put("total", div(total, (1024 * 1024 * 1024), 2));// 内存总大小
mem.put("usedMem", div(used, (1024 * 1024 * 1024), 2));// 已使用内存大小
mem.put("free", MathUtils.round(MathUtils.mul(free / total, 100), 2));// 剩余内存大小
mem.put("used", MathUtils.round(MathUtils.mul(used / total, 100), 2));
return mem;
}
/**
* 服务器信息
*/
private static Properties setSysInfo() {
Properties props = System.getProperties();
return props;
}
/**
* Java虚拟机
*/
private static Map setJvmInfo() {
Properties props = System.getProperties();
Map jvm = new HashMap();
jvm.put("total", div(Runtime.getRuntime().totalMemory(),1024*1024,2));
jvm.put("maxTotal",div(Runtime.getRuntime().maxMemory(),1024*1024,2));
jvm.put("usedMem", div((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()),1024*1024,2));
jvm.put("used", MathUtils.round(MathUtils.mul((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())*1.0/ Runtime.getRuntime().totalMemory(), 100), 2));
return jvm;
}
/**
* 设置磁盘信息
*/
private static List setSysFiles(OperatingSystem os) {
FileSystem fileSystem = os.getFileSystem();
OSFileStore[] fsArray = fileSystem.getFileStores();
List list = Lists.newArrayList();
for (OSFileStore fs : fsArray) {
long free = fs.getUsableSpace();
long total = fs.getTotalSpace();
long used = total - free;
Map map = new HashMap();
map.put("path", fs.getMount());
map.put("type", fs.getType());
map.put("name", fs.getName());
map.put("total", convertFileSize(total));
map.put("free", convertFileSize(free));
map.put("used", convertFileSize(used));
map.put("rate", div(used, total, 4)*100);
list.add(map);
}
return list;
}
/**
* 字节转换
*
* @param size 字节大小
* @return 转换后值
*/
public static String convertFileSize(long size) {
long kb = 1024;
long mb = kb * 1024;
long gb = mb * 1024;
if (size >= gb) {
return String.format("%.1f GB", (float) size / gb);
} else if (size >= mb) {
float f = (float) size / mb;
return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
} else if (size >= kb) {
float f = (float) size / kb;
return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
} else {
return String.format("%d B", size);
}
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale)
{
return ((double)((int)( v1/v2 * Math.pow(10 , scale))))/Math.pow(10 , scale) ;
}
public static void main(String[] args){
System.out.println(div(19, 7, 4));
}
}
辅助工具类
package com.test.modules.monitor.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IpUtils
{
public static String getHostIp()
{
try
{
return InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e)
{
}
return "127.0.0.1";
}
public static String getHostName()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e)
{
}
return "未知";
}
public static String getIpAddr(HttpServletRequest request)
{
if (request == null)
{
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
public static boolean internalIp(String ip)
{
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
private static boolean internalIp(byte[] addr)
{
if (addr == null || addr.length < 2)
{
return true;
}
final byte b0 = addr[0];
final byte b1 = addr[1];
final byte SECTION_1 = 0x0A;
final byte SECTION_2 = (byte) 0xAC;
final byte SECTION_3 = (byte) 0x10;
final byte SECTION_4 = (byte) 0x1F;
// 192.168.x.x/16
final byte SECTION_5 = (byte) 0xC0;
final byte SECTION_6 = (byte) 0xA8;
switch (b0)
{
case SECTION_1:
return true;
case SECTION_2:
if (b1 >= SECTION_3 && b1 <= SECTION_4)
{
return true;
}
case SECTION_5:
switch (b1)
{
case SECTION_6:
return true;
}
default:
return false;
}
}
/**
* 将IPv4地址转换成字节
*/
public static byte[] textToNumericFormatV4(String text)
{
if (text.length() == 0)
{
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\\.", -1);
try
{
long l;
int i;
switch (elements.length)
{
case 1:
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L))
return null;
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 2:
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L))
return null;
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L))
return null;
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 3:
for (i = 0; i < 2; ++i)
{
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L))
return null;
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L))
return null;
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 4:
for (i = 0; i < 4; ++i)
{
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L))
return null;
bytes[i] = (byte) (int) (l & 0xFF);
}
break;
default:
return null;
}
}
catch (NumberFormatException e)
{
return null;
}
return bytes;
}
}
精确的浮点运算工具类:
package com.test.modules.monitor.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class MathUtils
{
/** 默认除法运算精度 */
private static final int DEF_DIV_SCALE = 10;
private MathUtils()
{
}
/**
* 提供精确的加法运算。
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2)
{
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale)
{
if (scale < 0)
{
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
if (b1.compareTo(BigDecimal.ZERO) == 0)
{
return BigDecimal.ZERO.doubleValue();
}
return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale)
{
if (scale < 0)
{
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
}
OSHI 的示例输出
OSHI 直接通过 Java 方法为其每个接口提供输出。通过定期轮询动态信息(例如,每秒),用户可以计算和跟踪变化。
系统信息测试
您可以通过克隆项目并使用Maven构建它来查看更多示例并运行SystemInfoTest 并查看系统的完整输出。
oshi-演示
此外,该oshi-demo模块包括一个OshiGui类,它实现了一个基本的 Swing GUI,为在 UI、监控或警报应用程序中使用 OSHI 的潜在可视化提供建议,如下所示。有关基于此方法的更高级 GUI,请参阅MooInfo 项目。
输出
有关操作系统和计算机系统硬件的一般信息:
通过测量时间间隔之间的滴答声(user、nice、system、idle、iowait 和 irq),可以计算出使用百分比。还提供了每个处理器的信息。
进程信息包括每个进程的 CPU 和内存可用。
内存和交换文件信息可用。