暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【监控】SpringBoot集成oshi实现对服务器及应用的监控

加耀 2020-06-17
1276
OSHI是Java的免费基于JNA的(本机)操作系统和硬件信息库。它不需要安装任何其他本机库,并且旨在提供一种跨平台的实现来检索系统信息,例如操作系统版本,进程,内存和CPU使用率,磁盘和分区,设备,传感器等。

使用OSHI我们可以对应用程序进行监控,可以对应用程序所在的服务器资源进行监控,可以监控到很多的指标,如下:

1、计算机系统和固件,底板

2、操作系统和版本/内部版本   3、物理(核心)和逻辑(超线程)CPU,处理器组,NUMA节点   4、系统和每个处理器的负载百分比和滴答计数器   5、CPU正常运行时间,进程和线程   6、进程正常运行时间,CPU,内存使用率,用户/组,命令行   7、已使用/可用的物理和虚拟内存   8、挂载的文件系统(类型,可用空间和总空间)   9、磁盘驱动器(型号,序列号,大小)和分区   10、网络接口(IP,带宽输入/输出)   11、电池状态(电量百分比,剩余时间,电量使用情况统计信息)   12、连接的显示器(带有EDID信息)   13、USB设备   14、传感器(温度,风扇速度,电压)


上面这些指标,很多都不是很懂,不过我们可以参考oshi的api进行相应的操作:http://oshi.github.io/oshi/apidocs/


我们使用java程序集成oshi后,对指标数据进行处理,可以得到一些好看的应用图表,如:


在实际项目中,对系统的监控我们更多的是采用第三方工具,比如Prometheus等,这个在下一章节中讲解;
我们在SpringBoot工程中,引入以下配置文件


<dependency>
   <groupId>
com.github.oshi</groupId>
   <artifactId>
oshi-core</artifactId>
   <version>
3.9.1</version>
</dependency>


<dependency>
   <groupId>
cn.hutool</groupId>
   <artifactId>
hutool-core</artifactId>
   <version>
4.6.1</version>
</dependency>
<dependency>
   <groupId>
net.java.dev.jna</groupId>
   <artifactId>
jna</artifactId>
   <version>
4.5.2</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;
复制


之前在集成的时候遇到了很多的问题,参考了网上很多的答案,同时也收获到了更多不同的异常日志;


最后才发现,只要把net.java.dev.jna:jna包的版本号控制住了,其他的就都好说了;在上述我引入的是net.java.dev.jna:jna包,另外,还有一个jar其实可以引入的,大家可以根据自己在引入包后实际产生的情况进行合理安排,实现项目中只有一个net.java.dev.jna:jna包并且这个包的版本号不是 4.2.2即可;
<dependency>
   <groupId>
net.java.dev.jna</groupId>
   <artifactId>
jna-platform</artifactId>
   <version>
4.5.2</version>
</dependency>
复制


jna-platform包中集成了jna;

当程序包的问题解决后,即可开始编写代码了;后续的代码跟网上的代码都是一样的,也都是从网上cv过来的;


Cpu实体类
import cn.hutool.core.util.NumberUtil;
import
lombok.Data;
import
java.io.Serializable;


@Data
public class Cpu implements Serializable {
   private static final long serialVersionUID = 1L;


   /**
    * 核心数
    */
   
private int cpuNum;


   /**
    * CPU总的使用率
    */
   
private double total;


   /**
    * CPU系统使用率
    */
   
private double sys;


   /**
    * CPU用户使用率
    */
   
private double used;


   /**
    * CPU当前等待率
    */
   
private double wait;


   /**
    * CPU当前空闲率
    */
   
private double free;


   public double getTotal() {
       return NumberUtil.round(NumberUtil.mul(total, 100), 2).doubleValue();
   
}

   public double getSys() {
       return NumberUtil.round(NumberUtil.mul(sys / total, 100), 2).doubleValue();
   
}

   public double getUsed() {
       return NumberUtil.round(NumberUtil.mul(used / total, 100), 2).doubleValue();
   
}

   public double getWait() {
       return NumberUtil.round(NumberUtil.mul(wait / total, 100), 2).doubleValue();
   
}

   public double getFree() {
       return NumberUtil.round(NumberUtil.mul(free / total, 100), 2).doubleValue();
   
}
}
复制


Jvm实体类
import cn.hutool.core.date.DateUnit;
import
cn.hutool.core.date.DateUtil;
import
cn.hutool.core.util.NumberUtil;
import
lombok.Data;
import
java.io.Serializable;
import
java.lang.management.ManagementFactory;
import
java.util.Date;


