SpringBoot自主监控,获取服务信息、JVM、CPU、内存、磁盘、堆、线程、GC等

1. 简介

  在日常开发中一些关键的业务服务,期望在高并发状态下可以正常工作,或在异常情况时可以记录当时的性能信息,所以就需要进行监控。常见的监控例如:Prometheus可以实现这个需求,如果需要更加简单方便的自主监控能力,可以引入本博客中的方案。

2. 相关博客

  Promtail+Loki+Grafana搭建轻量级日志管理平台SpringBoot 2.x + Prometheus + Grafana 实现应用监控

3. 示例代码

  • 创建项目SpringBoot自主监控,获取服务信息、JVM、CPU、内存、磁盘、堆、线程、GC等_第1张图片
  • 修改pom.xml


    4.0.0

    com.c3stones
    spring-boot-monitor-demo
    1.0-SNAPSHOT

    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.8
    

    
        
            cn.hutool
            hutool-core
            5.8.12
        
        
            com.alibaba.fastjson2
            fastjson2
            2.0.24
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-web
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

  • 配置定时巡检预警阈值
      在resources下新建配置文件application.yml。
# 预警配置
monitor:
  warn:
    enabled: true
    cpu:
      stage1: 85
      stage2: 95
    memory:
      stage1: 85
      stage2: 95
    disk:
      stage1: 85
      stage2: 95
  • 创建预警配置
import cn.hutool.core.util.NumberUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Objects;

/**
 * 预警状态枚举类
 *
 * @author  CL
 */
@Getter
@AllArgsConstructor
public enum WarnSateEnum {

    /**
     * 正常
     */
    GREEN(1),

    /**
     * 警告
     */
    YELLOW(2),

    /**
     * 紧急
     */
    RED(3),
    ;

    private final int value;

    /**
     * 根据值获取枚举
     *
     * @param value 值
     * @return {@link WarnSateEnum}
     */
    public static WarnSateEnum findByValue(Integer value) {
        if (Objects.nonNull(value)) {
            for (WarnSateEnum warnState : values()) {
                if (NumberUtil.equals(warnState.getValue(), value)) {
                    return warnState;
                }
            }
        }
        return GREEN;
    }

    /**
     * 获取高优先级
     *
     * @param warnSateEnum 预警状态
     * @return
     */
    public WarnSateEnum max(WarnSateEnum warnSateEnum) {
        if (Objects.nonNull(warnSateEnum)) {
            return findByValue(Math.max(this.value, warnSateEnum.value));
        }
        return this;
    }

}
  • 创建预警状态枚举
import cn.hutool.core.util.NumberUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Objects;

/**
 * 预警状态枚举类
 *
 * @author  CL
 */
@Getter
@AllArgsConstructor
public enum WarnSateEnum {

    /**
     * 正常
     */
    GREEN(1),

    /**
     * 警告
     */
    YELLOW(2),

    /**
     * 紧急
     */
    RED(3),
    ;

    private final int value;

    /**
     * 根据值获取枚举
     *
     * @param value 值
     * @return {@link WarnSateEnum}
     */
    public static WarnSateEnum findByValue(Integer value) {
        if (Objects.nonNull(value)) {
            for (WarnSateEnum warnState : values()) {
                if (NumberUtil.equals(warnState.getValue(), value)) {
                    return warnState;
                }
            }
        }
        return GREEN;
    }

    /**
     * 获取高优先级
     *
     * @param warnSateEnum 预警状态
     * @return
     */
    public WarnSateEnum max(WarnSateEnum warnSateEnum) {
        if (Objects.nonNull(warnSateEnum)) {
            return findByValue(Math.max(this.value, warnSateEnum.value));
        }
        return this;
    }

}
  • 创建字节转文本工具类
/**
 * 字节转换工具类
 *
 * @author CL
 */
public class ByteUtil {

    private static final int UNIT = 1024;

    /**
     * 格式化字节大小
     *
     * @param byteSize 字节大小
     * @return {@link String}
     */
    public static String formatByteSize(long byteSize) {

        if (byteSize <= -1) {
            return String.valueOf(byteSize);
        }

        double size = 1.0 * byteSize;

        String type;
        if ((int) Math.floor(size / UNIT) <= 0) { // 不足1KB
            type = "B";
            return format(size, type);
        }

        size = size / UNIT;
        if ((int) Math.floor(size / UNIT) <= 0) { // 不足1MB
            type = "KB";
            return format(size, type);
        }

        size = size / UNIT;
        if ((int) Math.floor(size / UNIT) <= 0) { // 不足1GB
            type = "MB";
            return format(size, type);
        }

        size = size / UNIT;
        if ((int) Math.floor(size / UNIT) <= 0) { // 不足1TB
            type = "GB";
            return format(size, type);
        }

        size = size / UNIT;
        if ((int) Math.floor(size / UNIT) <= 0) { // 不足1PB
            type = "TB";
            return format(size, type);
        }

        size = size / UNIT;
        if ((int) Math.floor(size / UNIT) <= 0) {
            type = "PB";
            return format(size, type);
        }
        return ">PB";
    }

