天天看点

【监控】你监控的指标都能检测到

简介

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 和内存可用。

【监控】你监控的指标都能检测到

内存和交换文件信息可用。

【监控】你监控的指标都能检测到