@Data
public class Jvm implements Serializable {

   private static final long serialVersionUID = 1L;


   /**
    * 当前JVM占用的内存总数(M)
    */
   
private double total;


   /**
    * JVM最大可用内存总数(M)
    */
   
private double max;


   /**
    * JVM空闲内存(M)
    */
   
private double free;


   /**
    * JDK版本
    */
   
private String version;


   /**
    * JDK路径
    */
   
private String home;


   public double getTotal() {
       return NumberUtil.div(total, (1024 * 1024), 2);
   
}

   public double getMax() {
       return NumberUtil.div(max, (1024 * 1024), 2);
   
}

   public double getFree() {
       return NumberUtil.div(free, (1024 * 1024), 2);
   
}

   public double getUsed() {
       return NumberUtil.div(total - free, (1024 * 1024), 2);
   
}

   public String getVersion() {
       return version;
   
}

   public String getHome() {
       return home;
   
}

   public double getUsage() {
       return NumberUtil.mul(NumberUtil.div(total - free, total, 4), 100);
   
}
   /**
    * 获取JDK名称
    */
   
public String getName() {
       return ManagementFactory.getRuntimeMXBean().getVmName();
   
}

   /**
    * JDK启动时间
    */
   
public String getStartTime() {
       long time = ManagementFactory.getRuntimeMXBean().getStartTime();
       
Date date = new Date(time);
       return
DateUtil.formatDateTime(date);
   
}

   /**
    * JDK运行时间
    */
   
public String getRunTime() {
       long time = ManagementFactory.getRuntimeMXBean().getStartTime();
       
Date date = new Date(time);


       //运行多少分钟
       
long runMS = DateUtil.between(date, new Date(), DateUnit.MS);


       long nd = 1000 * 24 * 60 * 60;
       long
nh = 1000 * 60 * 60;
       long
nm = 1000 * 60;


       long day = runMS / nd;
       long
hour = runMS % nd / nh;
       long
min = runMS % nd % nh / nm;
       return
day + "天" + hour + "小时" + min + "分钟";
   
}
}
复制


    Mem内存实体类
import cn.hutool.core.util.NumberUtil;
import
lombok.Data;
import
java.io.Serializable;


@Data
public class Mem implements Serializable {

   private static final long serialVersionUID = 1L;


   /**
    * 内存总量
    */
   
private double total;


   /**
    * 已用内存
    */
   
private double used;


   /**
    * 剩余内存
    */
   
private double free;


   public double getTotal() {
       return NumberUtil.div(total, (1024 * 1024 * 1024), 2);
   
}

   public double getUsed() {
       return NumberUtil.div(used, (1024 * 1024 * 1024), 2);
   
}

   public double getFree() {
       return NumberUtil.div(free, (1024 * 1024 * 1024), 2);
   
}

   public double getUsage() {
       return NumberUtil.mul(NumberUtil.div(used, total, 4), 100);
   
}
}
复制


    Sys系统实体类
import lombok.Data;
import
java.io.Serializable;


@Data
public class Sys implements Serializable {

   private static final long serialVersionUID = 1L;


   /**
    * 服务器名称
    */
   
private String computerName;


   /**
    * 服务器Ip
    */
   
private String computerIp;


   /**
    * 项目路径
    */
   
private String userDir;


   /**
    * 操作系统
    */
   
private String osName;


   /**
    * 系统架构
    */
   
private String osArch;
}
复制


    SysFile系统文件实体类
import lombok.Data;
import
java.io.Serializable;


@Data
public class SysFile implements Serializable {

   private static final long serialVersionUID = 1L;


   /**
    * 盘符路径
    */
   
private String dirName;


   /**
    * 盘符类型
    */
   
private String sysTypeName;


   /**
    * 文件类型
    */
   
private String typeName;


   /**
    * 总大小
    */
   
private String total;


   /**
    * 剩余大小
    */
   
private String free;


   /**
    * 已经使用量
    */
   
private String used;


   /**
    * 资源的使用率
    */
   
private double usage;
}
复制


    主程序类
import cn.hutool.core.net.NetUtil;
import
cn.hutool.core.util.NumberUtil;
import
com.gw.ard.common.tools.IpUtil;
import
lombok.Data;
import
oshi.SystemInfo;
import
oshi.hardware.CentralProcessor;
import
oshi.hardware.CentralProcessor.TickType;
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.io.Serializable;
import
java.util.LinkedList;
import
java.util.List;
import
java.util.Properties;


@Data
public class SystemHardwareInfo  implements Serializable {