    /**
     * 格式化字节大小为指定单位
     *
     * @param size 字节大小
     * @param type 单位类型
     * @return {@link String}
     */
    private static String format(double size, String type) {
        int precision;

        if (size * 100 % 10 > 0) {
            precision = 2;
        } else if (size * 10 % 10 > 0) {
            precision = 1;
        } else {
            precision = 0;
        }

        String formatStr = "%." + precision + "f";

        if ("KB".equals(type)) {
            return String.format(formatStr, (size)) + "KB";
        } else if ("MB".equals(type)) {
            return String.format(formatStr, (size)) + "MB";
        } else if ("GB".equals(type)) {
            return String.format(formatStr, (size)) + "GB";
        } else if ("TB".equals(type)) {
            return String.format(formatStr, (size)) + "TB";
        } else if ("PB".equals(type)) {
            return String.format(formatStr, (size)) + "PB";
        }
        return String.format(formatStr, (size)) + "B";
    }

}
  • 创建打印格式类
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 打印布局
 *
 * @author CL
 */
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Layout {

    // 横线和竖线
    private final static String H_LINE = "-";
    private final static String V_LINE = "┊";

    /**
     * 大标题
     */
    private String headline;

    /**
     * 数据
     */
    private ArrayList objs;

    /**
     * 自定义构造方法
     *
     * @param headline 大标题
     * @param objs     数据
     */
    private Layout(String headline, Object... objs) {
        this.headline = headline;
        this.objs = CollUtil.toList(objs);
    }

    /**
     * 自定义静态构造方法
     *
     * @param headline 大标题
     * @param objs     数据
     * @return {@link Layout}
     */
    public static Layout of(String headline, Object... objs) {
        return new Layout(headline, objs);
    }

    /**
     * 重写toString方法
     *
     * @return {@link String}
     */
    @Override
    public String toString() {
        StringJoiner sj = new StringJoiner(StrUtil.LF);
        if (Objects.nonNull(headline)) {
            sj.add(headline + StrUtil.COLON);
            sj.add(StrUtil.EMPTY);
        }
        objs.forEach(obj -> sj.add(obj.toString()));
        sj.add(StrUtil.EMPTY);
        return sj.toString();
    }


    /**
     * 表格
     */
    @Getter
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public static class Table {

        /**
         * 标题
         */
        private String title;

        /**
         * 行
         */
        private ArrayList rows;

        /**
         * 自定义构造方法
         *
         * @param title 标题
         * @param rows  行
         */
        private Table(String title, Row... rows) {
            this.title = title;
            this.rows = CollUtil.toList(rows);
        }

        /**
         * 自定义静态构造方法
         *
         * @param title 标题
         * @param rows  行
         * @return {@link Table}
         */
        public static Table of(String title, Row... rows) {
            return new Table(title, rows);
        }

        /**
         * 自定义静态构造方法
         *
         * @param rows 行
         * @return {@link Table}
         */
        public static Table of(Row... rows) {
            return new Table(null, rows);
        }

        /**
         * 重写ToString方法
         *
         * @return {@link String}
         */
        @Override
        public String toString() {
            int maxKeyLength = rows.stream().map(Row::getKey).map(StrUtil::length).max(Comparator.comparing(Integer::valueOf)).orElse(1) + 5;
            int maxValueLength = rows.stream().map(Row::getValues).flatMap(Collection::stream).map(StrUtil::toStringOrNull).map(StrUtil::length).max(Comparator.comparing(Integer::valueOf)).orElse(1) + 5;
            int totalLength = maxKeyLength + maxValueLength + 3;
            // 分割行
            String spit = StrUtil.SPACE + IntStream.range(0, totalLength).mapToObj(i -> H_LINE).collect(Collectors.joining()) + StrUtil.SPACE;
            StringJoiner sj = new StringJoiner(StrUtil.LF);
            if (Objects.nonNull(title)) {
                sj.add(spit);
                sj.add(V_LINE + StrUtil.padAfter(title, totalLength, StrUtil.SPACE) + V_LINE);
            }
            sj.add(spit);
            rows.forEach(row -> sj.add(row.toString(maxKeyLength, maxValueLength)));
            sj.add(spit);
            return sj.toString();
        }

    }

    /**
     * 行
     */
    @Getter
    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    public static class Row {

        /**
         * 键
         */
        private final String key;

        /**
         * 跨行
         */
        private final int rowspan;

        /**
         * 值
         */
        private final ArrayList values;

        /**
         * 自定义静态构造方法
         *
         * @param key   键
         * @param value 值
         * @return {@link Row}
         */
        public static Row of(String key, Object value) {
            return of(key, 1, value);
        }

        /**
         * 自定义静态构造方法
         *
         * @param key     键
         * @param rowspan 跨行
         * @param value   值
         * @return {@link Row}
         */
        public static Row of(String key, int rowspan, Object value) {
            return new Row(key, Math.max(rowspan, 1), value instanceof List ? new ArrayList<>(((List) value)) : CollUtil.toList(value));
        }

        /**
         * 重写ToString方法
         *
         * @param maxKeyLength   最大的键长度
         * @param maxValueLength 最大的值长度
         * @return {@link String}
         */
        public String toString(int maxKeyLength, int maxValueLength) {
            StringJoiner sj = new StringJoiner(StrUtil.LF);
            IntStream.range(0, rowspan).forEach(i -> sj.add(
                    V_LINE + StrUtil.SPACE +
                            String.format("%-" + Math.max(maxKeyLength, 1) + "s", i == 0 ? key : StrUtil.SPACE) +
                            V_LINE + StrUtil.SPACE +
                            String.format("%-" + Math.max(maxValueLength, 1) + "s", values.get(i)) +
                            V_LINE));
            return sj.toString();
        }

    }

} 
   
  • 创建监控信息BO
