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

1. 简介

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

2. 相关博客

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

3. 示例代码

  • 创建项目
  • 修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.c3stones</groupId>
    <artifactId>spring-boot-monitor-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.8</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.8.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.24</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 配置定时巡检预警阈值
      在resources下新建配置文件application.yml。
# 预警配置
monitor:
  warn:
    enabled: true
    cpu:
      stage1: 85
      stage2: 95
    memory:
      stage1: 85
      stage2: 95
    disk:
      stage1: 85
      stage2: 95
  • 创建预警配置
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 预警配置
 *
 * @author  CL
 */
@Getter
@Component
public class WarnConfig {

    /**
     * 预警开关
     */
    @Value("${monitor.warn.enabled:true}")
    private Boolean enabled;

    /**
     * CPU - 阶段1
     */
    @Value("${monitor.warn.cpu.stage1:85}")
    private Double cpuStage1;

    /**
     * CPU - 阶段2
     */
    @Value("${monitor.warn.cpu.stage2:95}")
    private Double cpuStage2;

    /**
     * 内存 - 阶段1
     */
    @Value("${monitor.warn.memory.stage1:85}")
    private Double memoryStage1;

    /**
     * 内存 - 阶段2
     */
    @Value("${monitor.warn.memory.stage2:95}")
    private Double memoryStage2;

    /**
     * 磁盘 - 阶段1
     */
    @Value("${monitor.warn.disk.stage1:85}")
    private Double diskStage1;

    /**
     * 磁盘 - 阶段2
     */
    @Value("${monitor.warn.disk.stage2:95}")
    private Double diskStage2;

}
  • 创建预警状态枚举
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<Object> 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<Row> 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<Object> 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<String> 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<Drive> 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.collection.ListUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.StrUtil;
import com.c3stones.monitor.print.Layout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.stream.Collectors;

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

    /**
     * GC收集器
     */
    private List<GcCollector> collectors;

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

    /**
     * GC收集器
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class GcCollector {

        /**
         * 收集器名称
         */
        private String name;

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

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

        @Override
        public String toString() {
            return Layout.Table.of(name,
                    Layout.Row.of("Count", count),
                    Layout.Row.of("Time", DateUtil.formatBetween(time, 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.lang.Opt;
import cn.hutool.core.text.CharPool;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
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;
    }

    /**
     * 打印
     *
     * @param cmd 指定参数。all-全部监控信息,xxx-单个监控信息,默认展示CPU/Memory/gc信息。
     * @return {@link String}
     */
    @GetMapping({"", "/print"})
    public String print(String cmd) {
        boolean isAll = StrUtil.equalsIgnoreCase("all", cmd);

        Map<String, Function<Void, Object>> items = new LinkedHashMap<>();
        items.put("Server Info", unused -> server());
        items.put("JVM", unused -> jvm());
        items.put("CPU", unused -> cpu());
        items.put("Memory", unused -> memory());
        items.put("Disk", unused -> disk());
        items.put("Heap/NonHeap", unused -> heap());
        items.put("Thread", unused -> thread());
        items.put("GC", unused -> gc());

        Stream<Layout> streams = items.entrySet().stream()
                .filter(entry -> isAll ? Boolean.TRUE : StrUtil.containsAnyIgnoreCase(entry.getKey(),
                        Opt.ofBlankAble(cmd).orElse("CPU&Memory&GC").split(Objects.toString(CharPool.AMP))))
                .map(entry -> Layout.of(entry.getKey(), entry.getValue().apply(null)));

        String tip = !isAll ? "Tip: Set parameters [cmd=all] will return more information."
                + StrUtil.LF + StrUtil.LF : StrUtil.EMPTY;
        return tip + streams.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<String, String> 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<Disk.Drive> 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() {
        List<Gc.GcCollector> collectors = ManagementFactory.getGarbageCollectorMXBeans().stream().map(garbageCollectorMXBean -> Gc.GcCollector.builder()
                        .name(garbageCollectorMXBean.getName())
                        .count(garbageCollectorMXBean.getCollectionCount())
                        .time(garbageCollectorMXBean.getCollectionTime())
                        .build()).sorted(Comparator.comparing(Gc.GcCollector::getCount).reversed())
                .collect(Collectors.toList());
        return Gc.builder().collectors(collectors).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?cmd=all

  接口响应:

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:

 ----------------------- 
┊PS Scavenge            ┊
 ----------------------- 
┊ Count     ┊ 5         ┊
┊ Time      ┊ 240ms     ┊
 ----------------------- 
 ----------------------- 
┊PS MarkSweep           ┊
 ----------------------- 
┊ Count     ┊ 2         ┊
┊ Time      ┊ 266ms     ┊
 ----------------------- 
 
  • 测试单个监控信息
curl http://127.0.0.1:8080/monitor/print?cmd=memory

  接口响应:

Tip: Set parameters [cmd=all] will return more information.

Memory:

 ------------------------------------------ 
┊ Total Physical Memory     ┊ 15.76GB      ┊
┊ Free Physical Memory      ┊ 2.85GB       ┊
┊ Used Physical Memory      ┊ 12.91GB      ┊
┊ Physical Memory Usage     ┊ 81.93%       ┊
┊ Total Memory              ┊ 249MB        ┊
┊ Free Memory               ┊ 226.00MB     ┊
┊ Used Memory               ┊ 23.00MB      ┊
┊ Max Memory                ┊ 3.50GB       ┊
┊ Max Use Memory            ┊ 3.48GB       ┊
┊ Memory Usage              ┊ 9.24%        ┊
 ------------------------------------------ 

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

  接口响应:

Tip: Set parameters [cmd=all] will return more information.

CPU:

 --------------------------------------- 
┊ Available Processors     ┊ 8          ┊
┊ System CPU Usage         ┊ 67.12%     ┊
┊ Process CPU Usage        ┊ 0.07%      ┊
 --------------------------------------- 

Memory:

 ------------------------------------------ 
┊ Total Physical Memory     ┊ 15.76GB      ┊
┊ Free Physical Memory      ┊ 3.15GB       ┊
┊ Used Physical Memory      ┊ 12.61GB      ┊
┊ Physical Memory Usage     ┊ 80.00%       ┊
┊ Total Memory              ┊ 249MB        ┊
┊ Free Memory               ┊ 218.76MB     ┊
┊ Used Memory               ┊ 30.24MB      ┊
┊ Max Memory                ┊ 3.50GB       ┊
┊ Max Use Memory            ┊ 3.47GB       ┊
┊ Memory Usage              ┊ 12.14%       ┊
 ------------------------------------------ 

GC:

 ----------------------- 
┊PS Scavenge            ┊
 ----------------------- 
┊ Count     ┊ 5         ┊
┊ Time      ┊ 240ms     ┊
 ----------------------- 
 ----------------------- 
┊PS MarkSweep           ┊
 ----------------------- 
┊ Count     ┊ 2         ┊
┊ Time      ┊ 266ms     ┊
 ----------------------- 
 
  • 测试主动预警
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

posted @ 2023-03-09 21:21  C3Stones  阅读(1788)  评论(0编辑  收藏  举报