   private static final long serialVersionUID = 1L;


   private static final int OSHI_WAIT_SECOND = 1000;


   /**
    * CPU相关信息
    */
   
private Cpu cpu = new Cpu();


   /**
    * 內存相关信息
    */
   
private Mem mem = new Mem();


   /**
    * JVM相关信息
    */
   
private Jvm jvm = new Jvm();


   /**
    * 服务器相关信息
    */
   
private Sys sys = new Sys();


   /**
    * 磁盘相关信息
    */
   
private List<SysFile> sysFiles = new LinkedList<SysFile>();


   public void copyTo() throws Exception {
       SystemInfo si = new SystemInfo();
       
HardwareAbstractionLayer hal = si.getHardware();


       setCpuInfo(hal.getProcessor());


       setMemInfo(hal.getMemory());


       setSysInfo();


       setJvmInfo();


       setSysFiles(si.getOperatingSystem());
   
}

   /**
    * 设置CPU信息
    */
   
private void setCpuInfo(CentralProcessor processor) {
       // CPU信息
       
long[] prevTicks = processor.getSystemCpuLoadTicks();
       
Util.sleep(OSHI_WAIT_SECOND);
       long
[] ticks = processor.getSystemCpuLoadTicks();
       long
nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
       long
irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
       long
softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
       long
steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
       long
cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
       long
user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
       long
iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
       long
idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
       long
totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
       
cpu.setCpuNum(processor.getLogicalProcessorCount());
       
cpu.setTotal(totalCpu);
       
cpu.setSys(cSys);
       
cpu.setUsed(user);
       
cpu.setWait(iowait);
       
cpu.setFree(idle);
   
}

   /**
    * 设置内存信息
    */
   
private void setMemInfo(GlobalMemory memory) {
       mem.setTotal(memory.getTotal());
       
mem.setUsed(memory.getTotal() - memory.getAvailable());
       
mem.setFree(memory.getAvailable());
   
}

   /**
    * 设置服务器信息
    */
   
private void setSysInfo() {
       Properties props = System.getProperties();
       
sys.setComputerName(IpUtil.getHostName());
       
sys.setComputerIp(NetUtil.getLocalhostStr());
       
sys.setOsName(props.getProperty("os.name"));
       
sys.setOsArch(props.getProperty("os.arch"));
       
sys.setUserDir(props.getProperty("user.dir"));
   
}

   /**
    * 设置Java虚拟机
    */
   
private void setJvmInfo() {
       Properties props = System.getProperties();
       
jvm.setTotal(Runtime.getRuntime().totalMemory());
       
jvm.setMax(Runtime.getRuntime().maxMemory());
       
jvm.setFree(Runtime.getRuntime().freeMemory());
       
jvm.setVersion(props.getProperty("java.version"));
       
jvm.setHome(props.getProperty("java.home"));
   
}

   /**
    * 设置磁盘信息
    */
   
private void setSysFiles(OperatingSystem os) {
       FileSystem fileSystem = os.getFileSystem();
       
OSFileStore[] fsArray = fileSystem.getFileStores();
       for
(OSFileStore fs : fsArray) {
           long free = fs.getUsableSpace();
           long
total = fs.getTotalSpace();
           long
used = total - free;
           
SysFile sysFile = new SysFile();
           
sysFile.setDirName(fs.getMount());
           
sysFile.setSysTypeName(fs.getType());
           
sysFile.setTypeName(fs.getName());
           
sysFile.setTotal(convertFileSize(total));
           
sysFile.setFree(convertFileSize(free));
           
sysFile.setUsed(convertFileSize(used));
           
sysFile.setUsage(NumberUtil.round(NumberUtil.mul(used, total, 4), 100).doubleValue());
           
sysFiles.add(sysFile);
       
}
   }

   /**
    * 字节转换
    *
    *
@param size 字节大小
    *
@return 转换后值
    */
   
public 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);
       
}
   }
}
复制


  这样,只需要调用当前类的copyTo()方法即可获取到数据信息;


  踩坑比较多,不过最终定位踩坑的主要原因在于jar包的版本上,大家只要注意好jar包的版本后,基本上就可以跳过很多坑了;


  我们在应用程序中,只需要调用一下这个类的copyTo()方法即可获取到当前部署节点的应用信息和服务器资源信息,如果还想获取更多其他的信息可以参考OSHI的API文档;


  还有,如果我们是分布式项目的话,使用oshi也可以监听到分布式项目的所有的节点,见下一章节dubbo的远程Rpc调用;


2020年6月16日 18:24:42



文章转载自加耀,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论