import com.c3stones.monitor.print.Layout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 服务器信息
 *
 * @author CL
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Server {

    /**
     * 主机名
     */
    private String hostName;

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

    /**
     * 操作系统版本
     */
    private String osVersion;

    /**
     * 系统架构
     */
    private String arch;

    /**
     * 本地IP
     */
    private String localIp;

    @Override
    public String toString() {
        return Layout.Table.of(
                Layout.Row.of("HostName", hostName),
                Layout.Row.of("OS", osName + "/" + osVersion),
                Layout.Row.of("Arch", arch),
                Layout.Row.of("LocalIp", localIp)
        ).toString();
    }

}
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import com.c3stones.monitor.print.Layout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;

import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;

/**
 * JVM信息
 *
 * @author CL
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Jvm {

    /**
     * 虚拟机名称
     */
    private String vmName;

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

    /**
     * JavaHome
     */
    private String javaHome;

    /**
     * 进程号
     */
    private String pid;

    /**
     * 启动时间
     */
    private LocalDateTime startTime;

    /**
     * 运行时长
     */
    private long runtime;

    /**
     * 启动餐参数
     */
    private List startParameters;

    @Override
    public String toString() {
        return Layout.Table.of(
                Layout.Row.of("VM Name", vmName),
                Layout.Row.of("JDK Version", jdkVersion),
                Layout.Row.of("Java Home", javaHome),
                Layout.Row.of("PID", pid),
                Layout.Row.of("StartTime", NORM_DATETIME_FORMATTER.format(startTime)),
                Layout.Row.of("RunTime", DateUtil.formatBetween(runtime, BetweenFormatter.Level.SECOND)
                        .replaceAll(BetweenFormatter.Level.SECOND.getName(), "s")
                        .replaceAll(BetweenFormatter.Level.MINUTE.getName(), "m")
                        .replaceAll(BetweenFormatter.Level.HOUR.getName(), "h")
                        .replaceAll(BetweenFormatter.Level.DAY.getName(), "d")),
                Layout.Row.of("Start Parameters", startParameters.size(), startParameters)
        ).toString();
    }

}
import cn.hutool.core.util.NumberUtil;
import com.c3stones.monitor.print.Layout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * CPU信息
 *
 * @author  CL
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Cpu {

    /**
     * 可用处理器数
     */
    private int availableProcssors;

    /**
     * 系统CPU使用率
     */
    private BigDecimal systemCpuUsage;

    /**
     * 当前进程CPU使用率
     */
    private BigDecimal processCpuUsage;

    @Override
    public String toString() {
        return Layout.Table.of(
                Layout.Row.of("Available Processors", availableProcssors),
                Layout.Row.of("System CPU Usage", NumberUtil.mul(systemCpuUsage, 100).setScale(2, RoundingMode.HALF_UP) + "%"),
                Layout.Row.of("Process CPU Usage", NumberUtil.mul(processCpuUsage, 100).setScale(2, RoundingMode.HALF_UP) + "%")
        ).toString();
    }

}
import cn.hutool.core.util.NumberUtil;
import com.c3stones.monitor.print.Layout;
import com.c3stones.monitor.utils.ByteUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 内存信息
 *
 * @author CL
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Memory {

    /**
     * 总物理内存
     */
    private long totalPhysicalMemory;

    /**
     * 空闲物理内存
     */
    private long freePhysicalMemory;

    /**
     * 已使用物理内存
     */
    private long usedPhysicalMemory;

    /**
     * 物理内存使用率
     */
    private BigDecimal physicalMemoryUsage;

    /**
     * 总内存
     */
    private long totalMemory;

    /**
     * 空闲内存
     */
    private long freeMemory;

    /**
     * 已用内存
     */
    private long usedMemory;

    /**
     * 最大内存
     */
    private long maxMemory;

    /**
     * 最大可用内存
     */
    private long maxUseMemory;

    /**
     * 内存使用率
     */
    private BigDecimal memoryUsage;

    @Override
    public String toString() {
        return Layout.Table.of(
                Layout.Row.of("Total Physical Memory", ByteUtil.formatByteSize(totalPhysicalMemory)),
                Layout.Row.of("Free Physical Memory", ByteUtil.formatByteSize(freePhysicalMemory)),
                Layout.Row.of("Used Physical Memory", ByteUtil.formatByteSize(usedPhysicalMemory)),
                Layout.Row.of("Physical Memory Usage", NumberUtil.mul(physicalMemoryUsage, 100).setScale(2, RoundingMode.HALF_UP) + "%"),
                Layout.Row.of("Total Memory", ByteUtil.formatByteSize(totalMemory)),
                Layout.Row.of("Free Memory", ByteUtil.formatByteSize(freeMemory)),
                Layout.Row.of("Used Memory", ByteUtil.formatByteSize(usedMemory)),
                Layout.Row.of("Max Memory", ByteUtil.formatByteSize(maxMemory)),
                Layout.Row.of("Max Use Memory", ByteUtil.formatByteSize(maxUseMemory)),
                Layout.Row.of("Memory Usage", NumberUtil.mul(memoryUsage, 100).setScale(2, RoundingMode.HALF_UP) + "%")
        ).toString();
    }

}
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.c3stones.monitor.print.Layout;
import com.c3stones.monitor.utils.ByteUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 磁盘信息
 *
 * @author CL
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Disk {

    /**
     * 盘符
     */
    private List devices;

    @Override
    public String toString() {
        return Opt.ofNullable(devices).orElse(ListUtil.empty()).stream().map(Drive::toString).collect(Collectors.joining(StrUtil.LF));
    }

    /**
     * 盘符
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Drive {

        /**
         * 盘符名称
         */
        private String name;

        /**
         * 总大小
         */
        private long totalSpace;

        /**
         * 空闲大小
         */
        private long freeSpace;

        /**
         * 已用大小
         */
        private long usedSpace;

        /**
         * 盘符使用率
         */
        private BigDecimal driveUsage;

        @Override
        public String toString() {
            return Layout.Table.of(name,
                    Layout.Row.of("Total Space", ByteUtil.formatByteSize(totalSpace)),
                    Layout.Row.of("Free Space", ByteUtil.formatByteSize(freeSpace)),
                    Layout.Row.of("Used Space", ByteUtil.formatByteSize(usedSpace)),
                    Layout.Row.of("Drive Space", NumberUtil.mul(driveUsage, 100).setScale(2, RoundingMode.HALF_UP) + "%")
            ).toString();
        }
    }

}
import com.c3stones.monitor.print.Layout;
import com.c3stones.monitor.utils.ByteUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 堆/非堆信息
 *
 * @author CL
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Heap {

    /**
     * 堆初始化大小
     */
    private long heapInit;

    /**
     * 堆最大大小
     */
    private long heapMaxMemory;

    /**
     * 堆已用大小
     */
    private long heapUsedMemory;

    /**
     * 堆空闲大小
     */
    private long heapFreeMemory;

    /**
     * 非堆初始化大小
     */
    private long nonHeapInit;

    /**
     * 非堆最大大小
     */
    private long nonHeapMaxMemory;

    /**
     * 非堆已用大小
     */
    private long nonHeapUsedMemory;

    /**
     * 非堆空闲大小
     */
    private long nonHeapFreeMemory;

    @Override
    public String toString() {
        return Layout.Table.of(
                Layout.Row.of("Heap Init Size", ByteUtil.formatByteSize(heapInit)),
                Layout.Row.of("Heap Max Size", ByteUtil.formatByteSize(heapMaxMemory)),
                Layout.Row.of("Heap Used Size", ByteUtil.formatByteSize(heapUsedMemory)),
                Layout.Row.of("Heap Free Size", ByteUtil.formatByteSize(heapFreeMemory)),
                Layout.Row.of("NonHeap Init Size", ByteUtil.formatByteSize(nonHeapInit)),
                Layout.Row.of("NonHeap Max Size", ByteUtil.formatByteSize(nonHeapMaxMemory)),
                Layout.Row.of("NonHeap Used Size", ByteUtil.formatByteSize(nonHeapUsedMemory)),
                Layout.Row.of("NonHeap Free Size", ByteUtil.formatByteSize(nonHeapFreeMemory))
        ).toString();
    }

}
import com.c3stones.monitor.print.Layout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 线程信息
 *
 * @author CL
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Thread {

    /**
     * 活动线程数
     */
    private int threadCount;

    /**
     * 峰值活动线程数
     */
    private int peakThreadCount;

    /**
     * 守护线程数
     */
    private int daemonThreadCount;

    /**
     * 非守护线程数
     */
    private int nonDaemonThreadCount;

    /**
     * 总线程数
     */
    private long totalStartedThreadCount;

    @Override
    public String toString() {
        return Layout.Table.of(
                Layout.Row.of("Active Thread Count", threadCount),
                Layout.Row.of("Active Peak Thread Count", peakThreadCount),
                Layout.Row.of("Daemon Thread Count", daemonThreadCount),
                Layout.Row.of("NonDaemon Thread Count", nonDaemonThreadCount),
                Layout.Row.of("Total Thread Count", totalStartedThreadCount)
        ).toString();
    }
    
}
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import com.c3stones.monitor.print.Layout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 垃圾回收信息
 *
 * @author CL
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Gc {

    /**
     * 收集总数
     */
    private long collectionCount;

    /**
     * 收集耗时
     */
    private long collectionTime;

    @Override
    public String toString() {
        return Layout.Table.of(
                Layout.Row.of("Total Collection Count", collectionCount),
                Layout.Row.of("Total Collection Time", DateUtil.formatBetween(collectionTime, BetweenFormatter.Level.MILLISECOND)
                        .replaceAll(BetweenFormatter.Level.MILLISECOND.getName(), "ms")
                        .replaceAll(BetweenFormatter.Level.SECOND.getName(), "s")
                        .replaceAll(BetweenFormatter.Level.MINUTE.getName(), "m")
                        .replaceAll(BetweenFormatter.Level.HOUR.getName(), "h")
                        .replaceAll(BetweenFormatter.Level.DAY.getName(), "d"))
        ).toString();
    }

}
  • 创建监控Controller
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.c3stones.monitor.bo.Thread;
import com.c3stones.monitor.bo.*;
import com.c3stones.monitor.config.WarnConfig;
import com.c3stones.monitor.config.WarnSateEnum;
import com.c3stones.monitor.print.Layout;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.lang.management.*;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 监控 Controller
 *
 * @author CL
 */
@Slf4j
@RestController
@RequestMapping("/monitor")
public class MonitorController {

    @Autowired
    private WarnConfig warnConfig;

    /**
     * 巡检
     */
    @Scheduled(cron = "0 0/10 * * * ?")
    public void patrol() {
        // 发现预警
        warn();
    }

    /**
     * 预警
     *
     * @return {@link WarnSateEnum}
     */
    @GetMapping("/warn")
    public WarnSateEnum warn() {
        // 不开启,则返回空
        if (BooleanUtil.isFalse(warnConfig.getEnabled())) return null;

        WarnSateEnum result = WarnSateEnum.GREEN;

        // 判断CPU
        Cpu cpu = cpu();
        if (NumberUtil.sub(cpu.getSystemCpuUsage().doubleValue() * 100, warnConfig.getCpuStage2().doubleValue()) >= 0 ||
                NumberUtil.sub(cpu.getProcessCpuUsage().doubleValue() * 100, warnConfig.getCpuStage2().doubleValue()) >= 0) {
            result = result.max(WarnSateEnum.RED);
            log.error("Monitoring capability detection cpu warning!!!" + StrUtil.LF + cpu);
        } else if (NumberUtil.sub(cpu.getSystemCpuUsage().doubleValue() * 100, warnConfig.getCpuStage1().doubleValue()) >= 0 ||
                NumberUtil.sub(cpu.getProcessCpuUsage().doubleValue() * 100, warnConfig.getCpuStage1().doubleValue()) >= 0) {
            result = result.max(WarnSateEnum.YELLOW);
            log.warn("Monitoring capability detection cpu warning!!!" + StrUtil.LF + cpu);
        }

        // 判断内存
        Memory memory = memory();
        if (NumberUtil.sub(memory.getPhysicalMemoryUsage().doubleValue() * 100, warnConfig.getMemoryStage2().doubleValue()) >= 0 ||
                NumberUtil.sub(memory.getMemoryUsage().doubleValue() * 100, warnConfig.getMemoryStage2().doubleValue()) >= 0) {
            result = result.max(WarnSateEnum.RED);
            log.warn("Monitoring capability detection memory warning!!!" + StrUtil.LF + memory);
        } else if (NumberUtil.sub(memory.getPhysicalMemoryUsage().doubleValue() * 100, warnConfig.getMemoryStage1().doubleValue()) >= 0 ||
                NumberUtil.sub(memory.getMemoryUsage().doubleValue() * 100, warnConfig.getMemoryStage1().doubleValue()) >= 0) {
            result = result.max(WarnSateEnum.YELLOW);
            log.warn("Monitoring capability detection memory warning!!!" + StrUtil.LF + memory);
        }

        // 判断磁盘
        Disk disk = disk();
        double diskTotalUsage = disk.getDevices().stream().map(Disk.Drive::getDriveUsage).mapToDouble(BigDecimal::doubleValue).sum();
        if (NumberUtil.sub(diskTotalUsage * 100, warnConfig.getDiskStage2().doubleValue()) >= 0) {
            result = result.max(WarnSateEnum.RED);
            log.warn("Monitoring capability detection disk warning!!!" + StrUtil.LF + disk);
        } else if (NumberUtil.sub(diskTotalUsage * 100, warnConfig.getDiskStage1().doubleValue()) >= 0) {
            result = result.max(WarnSateEnum.YELLOW);
            log.warn("Monitoring capability detection disk warning!!!" + StrUtil.LF + disk);
        }

        return result;
    }

    /**
     * 打印
     *
     * @return {@link String}
     */
    @GetMapping({"", "/print"})
    public String print() {
        return Stream.of(
                Layout.of("Server Info", server()),
                Layout.of("JVM", jvm()),
                Layout.of("CPU", cpu()),
                Layout.of("Memory", memory()),
                Layout.of("Disk", disk()),
                Layout.of("Heap/NonHeap", heap()),
                Layout.of("Thread", thread()),
                Layout.of("GC", gc())
        ).map(Layout::toString).collect(Collectors.joining(StrUtil.LF));
    }

    /**
     * 服务器信息
     *
     * @return {@link Server}
     */
    @SneakyThrows
    private Server server() {
        InetAddress inetAddress = InetAddress.getLocalHost();
        OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
        return Server.builder()
                .hostName(inetAddress.getHostName())
                .osName(operatingSystemMXBean.getName())
                .osVersion(operatingSystemMXBean.getVersion())
                .arch(operatingSystemMXBean.getArch())
                .localIp(inetAddress.getHostAddress())
                .build();
    }

    /**
     * Java虚拟机信息
     *
     * @return {@link Jvm}
     */
    private Jvm jvm() {
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        Map systemProperties = runtimeMXBean.getSystemProperties();
        return Jvm.builder()
                .vmName(runtimeMXBean.getVmName())
                .jdkVersion(systemProperties.get("java.runtime.version"))
                .javaHome(systemProperties.get("java.home"))
                .pid(systemProperties.get("PID"))
                .startTime(LocalDateTimeUtil.of(runtimeMXBean.getStartTime()))
                .runtime(runtimeMXBean.getUptime())
                .startParameters(runtimeMXBean.getInputArguments())
                .build();
    }

    /**
     * CPU
     *
     * @return {@link Cpu}
     */
    private Cpu cpu() {
        OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
        JSONObject operatingSystemJson = JSON.parseObject(JSON.toJSONString(operatingSystemMXBean));
        return Cpu.builder()
                .availableProcssors(operatingSystemMXBean.getAvailableProcessors())
                .systemCpuUsage(operatingSystemJson.getBigDecimal("systemCpuLoad"))
                .processCpuUsage(operatingSystemJson.getBigDecimal("processCpuLoad"))
                .build();
    }

    /**
     * 内存
     *
     * @return {@link Memory}
     */
    private Memory memory() {
        OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
        JSONObject operatingSystemJson = JSON.parseObject(JSON.toJSONString(operatingSystemMXBean));
        long totalPhysicalMemory = operatingSystemJson.getLongValue("totalPhysicalMemorySize");
        long freePhysicalMemory = operatingSystemJson.getLongValue("freePhysicalMemorySize");
        long usedPhysicalMemory = totalPhysicalMemory - freePhysicalMemory;
        Runtime runtime = Runtime.getRuntime();
        return Memory.builder()
                .totalPhysicalMemory(totalPhysicalMemory)
                .freePhysicalMemory(freePhysicalMemory)
                .usedPhysicalMemory(usedPhysicalMemory)
                .physicalMemoryUsage(NumberUtil.toBigDecimal(NumberUtil.div(usedPhysicalMemory, totalPhysicalMemory)))
                .totalMemory(runtime.totalMemory())
                .freeMemory(runtime.freeMemory())
                .usedMemory(runtime.totalMemory() - runtime.freeMemory()).maxMemory(runtime.maxMemory())
                .maxUseMemory(runtime.maxMemory() - runtime.totalMemory() + runtime.freeMemory())
                .memoryUsage(NumberUtil.toBigDecimal(NumberUtil.div(runtime.totalMemory() - runtime.freeMemory(), runtime.totalMemory())))
                .build();
    }

    /**
     * 磁盘信息
     *
     * @return {@link Disk}
     */
    private Disk disk() {
        List drives = Stream.of(File.listRoots()).map(file -> Disk.Drive.builder()
                .name(file.toString())
                .totalSpace(file.getTotalSpace())
                .freeSpace(file.getFreeSpace())
                .usedSpace(file.getTotalSpace() - file.getFreeSpace())
                .driveUsage(NumberUtil.toBigDecimal(NumberUtil.div(file.getTotalSpace() - file.getFreeSpace(), file.getTotalSpace())))
                .build()
        ).collect(Collectors.toList());
        return new Disk(drives);
    }

    /**
     * 堆/非堆
     *
     * @return {@link StringBuilder}
     */
    private Heap heap() {
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
        MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
        return Heap.builder()
                .heapInit(heapMemoryUsage.getInit())
                .heapMaxMemory(heapMemoryUsage.getMax())
                .heapUsedMemory(heapMemoryUsage.getUsed())
                .heapFreeMemory(heapMemoryUsage.getMax() - heapMemoryUsage.getUsed())
                .nonHeapInit(nonHeapMemoryUsage.getInit())
                .nonHeapMaxMemory(nonHeapMemoryUsage.getMax())
                .nonHeapUsedMemory(nonHeapMemoryUsage.getUsed())
                .nonHeapFreeMemory(nonHeapMemoryUsage.getMax() - nonHeapMemoryUsage.getUsed())
                .build();
    }

    /**
     * 线程信息
     *
     * @return {@link Thread}
     */
    private Thread thread() {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        return Thread.builder().threadCount(threadMXBean.getThreadCount()).peakThreadCount(threadMXBean.getPeakThreadCount()).daemonThreadCount(threadMXBean.getDaemonThreadCount()).nonDaemonThreadCount(threadMXBean.getThreadCount() - threadMXBean.getDaemonThreadCount()).totalStartedThreadCount(threadMXBean.getTotalStartedThreadCount()).build();
    }

    /**
     * 垃圾回收信息
     *
     * @return {@link Gc}
     */
    private Gc gc() {
        long collectionCount = 0, collectionTime = 0;
        for (GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) {
            collectionCount += garbageCollectorMXBean.getCollectionCount();
            collectionTime += garbageCollectorMXBean.getCollectionTime();
        }
        return Gc.builder().collectionCount(collectionCount).collectionTime(collectionTime).build();
    }

}
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author CL
 */
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

4. 测试

  • 测试监控信息
curl http://127.0.0.1:8080/monitor/print

  接口响应:

Server Info:

 ------------------------------------
┊ HostName     ┊ c3sontes-pc         ┊
┊ OS           ┊ Windows 10/10.0     ┊
┊ Arch         ┊ amd64               ┊
┊ LocalIp      ┊ 127.0.0.1           ┊
 ------------------------------------

JVM:

 --------------------------------------------------------------
┊ VM Name              ┊ Java HotSpot(TM) 64-Bit Server VM     ┊
┊ JDK Version          ┊ 1.8.0_102-b14                         ┊
┊ Java Home            ┊ C:\Java\jdk1.8.0_102\jre              ┊
┊ PID                  ┊ 29128                                 ┊
┊ StartTime            ┊ 2023-03-09 21:14:42                   ┊
┊ RunTime              ┊ 1m8s                                  ┊
┊ Start Parameters     ┊     -Xms1g                            ┊
┊                      ┊ -Xmx2g                                ┊
┊                      ┊ -Xss512k                              ┊
┊                      ┊ -XX:MetaspaceSize=128m                ┊
┊                      ┊ -XX:MaxMetaspaceSize=256m             ┊
┊                      ┊ -Dfile.encoding=UTF-8                 ┊
 --------------------------------------------------------------

CPU:

 ---------------------------------------
┊ Available Processors     ┊ 8          ┊
┊ System CPU Usage         ┊ 54.10%     ┊
┊ Process CPU Usage        ┊ 12.63%     ┊
 ---------------------------------------

Memory:

 ------------------------------------------
┊ Total Physical Memory     ┊ 15.76GB      ┊
┊ Free Physical Memory      ┊ 2.22GB       ┊
┊ Used Physical Memory      ┊ 13.53GB      ┊
┊ Physical Memory Usage     ┊ 85.89%       ┊
┊ Total Memory              ┊ 258.5MB      ┊
┊ Free Memory               ┊ 241.64MB     ┊
┊ Used Memory               ┊ 16.86MB      ┊
┊ Max Memory                ┊ 3.50GB       ┊
┊ Max Use Memory            ┊ 3.49GB       ┊
┊ Memory Usage              ┊ 6.52%        ┊
 ------------------------------------------

Disk:

 --------------------------------
┊C:\                             ┊
 --------------------------------
┊ Total Space     ┊ 150.00GB     ┊
┊ Free Space      ┊ 53.59GB      ┊
┊ Used Space      ┊ 96.41GB      ┊
┊ Drive Space     ┊ 64.27%       ┊
 --------------------------------
 --------------------------------
┊D:\                             ┊
 --------------------------------
┊ Total Space     ┊ 326.52GB     ┊
┊ Free Space      ┊ 263.06GB     ┊
┊ Used Space      ┊ 63.46GB      ┊
┊ Drive Space     ┊ 19.43%       ┊
 --------------------------------

Heap/NonHeap:

 ---------------------------------------
┊ Heap Init Size        ┊ 254MB         ┊
┊ Heap Max Size         ┊ 3.50GB        ┊
┊ Heap Used Size        ┊ 16.86MB       ┊
┊ Heap Free Size        ┊ 3.49GB        ┊
┊ NonHeap Init Size     ┊ 2.44MB        ┊
┊ NonHeap Max Size      ┊ -1            ┊
┊ NonHeap Used Size     ┊ 43.12MB       ┊
┊ NonHeap Free Size     ┊ -45210705     ┊
 ---------------------------------------

Thread:

 ---------------------------------------
┊ Active Thread Count          ┊ 22     ┊
┊ Active Peak Thread Count     ┊ 25     ┊
┊ Daemon Thread Count          ┊ 18     ┊
┊ NonDaemon Thread Count       ┊ 4      ┊
┊ Total Thread Count           ┊ 38     ┊
 ---------------------------------------

GC:

 ----------------------------------------
┊ Total Collection Count     ┊ 7         ┊
┊ Total Collection Time      ┊ 223ms     ┊
 ----------------------------------------
  • 测试主动预警
curl http://127.0.0.1:8080/monitor/warn

  接口响应:

"YELLOW"

  日志打印:

2023-03-09 21:10:50.110  WARN 97132 --- [nio-8080-exec-2] com.c3stones.monitor.MonitorController   : Monitoring capability detection memory warning!!!
 ------------------------------------------ 
┊ Total Physical Memory     ┊ 15.76GB      ┊
┊ Free Physical Memory      ┊ 2.23GB       ┊
┊ Used Physical Memory      ┊ 13.53GB      ┊
┊ Physical Memory Usage     ┊ 85.84%       ┊
┊ Total Memory              ┊ 258.5MB      ┊
┊ Free Memory               ┊ 228.27MB     ┊
┊ Used Memory               ┊ 30.23MB      ┊
┊ Max Memory                ┊ 3.50GB       ┊
┊ Max Use Memory            ┊ 3.47GB       ┊
┊ Memory Usage              ┊ 11.70%       ┊
 ------------------------------------------

5. 项目地址

  spring-boot-monitor-demo

你可能感兴趣的:(jvm,spring,boot,java,mybatis,spring)