update system status api, add UPS/Docker status api
This commit is contained in:
@@ -0,0 +1,142 @@
|
|||||||
|
package com.imyeyu.api.modules.system.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 容器状态缓存
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
public class DockerStatusStore {
|
||||||
|
|
||||||
|
/** 容器状态映射 */
|
||||||
|
private final Map<String, Container> containers = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容器缓存
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Container {
|
||||||
|
|
||||||
|
/** 容器 ID */
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/** 容器名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 镜像 */
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
/** 镜像 ID */
|
||||||
|
private String imageId;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private long createdAt;
|
||||||
|
|
||||||
|
/** 运行状态 */
|
||||||
|
private String state;
|
||||||
|
|
||||||
|
/** 状态描述 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 健康状态 */
|
||||||
|
private String healthStatus;
|
||||||
|
|
||||||
|
/** 启动时间 */
|
||||||
|
private String startedAt;
|
||||||
|
|
||||||
|
/** 结束时间 */
|
||||||
|
private String finishedAt;
|
||||||
|
|
||||||
|
/** 退出码 */
|
||||||
|
private Integer exitCode;
|
||||||
|
|
||||||
|
/** 重启次数 */
|
||||||
|
private int restartCount;
|
||||||
|
|
||||||
|
/** OOM 标记 */
|
||||||
|
private boolean oomKilled;
|
||||||
|
|
||||||
|
/** CPU 百分比 */
|
||||||
|
private Double cpuPercent;
|
||||||
|
|
||||||
|
/** 内存使用量 */
|
||||||
|
private Long memoryUsageBytes;
|
||||||
|
|
||||||
|
/** 内存限制 */
|
||||||
|
private Long memoryLimitBytes;
|
||||||
|
|
||||||
|
/** 内存百分比 */
|
||||||
|
private Double memoryPercent;
|
||||||
|
|
||||||
|
/** 网络接收字节 */
|
||||||
|
private Long networkRxBytes;
|
||||||
|
|
||||||
|
/** 网络发送字节 */
|
||||||
|
private Long networkTxBytes;
|
||||||
|
|
||||||
|
/** 块设备读取字节 */
|
||||||
|
private Long blockReadBytes;
|
||||||
|
|
||||||
|
/** 块设备写入字节 */
|
||||||
|
private Long blockWriteBytes;
|
||||||
|
|
||||||
|
/** 进程数 */
|
||||||
|
private Integer pids;
|
||||||
|
|
||||||
|
/** 采样时间 */
|
||||||
|
private long updatedAt;
|
||||||
|
|
||||||
|
/** 历史点 */
|
||||||
|
private final Deque<Point> history = new ArrayDeque<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容器指标历史点
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Point {
|
||||||
|
|
||||||
|
/** 时间 */
|
||||||
|
private long at;
|
||||||
|
|
||||||
|
/** CPU 百分比 */
|
||||||
|
private Double cpuPercent;
|
||||||
|
|
||||||
|
/** 内存使用量 */
|
||||||
|
private Long memoryUsageBytes;
|
||||||
|
|
||||||
|
/** 内存百分比 */
|
||||||
|
private Double memoryPercent;
|
||||||
|
|
||||||
|
/** 网络接收字节 */
|
||||||
|
private Long networkRxBytes;
|
||||||
|
|
||||||
|
/** 网络发送字节 */
|
||||||
|
private Long networkTxBytes;
|
||||||
|
|
||||||
|
/** 块设备读取字节 */
|
||||||
|
private Long blockReadBytes;
|
||||||
|
|
||||||
|
/** 块设备写入字节 */
|
||||||
|
private Long blockWriteBytes;
|
||||||
|
|
||||||
|
/** 进程数 */
|
||||||
|
private Integer pids;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.system.bean;
|
package com.imyeyu.api.modules.system.bean;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import com.imyeyu.java.TimiJava;
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import lombok.Data;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
@@ -11,22 +11,24 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器状态数据,所有动态数据左出右进,此对象由 IOC 托管
|
* 服务端状态缓存
|
||||||
|
*
|
||||||
|
* <p>该对象只用于采集任务内部缓存,不直接作为接口协议返回。</p>
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:35
|
* @since 2022-01-31 15:35
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Component
|
@Component
|
||||||
public class ServerStatus implements TimiJava {
|
public class ServerStatus implements TimiJava {
|
||||||
|
|
||||||
/** 动态数据更新时轴 */
|
/** 采样时间轴 */
|
||||||
private LinkedList<Number> updateAxis = new LinkedList<>();
|
private LinkedList<Number> updateAxis = new LinkedList<>();
|
||||||
|
|
||||||
/** 系统 */
|
/** 操作系统 */
|
||||||
private OS os = new OS();
|
private OS os = new OS();
|
||||||
|
|
||||||
/** CPU 使用率 */
|
/** CPU */
|
||||||
private CPU cpu = new CPU();
|
private CPU cpu = new CPU();
|
||||||
|
|
||||||
/** 系统内存 */
|
/** 系统内存 */
|
||||||
@@ -35,17 +37,20 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 网络 */
|
/** 网络 */
|
||||||
private Network network = new Network();
|
private Network network = new Network();
|
||||||
|
|
||||||
/** 本程序状态 */
|
/** 硬件 */
|
||||||
|
private Hardware hardware = new Hardware();
|
||||||
|
|
||||||
|
/** JVM */
|
||||||
private JVM jvm = new JVM();
|
private JVM jvm = new JVM();
|
||||||
|
|
||||||
/** 磁盘 */
|
/** 存储分区 */
|
||||||
private List<Partition> partitions = new ArrayList<>();
|
private List<StoragePartition> storagePartitions = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统
|
* 操作系统
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-12 20:55
|
* @since 2022-08-12 20:55
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class OS {
|
public static class OS {
|
||||||
@@ -58,10 +63,10 @@ public class ServerStatus implements TimiJava {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 虚拟机状态
|
* JVM
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 21:10
|
* @since 2022-01-31 21:10
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class JVM {
|
public static class JVM {
|
||||||
@@ -69,80 +74,83 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 启动时间 */
|
/** 启动时间 */
|
||||||
private long bootAt;
|
private long bootAt;
|
||||||
|
|
||||||
/** JVM 名称 */
|
/** 名称 */
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/** JVM 版本 */
|
/** 版本 */
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
/** 内存 */
|
/** GC 名称 */
|
||||||
|
private String gcName;
|
||||||
|
|
||||||
|
/** 堆内存 */
|
||||||
private Memory memory = new Memory();
|
private Memory memory = new Memory();
|
||||||
|
|
||||||
/** 内存回收 */
|
/** GC 状态 */
|
||||||
private ZGC zgc = new ZGC();
|
private GC gc = new GC();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内存
|
* JVM 内存
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-12 20:32
|
* @since 2022-08-12 20:32
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class Memory {
|
public static class Memory {
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始大小 */
|
||||||
private long init;
|
private long init;
|
||||||
|
|
||||||
/** 最大 */
|
/** 最大大小 */
|
||||||
private long max;
|
private long max;
|
||||||
|
|
||||||
/** 已使用 */
|
/** 已用大小 */
|
||||||
private final Deque<Number> used = new ArrayDeque<>();
|
private final Deque<Number> used = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 已提交 */
|
/** 已提交大小 */
|
||||||
private final Deque<Number> committed = new ArrayDeque<>();
|
private final Deque<Number> committed = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内存回收
|
* GC 状态
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-12 20:32
|
* @since 2022-08-12 20:32
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class ZGC {
|
public static class GC {
|
||||||
|
|
||||||
/** 并发周期 */
|
/** 周期次数 */
|
||||||
private long syncCycles = 0;
|
private long syncCycles;
|
||||||
|
|
||||||
/** 累计并发周期耗时(毫秒) */
|
/** 周期累计耗时 */
|
||||||
private long syncCyclesTimeTotal = 0;
|
private long syncCyclesTimeTotal;
|
||||||
|
|
||||||
/** 累计次数 */
|
/** 暂停次数 */
|
||||||
private long pauses = 0;
|
private long pauses;
|
||||||
|
|
||||||
/** 累计回收暂停时长(毫秒) */
|
/** 暂停累计耗时 */
|
||||||
private long pausesTimeTotal = 0;
|
private long pausesTimeTotal;
|
||||||
|
|
||||||
/** 上一次回收时间 */
|
/** 上次暂停时间 */
|
||||||
private long lastPauseAt = 0;
|
private long lastPauseAt;
|
||||||
|
|
||||||
/** 上一次回收大小 */
|
/** 上次回收大小 */
|
||||||
private long lastRecoverySize = 0;
|
private long lastRecoverySize;
|
||||||
|
|
||||||
/** 并发周期耗时 */
|
/** 周期耗时序列 */
|
||||||
private final Deque<Number> syncCyclesTime = new ArrayDeque<>();
|
private final Deque<Number> syncCyclesTime = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 回收暂停时长 */
|
/** 暂停耗时序列 */
|
||||||
private final Deque<Number> pausesTime = new ArrayDeque<>();
|
private final Deque<Number> pausesTime = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中央处理器
|
* CPU
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:40
|
* @since 2022-01-31 15:40
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class CPU {
|
public static class CPU {
|
||||||
@@ -150,19 +158,19 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 名称 */
|
/** 名称 */
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/** 物理核心数量 */
|
/** 物理核心数 */
|
||||||
private int coreCount;
|
private int coreCount;
|
||||||
|
|
||||||
/** 线程数量 */
|
/** 逻辑核心数 */
|
||||||
private int logicalCount;
|
private int logicalCount;
|
||||||
|
|
||||||
/** 温度 */
|
/** 温度 */
|
||||||
private double temperature;
|
private double temperature;
|
||||||
|
|
||||||
/** 系统使用 */
|
/** 系统占用 */
|
||||||
private final Deque<Number> system = new ArrayDeque<>();
|
private final Deque<Number> system = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 总共已使用 */
|
/** 总占用 */
|
||||||
private final Deque<Number> used = new ArrayDeque<>();
|
private final Deque<Number> used = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +178,7 @@ public class ServerStatus implements TimiJava {
|
|||||||
* 系统内存
|
* 系统内存
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:50
|
* @since 2022-01-31 15:50
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class Memory {
|
public static class Memory {
|
||||||
@@ -178,69 +186,174 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 物理内存大小 */
|
/** 物理内存大小 */
|
||||||
private long size;
|
private long size;
|
||||||
|
|
||||||
/** 交换区大小 */
|
/** 交换分区大小 */
|
||||||
private long swapSize;
|
private long swapSize;
|
||||||
|
|
||||||
/** 已使用 */
|
/** 已用内存 */
|
||||||
private final Deque<Number> used = new ArrayDeque<>();
|
private final Deque<Number> used = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 交换区已使用 */
|
/** 已用交换分区 */
|
||||||
private final Deque<Number> swapUsed = new ArrayDeque<>();
|
private final Deque<Number> swapUsed = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网卡网速
|
* 网络
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-10 21:41
|
* @since 2022-08-10 21:41
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class Network {
|
public static class Network {
|
||||||
|
|
||||||
|
/** 网卡名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
/** 累计接收 */
|
/** 累计接收 */
|
||||||
private long recvTotal;
|
private long recvTotal;
|
||||||
|
|
||||||
/** 累计发送 */
|
/** 累计发送 */
|
||||||
private long sentTotal;
|
private long sentTotal;
|
||||||
|
|
||||||
/** 实时接收速度 */
|
/** 实时接收速率 */
|
||||||
private long recvNow;
|
private long recvNow;
|
||||||
|
|
||||||
/** 实时发送速度 */
|
/** 实时发送速率 */
|
||||||
private long sentNow;
|
private long sentNow;
|
||||||
|
|
||||||
/** MAC 地址 */
|
/** MAC 地址 */
|
||||||
private String mac;
|
private String mac;
|
||||||
|
|
||||||
/** 发送 */
|
/** 接收包总数 */
|
||||||
|
private long recvPacketsTotal;
|
||||||
|
|
||||||
|
/** 发送包总数 */
|
||||||
|
private long sentPacketsTotal;
|
||||||
|
|
||||||
|
/** 输入错误包总数 */
|
||||||
|
private long inErrors;
|
||||||
|
|
||||||
|
/** 输出错误包总数 */
|
||||||
|
private long outErrors;
|
||||||
|
|
||||||
|
/** 输入丢弃包总数 */
|
||||||
|
private long inDrops;
|
||||||
|
|
||||||
|
/** 碰撞总数 */
|
||||||
|
private long collisions;
|
||||||
|
|
||||||
|
/** 发送序列 */
|
||||||
private final Deque<Number> sent = new ArrayDeque<>();
|
private final Deque<Number> sent = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 接收 */
|
/** 接收序列 */
|
||||||
private final Deque<Number> recv = new ArrayDeque<>();
|
private final Deque<Number> recv = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分区
|
* 硬件信息
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 20:19
|
* @since 2026-04-06
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class Partition {
|
public static class Hardware {
|
||||||
|
|
||||||
/** 识别 UUID */
|
/** 风扇转速 */
|
||||||
|
private List<Integer> fanSpeeds = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 主板信息 */
|
||||||
|
private Baseboard baseboard = new Baseboard();
|
||||||
|
|
||||||
|
/** BIOS 信息 */
|
||||||
|
private Firmware firmware = new Firmware();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主板信息
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Baseboard {
|
||||||
|
|
||||||
|
/** 厂商 */
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
/** 型号 */
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
/** 版本 */
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/** 序列号 */
|
||||||
|
private String serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIOS 信息
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Firmware {
|
||||||
|
|
||||||
|
/** 厂商 */
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
/** 名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 描述 */
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/** 版本 */
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/** 发布时间 */
|
||||||
|
private String releaseDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储分区
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class StoragePartition {
|
||||||
|
|
||||||
|
/** 物理磁盘名称 */
|
||||||
|
private String diskName;
|
||||||
|
|
||||||
|
/** 物理磁盘型号 */
|
||||||
|
private String diskModel;
|
||||||
|
|
||||||
|
/** 物理磁盘序列号 */
|
||||||
|
private String diskSerial;
|
||||||
|
|
||||||
|
/** 分区标识 */
|
||||||
|
private String partitionId;
|
||||||
|
|
||||||
|
/** 分区名称 */
|
||||||
|
private String partitionName;
|
||||||
|
|
||||||
|
/** 分区类型 */
|
||||||
|
private String partitionType;
|
||||||
|
|
||||||
|
/** 分区 UUID */
|
||||||
private String uuid;
|
private String uuid;
|
||||||
|
|
||||||
/** 路径 */
|
/** 挂载点 */
|
||||||
private String path;
|
private String mountPoint;
|
||||||
|
|
||||||
/** 文件系统类型 */
|
/** 分区总空间 */
|
||||||
private String type;
|
private long totalBytes;
|
||||||
|
|
||||||
/** 已使用 */
|
/** 分区已用空间 */
|
||||||
private long used;
|
private Long usedBytes;
|
||||||
|
|
||||||
/** 总大小 */
|
/** 磁盘传输耗时 */
|
||||||
private long total;
|
private long transferTimeMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.imyeyu.api.modules.system.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 状态缓存
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
public class UpsStatusStore {
|
||||||
|
|
||||||
|
/** 当前状态 */
|
||||||
|
private Snapshot current;
|
||||||
|
|
||||||
|
/** 历史点 */
|
||||||
|
private final Deque<Point> history = new ArrayDeque<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 当前快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Snapshot {
|
||||||
|
|
||||||
|
/** 采样时间 */
|
||||||
|
private long updatedAt;
|
||||||
|
|
||||||
|
/** UPS 数据时间 */
|
||||||
|
private Long upsTime;
|
||||||
|
|
||||||
|
/** 上游主机地址 */
|
||||||
|
private String hostName;
|
||||||
|
|
||||||
|
/** 厂商名称 */
|
||||||
|
private String customer;
|
||||||
|
|
||||||
|
/** 上游版本 */
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/** 设备标识 */
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
/** UPS 类型 */
|
||||||
|
private String upsType;
|
||||||
|
|
||||||
|
/** UPS 形态 */
|
||||||
|
private String morphological;
|
||||||
|
|
||||||
|
/** 输入输出相位 */
|
||||||
|
private String ioPhase;
|
||||||
|
|
||||||
|
/** 工作模式 */
|
||||||
|
private String workMode;
|
||||||
|
|
||||||
|
/** 输入电压 */
|
||||||
|
private Double inputVoltage;
|
||||||
|
|
||||||
|
/** 输入频率 */
|
||||||
|
private Double inputFrequency;
|
||||||
|
|
||||||
|
/** 输出电压 */
|
||||||
|
private Double outputVoltage;
|
||||||
|
|
||||||
|
/** 输出频率 */
|
||||||
|
private Double outputFrequency;
|
||||||
|
|
||||||
|
/** 输出负载百分比 */
|
||||||
|
private Integer outputLoadPercent;
|
||||||
|
|
||||||
|
/** 电池电压 */
|
||||||
|
private Double batteryVoltage;
|
||||||
|
|
||||||
|
/** 电池容量百分比 */
|
||||||
|
private Integer batteryCapacity;
|
||||||
|
|
||||||
|
/** 电池剩余时间 */
|
||||||
|
private Integer batteryRemainTime;
|
||||||
|
|
||||||
|
/** 温度 */
|
||||||
|
private Double temperature;
|
||||||
|
|
||||||
|
/** 是否旁路运行 */
|
||||||
|
private boolean bypassActive;
|
||||||
|
|
||||||
|
/** 是否关机中 */
|
||||||
|
private boolean shutdownActive;
|
||||||
|
|
||||||
|
/** 是否输出开启 */
|
||||||
|
private boolean outputOn;
|
||||||
|
|
||||||
|
/** 是否充电中 */
|
||||||
|
private boolean charging;
|
||||||
|
|
||||||
|
/** 故障类型 */
|
||||||
|
private String faultType;
|
||||||
|
|
||||||
|
/** 故障明细 */
|
||||||
|
private String faultKind;
|
||||||
|
|
||||||
|
/** 告警列表 */
|
||||||
|
private List<String> warnings = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 历史点
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Point {
|
||||||
|
|
||||||
|
/** 采样时间 */
|
||||||
|
private long at;
|
||||||
|
|
||||||
|
/** UPS 数据时间 */
|
||||||
|
private Long upsTime;
|
||||||
|
|
||||||
|
/** 工作模式 */
|
||||||
|
private String workMode;
|
||||||
|
|
||||||
|
/** 输入电压 */
|
||||||
|
private Double inputVoltage;
|
||||||
|
|
||||||
|
/** 输入频率 */
|
||||||
|
private Double inputFrequency;
|
||||||
|
|
||||||
|
/** 输出电压 */
|
||||||
|
private Double outputVoltage;
|
||||||
|
|
||||||
|
/** 输出频率 */
|
||||||
|
private Double outputFrequency;
|
||||||
|
|
||||||
|
/** 输出负载百分比 */
|
||||||
|
private Integer outputLoadPercent;
|
||||||
|
|
||||||
|
/** 电池电压 */
|
||||||
|
private Double batteryVoltage;
|
||||||
|
|
||||||
|
/** 电池容量百分比 */
|
||||||
|
private Integer batteryCapacity;
|
||||||
|
|
||||||
|
/** 电池剩余时间 */
|
||||||
|
private Integer batteryRemainTime;
|
||||||
|
|
||||||
|
/** 温度 */
|
||||||
|
private Double temperature;
|
||||||
|
|
||||||
|
/** 是否旁路运行 */
|
||||||
|
private boolean bypassActive;
|
||||||
|
|
||||||
|
/** 是否关机中 */
|
||||||
|
private boolean shutdownActive;
|
||||||
|
|
||||||
|
/** 是否输出开启 */
|
||||||
|
private boolean outputOn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.imyeyu.api.modules.system.controller;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.service.DockerService;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerStatusView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerSummaryView;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 控制器
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/system/docker")
|
||||||
|
public class DockerController {
|
||||||
|
|
||||||
|
private final DockerService dockerService;
|
||||||
|
|
||||||
|
@GetMapping("/containers")
|
||||||
|
public List<DockerContainerSummaryView> listContainers() {
|
||||||
|
return dockerService.listContainers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/containers/{containerId}/status")
|
||||||
|
public DockerContainerStatusView getContainerStatus(@PathVariable String containerId) {
|
||||||
|
return dockerService.getContainerStatus(containerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/containers/{containerId}/history")
|
||||||
|
public DockerContainerHistoryView getContainerHistory(@PathVariable String containerId, @RequestParam(required = false) String window) {
|
||||||
|
return dockerService.getContainerHistory(containerId, window);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@ package com.imyeyu.api.modules.system.controller;
|
|||||||
|
|
||||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
import com.imyeyu.api.modules.system.service.StatusService;
|
||||||
import com.imyeyu.api.modules.system.service.SystemService;
|
import com.imyeyu.api.modules.system.service.SystemService;
|
||||||
|
import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView;
|
||||||
import com.imyeyu.api.modules.system.vo.TempAttachRequest;
|
import com.imyeyu.api.modules.system.vo.TempAttachRequest;
|
||||||
import com.imyeyu.io.IO;
|
import com.imyeyu.io.IO;
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
@@ -12,6 +14,7 @@ import com.imyeyu.spring.annotation.AOPLog;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -23,10 +26,10 @@ import java.io.IOException;
|
|||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器控制接口
|
* 服务端控制器
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 22:47
|
* @since 2022-01-31 22:47
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@@ -34,7 +37,7 @@ import java.util.concurrent.Semaphore;
|
|||||||
@RequestMapping("/system/server")
|
@RequestMapping("/system/server")
|
||||||
public class SystemController {
|
public class SystemController {
|
||||||
|
|
||||||
private final ServerStatus serverStatus;
|
private final StatusService statusService;
|
||||||
private final SystemService service;
|
private final SystemService service;
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
|
|
||||||
@@ -42,16 +45,36 @@ public class SystemController {
|
|||||||
private final Semaphore rebootSemaphore = new Semaphore(1);
|
private final Semaphore rebootSemaphore = new Semaphore(1);
|
||||||
private final Semaphore restoreSemaphore = new Semaphore(1);
|
private final Semaphore restoreSemaphore = new Semaphore(1);
|
||||||
|
|
||||||
/** @return 实时服务器状态 */
|
/**
|
||||||
@RequestMapping("/status")
|
* 获取系统状态快照
|
||||||
public ServerStatus getStatus() {
|
*
|
||||||
return serverStatus;
|
* @param metrics 返回指标,使用逗号分隔
|
||||||
|
* @return 状态快照
|
||||||
|
*/
|
||||||
|
@GetMapping("/status")
|
||||||
|
public SystemStatusSnapshotView getStatus(@RequestParam(required = false) String metrics) {
|
||||||
|
return statusService.getStatus(metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统状态历史
|
||||||
|
*
|
||||||
|
* @param window 历史窗口,支持 s/m/h/d 后缀
|
||||||
|
* @param metrics 返回指标,使用逗号分隔
|
||||||
|
* @return 状态历史
|
||||||
|
*/
|
||||||
|
@GetMapping("/status/history")
|
||||||
|
public SystemStatusHistoryView getStatusHistory(
|
||||||
|
@RequestParam(required = false) String window,
|
||||||
|
@RequestParam(required = false) String metrics
|
||||||
|
) {
|
||||||
|
return statusService.getStatusHistory(window, metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新系统
|
* 更新系统
|
||||||
*
|
*
|
||||||
* @param file
|
* @param file 更新文件
|
||||||
*/
|
*/
|
||||||
@AOPLog
|
@AOPLog
|
||||||
@PostMapping("/update")
|
@PostMapping("/update")
|
||||||
@@ -85,8 +108,7 @@ public class SystemController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止系统
|
* 关闭系统
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@AOPLog
|
@AOPLog
|
||||||
@RequestMapping("/shutdown")
|
@RequestMapping("/shutdown")
|
||||||
@@ -111,7 +133,11 @@ public class SystemController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 临时接口
|
/**
|
||||||
|
* 上传临时附件
|
||||||
|
*
|
||||||
|
* @param request 上传请求
|
||||||
|
*/
|
||||||
@AOPLog
|
@AOPLog
|
||||||
@PostMapping("/attach")
|
@PostMapping("/attach")
|
||||||
public void uploadAttachment(TempAttachRequest request) {
|
public void uploadAttachment(TempAttachRequest request) {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.imyeyu.api.modules.system.controller;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.service.UpsService;
|
||||||
|
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.ups.UpsStatusView;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 控制器
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/system/ups")
|
||||||
|
public class UpsController {
|
||||||
|
|
||||||
|
private final UpsService upsService;
|
||||||
|
|
||||||
|
@GetMapping("/status")
|
||||||
|
public UpsStatusView getStatus() {
|
||||||
|
return upsService.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/history")
|
||||||
|
public UpsHistoryView getHistory(@RequestParam(required = false) String window) {
|
||||||
|
return upsService.getHistory(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.imyeyu.api.modules.system.service;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerStatusView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerSummaryView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 查询服务
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
public interface DockerService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取容器列表
|
||||||
|
*
|
||||||
|
* @return 容器列表
|
||||||
|
*/
|
||||||
|
List<DockerContainerSummaryView> listContainers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取容器状态
|
||||||
|
*
|
||||||
|
* @param containerId 容器 ID
|
||||||
|
* @return 容器状态
|
||||||
|
*/
|
||||||
|
DockerContainerStatusView getContainerStatus(String containerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取容器历史
|
||||||
|
*
|
||||||
|
* @param containerId 容器 ID
|
||||||
|
* @param window 历史窗口
|
||||||
|
* @return 容器历史
|
||||||
|
*/
|
||||||
|
DockerContainerHistoryView getContainerHistory(String containerId, String window);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.imyeyu.api.modules.system.service;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统状态查询服务
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
public interface StatusService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前状态快照
|
||||||
|
*
|
||||||
|
* @param metrics 返回指标
|
||||||
|
* @return 状态快照
|
||||||
|
*/
|
||||||
|
SystemStatusSnapshotView getStatus(String metrics);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态历史
|
||||||
|
*
|
||||||
|
* @param window 历史窗口
|
||||||
|
* @param metrics 返回指标
|
||||||
|
* @return 状态历史
|
||||||
|
*/
|
||||||
|
SystemStatusHistoryView getStatusHistory(String window, String metrics);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.imyeyu.api.modules.system.service;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.ups.UpsStatusView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 查询服务
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
public interface UpsService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 UPS 当前状态
|
||||||
|
*
|
||||||
|
* @return UPS 状态
|
||||||
|
*/
|
||||||
|
UpsStatusView getStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 UPS 历史
|
||||||
|
*
|
||||||
|
* @param window 历史窗口
|
||||||
|
* @return UPS 历史
|
||||||
|
*/
|
||||||
|
UpsHistoryView getHistory(String window);
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package com.imyeyu.api.modules.system.service.implement;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.bean.DockerStatusStore;
|
||||||
|
import com.imyeyu.api.modules.system.service.DockerService;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryPointView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerStatusView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.docker.DockerContainerSummaryView;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 查询服务实现
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DockerServiceImplement implements DockerService {
|
||||||
|
|
||||||
|
private final DockerStatusStore dockerStatusStore;
|
||||||
|
|
||||||
|
@Value("${docker.engine.collect-rate-ms:10000}")
|
||||||
|
private long collectRateMs;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DockerContainerSummaryView> listContainers() {
|
||||||
|
synchronized (dockerStatusStore) {
|
||||||
|
List<DockerContainerSummaryView> result = new ArrayList<>(dockerStatusStore.getContainers().size());
|
||||||
|
for (DockerStatusStore.Container container : dockerStatusStore.getContainers().values()) {
|
||||||
|
DockerContainerSummaryView item = new DockerContainerSummaryView();
|
||||||
|
item.setId(container.getId());
|
||||||
|
item.setName(container.getName());
|
||||||
|
item.setImage(container.getImage());
|
||||||
|
item.setState(container.getState());
|
||||||
|
item.setStatus(container.getStatus());
|
||||||
|
item.setHealthStatus(container.getHealthStatus());
|
||||||
|
item.setCpuPercent(container.getCpuPercent());
|
||||||
|
item.setMemoryUsageBytes(container.getMemoryUsageBytes());
|
||||||
|
item.setMemoryLimitBytes(container.getMemoryLimitBytes());
|
||||||
|
item.setMemoryPercent(container.getMemoryPercent());
|
||||||
|
item.setNetworkRxBytes(container.getNetworkRxBytes());
|
||||||
|
item.setNetworkTxBytes(container.getNetworkTxBytes());
|
||||||
|
item.setUpdatedAt(container.getUpdatedAt());
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DockerContainerStatusView getContainerStatus(String containerId) {
|
||||||
|
synchronized (dockerStatusStore) {
|
||||||
|
DockerStatusStore.Container container = findContainer(containerId);
|
||||||
|
if (container == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DockerContainerStatusView view = new DockerContainerStatusView();
|
||||||
|
view.setId(container.getId());
|
||||||
|
view.setName(container.getName());
|
||||||
|
view.setImage(container.getImage());
|
||||||
|
view.setImageId(container.getImageId());
|
||||||
|
view.setCreatedAt(container.getCreatedAt());
|
||||||
|
view.setState(container.getState());
|
||||||
|
view.setStatus(container.getStatus());
|
||||||
|
view.setHealthStatus(container.getHealthStatus());
|
||||||
|
view.setStartedAt(container.getStartedAt());
|
||||||
|
view.setFinishedAt(container.getFinishedAt());
|
||||||
|
view.setExitCode(container.getExitCode());
|
||||||
|
view.setRestartCount(container.getRestartCount());
|
||||||
|
view.setOomKilled(container.isOomKilled());
|
||||||
|
view.setCpuPercent(container.getCpuPercent());
|
||||||
|
view.setMemoryUsageBytes(container.getMemoryUsageBytes());
|
||||||
|
view.setMemoryLimitBytes(container.getMemoryLimitBytes());
|
||||||
|
view.setMemoryPercent(container.getMemoryPercent());
|
||||||
|
view.setNetworkRxBytes(container.getNetworkRxBytes());
|
||||||
|
view.setNetworkTxBytes(container.getNetworkTxBytes());
|
||||||
|
view.setBlockReadBytes(container.getBlockReadBytes());
|
||||||
|
view.setBlockWriteBytes(container.getBlockWriteBytes());
|
||||||
|
view.setPids(container.getPids());
|
||||||
|
view.setUpdatedAt(container.getUpdatedAt());
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DockerContainerHistoryView getContainerHistory(String containerId, String window) {
|
||||||
|
synchronized (dockerStatusStore) {
|
||||||
|
DockerStatusStore.Container container = findContainer(containerId);
|
||||||
|
if (container == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DockerContainerHistoryView view = new DockerContainerHistoryView();
|
||||||
|
view.setId(container.getId());
|
||||||
|
view.setName(container.getName());
|
||||||
|
view.setServerTime(Time.now());
|
||||||
|
view.setSampleRateMs(collectRateMs);
|
||||||
|
|
||||||
|
long windowMs = parseWindowMs(window);
|
||||||
|
long threshold = Time.now() - windowMs;
|
||||||
|
for (DockerStatusStore.Point point : container.getHistory()) {
|
||||||
|
if (point.getAt() < threshold) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 == view.getFrom()) {
|
||||||
|
view.setFrom(point.getAt());
|
||||||
|
}
|
||||||
|
view.setTo(point.getAt());
|
||||||
|
DockerContainerHistoryPointView item = new DockerContainerHistoryPointView();
|
||||||
|
item.setAt(point.getAt());
|
||||||
|
item.setCpuPercent(point.getCpuPercent());
|
||||||
|
item.setMemoryUsageBytes(point.getMemoryUsageBytes());
|
||||||
|
item.setMemoryPercent(point.getMemoryPercent());
|
||||||
|
item.setNetworkRxBytes(point.getNetworkRxBytes());
|
||||||
|
item.setNetworkTxBytes(point.getNetworkTxBytes());
|
||||||
|
item.setBlockReadBytes(point.getBlockReadBytes());
|
||||||
|
item.setBlockWriteBytes(point.getBlockWriteBytes());
|
||||||
|
item.setPids(point.getPids());
|
||||||
|
view.getPoints().add(item);
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long parseWindowMs(String window) {
|
||||||
|
if (window == null || window.isBlank()) {
|
||||||
|
return 60L * collectRateMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = window.trim().toLowerCase();
|
||||||
|
char suffix = normalized.charAt(normalized.length() - 1);
|
||||||
|
long unit = switch (suffix) {
|
||||||
|
case 's' -> 1000L;
|
||||||
|
case 'm' -> 60_000L;
|
||||||
|
case 'h' -> 3_600_000L;
|
||||||
|
case 'd' -> 86_400_000L;
|
||||||
|
default -> collectRateMs;
|
||||||
|
};
|
||||||
|
String valueText = Character.isDigit(suffix) ? normalized : normalized.substring(0, normalized.length() - 1);
|
||||||
|
try {
|
||||||
|
return Math.max(collectRateMs, Long.parseLong(valueText) * unit);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 60L * collectRateMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按容器 ID 或前缀查找容器
|
||||||
|
*
|
||||||
|
* @param containerId 容器 ID
|
||||||
|
* @return 容器缓存
|
||||||
|
*/
|
||||||
|
private DockerStatusStore.Container findContainer(String containerId) {
|
||||||
|
DockerStatusStore.Container exact = dockerStatusStore.getContainers().get(containerId);
|
||||||
|
if (exact != null) {
|
||||||
|
return exact;
|
||||||
|
}
|
||||||
|
for (DockerStatusStore.Container container : dockerStatusStore.getContainers().values()) {
|
||||||
|
if (container.getId() != null && container.getId().startsWith(containerId)) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
package com.imyeyu.api.modules.system.service.implement;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
|
import com.imyeyu.api.modules.common.service.SettingService;
|
||||||
|
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||||
|
import com.imyeyu.api.modules.system.service.StatusService;
|
||||||
|
import com.imyeyu.api.modules.system.vo.SystemStatusDataView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统状态查询服务实现
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StatusServiceImplement implements StatusService {
|
||||||
|
|
||||||
|
private final ServerStatus serverStatus;
|
||||||
|
private final SettingService settingService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemStatusSnapshotView getStatus(String metrics) {
|
||||||
|
long serverTime = Time.now();
|
||||||
|
int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE);
|
||||||
|
EnumSet<Metric> selectedMetrics = parseMetrics(metrics);
|
||||||
|
|
||||||
|
synchronized (serverStatus) {
|
||||||
|
SystemStatusSnapshotView view = new SystemStatusSnapshotView();
|
||||||
|
view.setServerTime(serverTime);
|
||||||
|
view.setSampleRateMs(sampleRateMs);
|
||||||
|
view.setSnapshot(buildSnapshot(serverTime, selectedMetrics));
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemStatusHistoryView getStatusHistory(String window, String metrics) {
|
||||||
|
long serverTime = Time.now();
|
||||||
|
int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE);
|
||||||
|
EnumSet<Metric> selectedMetrics = parseMetrics(metrics);
|
||||||
|
|
||||||
|
synchronized (serverStatus) {
|
||||||
|
SystemStatusHistoryView history = buildHistory(window, sampleRateMs, selectedMetrics);
|
||||||
|
history.setServerTime(serverTime);
|
||||||
|
history.setSampleRateMs(sampleRateMs);
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建当前快照
|
||||||
|
*
|
||||||
|
* @param serverTime 服务端时间
|
||||||
|
* @param selectedMetrics 指标集合
|
||||||
|
* @return 当前快照
|
||||||
|
*/
|
||||||
|
private SystemStatusDataView.Snapshot buildSnapshot(long serverTime, EnumSet<Metric> selectedMetrics) {
|
||||||
|
SystemStatusDataView.Snapshot snapshot = new SystemStatusDataView.Snapshot();
|
||||||
|
if (selectedMetrics.contains(Metric.OS)) {
|
||||||
|
SystemStatusDataView.OS os = new SystemStatusDataView.OS();
|
||||||
|
os.setName(serverStatus.getOs().getName());
|
||||||
|
os.setBootAt(serverStatus.getOs().getBootAt());
|
||||||
|
os.setUptimeMs(Math.max(0, serverTime - serverStatus.getOs().getBootAt()));
|
||||||
|
snapshot.setOs(os);
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.CPU)) {
|
||||||
|
SystemStatusDataView.CPU cpu = new SystemStatusDataView.CPU();
|
||||||
|
cpu.setModel(serverStatus.getCpu().getName());
|
||||||
|
cpu.setPhysicalCores(serverStatus.getCpu().getCoreCount());
|
||||||
|
cpu.setLogicalCores(serverStatus.getCpu().getLogicalCount());
|
||||||
|
cpu.setUsagePercent(lastDouble(serverStatus.getCpu().getUsed()));
|
||||||
|
cpu.setSystemPercent(lastDouble(serverStatus.getCpu().getSystem()));
|
||||||
|
cpu.setTemperatureCelsius(serverStatus.getCpu().getTemperature());
|
||||||
|
snapshot.setCpu(cpu);
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.MEMORY)) {
|
||||||
|
SystemStatusDataView.Memory memory = new SystemStatusDataView.Memory();
|
||||||
|
Long usedBytes = lastLong(serverStatus.getMemory().getUsed());
|
||||||
|
Long swapUsedBytes = lastLong(serverStatus.getMemory().getSwapUsed());
|
||||||
|
memory.setTotalBytes(serverStatus.getMemory().getSize());
|
||||||
|
memory.setUsedBytes(usedBytes);
|
||||||
|
memory.setUsagePercent(toPercent(usedBytes, serverStatus.getMemory().getSize()));
|
||||||
|
memory.setSwapTotalBytes(serverStatus.getMemory().getSwapSize());
|
||||||
|
memory.setSwapUsedBytes(swapUsedBytes);
|
||||||
|
snapshot.setMemory(memory);
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.JVM)) {
|
||||||
|
SystemStatusDataView.JVM jvm = new SystemStatusDataView.JVM();
|
||||||
|
SystemStatusDataView.GC gc = new SystemStatusDataView.GC();
|
||||||
|
jvm.setName(serverStatus.getJvm().getName());
|
||||||
|
jvm.setVersion(serverStatus.getJvm().getVersion());
|
||||||
|
jvm.setBootAt(serverStatus.getJvm().getBootAt());
|
||||||
|
jvm.setHeapInitBytes(serverStatus.getJvm().getMemory().getInit());
|
||||||
|
jvm.setHeapMaxBytes(serverStatus.getJvm().getMemory().getMax());
|
||||||
|
jvm.setHeapUsedBytes(lastLong(serverStatus.getJvm().getMemory().getUsed()));
|
||||||
|
jvm.setHeapCommittedBytes(lastLong(serverStatus.getJvm().getMemory().getCommitted()));
|
||||||
|
gc.setCollector(serverStatus.getJvm().getGcName());
|
||||||
|
gc.setCycleCount(serverStatus.getJvm().getGc().getSyncCycles());
|
||||||
|
gc.setPauseCount(serverStatus.getJvm().getGc().getPauses());
|
||||||
|
gc.setLastPauseAt(serverStatus.getJvm().getGc().getLastPauseAt());
|
||||||
|
gc.setLastRecoveredBytes(serverStatus.getJvm().getGc().getLastRecoverySize());
|
||||||
|
jvm.setGc(gc);
|
||||||
|
snapshot.setJvm(jvm);
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.NETWORK)) {
|
||||||
|
SystemStatusDataView.Network network = new SystemStatusDataView.Network();
|
||||||
|
network.setInterfaceName(serverStatus.getNetwork().getName());
|
||||||
|
network.setMac(serverStatus.getNetwork().getMac());
|
||||||
|
network.setRxBytesPerSecond(serverStatus.getNetwork().getRecvNow());
|
||||||
|
network.setTxBytesPerSecond(serverStatus.getNetwork().getSentNow());
|
||||||
|
network.setRxTotalBytes(serverStatus.getNetwork().getRecvTotal());
|
||||||
|
network.setTxTotalBytes(serverStatus.getNetwork().getSentTotal());
|
||||||
|
network.setRxPacketsTotal(serverStatus.getNetwork().getRecvPacketsTotal());
|
||||||
|
network.setTxPacketsTotal(serverStatus.getNetwork().getSentPacketsTotal());
|
||||||
|
network.setInErrors(serverStatus.getNetwork().getInErrors());
|
||||||
|
network.setOutErrors(serverStatus.getNetwork().getOutErrors());
|
||||||
|
network.setInDrops(serverStatus.getNetwork().getInDrops());
|
||||||
|
network.setCollisions(serverStatus.getNetwork().getCollisions());
|
||||||
|
snapshot.setNetwork(network);
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.HARDWARE)) {
|
||||||
|
SystemStatusDataView.Hardware hardware = new SystemStatusDataView.Hardware();
|
||||||
|
SystemStatusDataView.Baseboard baseboard = new SystemStatusDataView.Baseboard();
|
||||||
|
SystemStatusDataView.Firmware firmware = new SystemStatusDataView.Firmware();
|
||||||
|
hardware.setFanSpeeds(new ArrayList<>(serverStatus.getHardware().getFanSpeeds()));
|
||||||
|
baseboard.setManufacturer(serverStatus.getHardware().getBaseboard().getManufacturer());
|
||||||
|
baseboard.setModel(serverStatus.getHardware().getBaseboard().getModel());
|
||||||
|
baseboard.setVersion(serverStatus.getHardware().getBaseboard().getVersion());
|
||||||
|
baseboard.setSerialNumber(serverStatus.getHardware().getBaseboard().getSerialNumber());
|
||||||
|
firmware.setManufacturer(serverStatus.getHardware().getFirmware().getManufacturer());
|
||||||
|
firmware.setName(serverStatus.getHardware().getFirmware().getName());
|
||||||
|
firmware.setDescription(serverStatus.getHardware().getFirmware().getDescription());
|
||||||
|
firmware.setVersion(serverStatus.getHardware().getFirmware().getVersion());
|
||||||
|
firmware.setReleaseDate(serverStatus.getHardware().getFirmware().getReleaseDate());
|
||||||
|
hardware.setBaseboard(baseboard);
|
||||||
|
hardware.setFirmware(firmware);
|
||||||
|
snapshot.setHardware(hardware);
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.STORAGE)) {
|
||||||
|
List<SystemStatusDataView.StoragePartition> storagePartitions = new ArrayList<>();
|
||||||
|
for (ServerStatus.StoragePartition partition : serverStatus.getStoragePartitions()) {
|
||||||
|
SystemStatusDataView.StoragePartition item = new SystemStatusDataView.StoragePartition();
|
||||||
|
item.setDiskName(partition.getDiskName());
|
||||||
|
item.setDiskModel(partition.getDiskModel());
|
||||||
|
item.setDiskSerial(partition.getDiskSerial());
|
||||||
|
item.setPartitionId(partition.getPartitionId());
|
||||||
|
item.setPartitionName(partition.getPartitionName());
|
||||||
|
item.setPartitionType(partition.getPartitionType());
|
||||||
|
item.setUuid(partition.getUuid());
|
||||||
|
item.setMountPoint(partition.getMountPoint());
|
||||||
|
item.setTotalBytes(partition.getTotalBytes());
|
||||||
|
item.setUsedBytes(partition.getUsedBytes());
|
||||||
|
item.setUsagePercent(toPercent(partition.getUsedBytes(), partition.getTotalBytes()));
|
||||||
|
item.setTransferTimeMs(partition.getTransferTimeMs());
|
||||||
|
storagePartitions.add(item);
|
||||||
|
}
|
||||||
|
snapshot.setStoragePartitions(storagePartitions);
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建历史数据
|
||||||
|
*
|
||||||
|
* @param window 历史窗口
|
||||||
|
* @param sampleRateMs 采样周期
|
||||||
|
* @param selectedMetrics 指标集合
|
||||||
|
* @return 历史数据
|
||||||
|
*/
|
||||||
|
private SystemStatusHistoryView buildHistory(String window, int sampleRateMs, EnumSet<Metric> selectedMetrics) {
|
||||||
|
SystemStatusHistoryView view = new SystemStatusHistoryView();
|
||||||
|
List<Long> axis = copyLongs(serverStatus.getUpdateAxis());
|
||||||
|
if (axis.isEmpty()) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndex = resolveStartIndex(axis, parseWindowMs(window, sampleRateMs));
|
||||||
|
view.setFrom(axis.get(startIndex));
|
||||||
|
view.setTo(axis.get(axis.size() - 1));
|
||||||
|
|
||||||
|
List<Double> cpuUsed = selectedMetrics.contains(Metric.CPU) ? copyDoubles(serverStatus.getCpu().getUsed()) : List.of();
|
||||||
|
List<Double> cpuSystem = selectedMetrics.contains(Metric.CPU) ? copyDoubles(serverStatus.getCpu().getSystem()) : List.of();
|
||||||
|
List<Long> memoryUsed = selectedMetrics.contains(Metric.MEMORY) ? copyLongs(serverStatus.getMemory().getUsed()) : List.of();
|
||||||
|
List<Long> swapUsed = selectedMetrics.contains(Metric.MEMORY) ? copyLongs(serverStatus.getMemory().getSwapUsed()) : List.of();
|
||||||
|
List<Long> heapUsed = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getMemory().getUsed()) : List.of();
|
||||||
|
List<Long> heapCommitted = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getMemory().getCommitted()) : List.of();
|
||||||
|
List<Long> gcCycleTime = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getGc().getSyncCyclesTime()) : List.of();
|
||||||
|
List<Long> gcPauseTime = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getGc().getPausesTime()) : List.of();
|
||||||
|
List<Long> rx = selectedMetrics.contains(Metric.NETWORK) ? toRate(copyLongs(serverStatus.getNetwork().getRecv()), sampleRateMs) : List.of();
|
||||||
|
List<Long> tx = selectedMetrics.contains(Metric.NETWORK) ? toRate(copyLongs(serverStatus.getNetwork().getSent()), sampleRateMs) : List.of();
|
||||||
|
|
||||||
|
for (int index = startIndex; index < axis.size(); index++) {
|
||||||
|
SystemStatusDataView.Point point = new SystemStatusDataView.Point();
|
||||||
|
point.setAt(axis.get(index));
|
||||||
|
if (selectedMetrics.contains(Metric.CPU)) {
|
||||||
|
point.setCpuUsagePercent(getAlignedValue(cpuUsed, axis.size(), index));
|
||||||
|
point.setCpuSystemPercent(getAlignedValue(cpuSystem, axis.size(), index));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.MEMORY)) {
|
||||||
|
point.setMemoryUsedBytes(getAlignedValue(memoryUsed, axis.size(), index));
|
||||||
|
point.setSwapUsedBytes(getAlignedValue(swapUsed, axis.size(), index));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.JVM)) {
|
||||||
|
point.setHeapUsedBytes(getAlignedValue(heapUsed, axis.size(), index));
|
||||||
|
point.setHeapCommittedBytes(getAlignedValue(heapCommitted, axis.size(), index));
|
||||||
|
point.setGcCycleTimeMs(getAlignedValue(gcCycleTime, axis.size(), index));
|
||||||
|
point.setGcPauseTimeMs(getAlignedValue(gcPauseTime, axis.size(), index));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.contains(Metric.NETWORK)) {
|
||||||
|
point.setRxBytesPerSecond(getAlignedValue(rx, axis.size(), index));
|
||||||
|
point.setTxBytesPerSecond(getAlignedValue(tx, axis.size(), index));
|
||||||
|
}
|
||||||
|
view.getPoints().add(point);
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析指标
|
||||||
|
*
|
||||||
|
* @param metrics 指标字符串
|
||||||
|
* @return 指标集合
|
||||||
|
*/
|
||||||
|
private EnumSet<Metric> parseMetrics(String metrics) {
|
||||||
|
if (metrics == null || metrics.isBlank()) {
|
||||||
|
return EnumSet.allOf(Metric.class);
|
||||||
|
}
|
||||||
|
EnumSet<Metric> selected = EnumSet.noneOf(Metric.class);
|
||||||
|
for (String metric : metrics.split(",")) {
|
||||||
|
switch (metric.trim().toLowerCase(Locale.ROOT)) {
|
||||||
|
case "os" -> selected.add(Metric.OS);
|
||||||
|
case "cpu" -> selected.add(Metric.CPU);
|
||||||
|
case "memory" -> selected.add(Metric.MEMORY);
|
||||||
|
case "jvm", "gc" -> selected.add(Metric.JVM);
|
||||||
|
case "network" -> selected.add(Metric.NETWORK);
|
||||||
|
case "storage", "disk", "disks" -> selected.add(Metric.STORAGE);
|
||||||
|
case "hardware", "board", "bios", "fan", "fans" -> selected.add(Metric.HARDWARE);
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected.isEmpty() ? EnumSet.allOf(Metric.class) : selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析窗口毫秒数
|
||||||
|
*
|
||||||
|
* @param window 窗口字符串
|
||||||
|
* @param sampleRateMs 采样周期
|
||||||
|
* @return 窗口毫秒数
|
||||||
|
*/
|
||||||
|
private long parseWindowMs(String window, int sampleRateMs) {
|
||||||
|
if (window == null || window.isBlank()) {
|
||||||
|
return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = window.trim().toLowerCase(Locale.ROOT);
|
||||||
|
char suffix = normalized.charAt(normalized.length() - 1);
|
||||||
|
long unit = switch (suffix) {
|
||||||
|
case 's' -> 1000L;
|
||||||
|
case 'm' -> 60_000L;
|
||||||
|
case 'h' -> 3_600_000L;
|
||||||
|
case 'd' -> 86_400_000L;
|
||||||
|
default -> sampleRateMs;
|
||||||
|
};
|
||||||
|
String valueText = Character.isDigit(suffix) ? normalized : normalized.substring(0, normalized.length() - 1);
|
||||||
|
try {
|
||||||
|
return Math.max(sampleRateMs, Long.parseLong(valueText) * unit);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析历史起点
|
||||||
|
*
|
||||||
|
* @param axis 时间轴
|
||||||
|
* @param windowMs 窗口毫秒数
|
||||||
|
* @return 起点下标
|
||||||
|
*/
|
||||||
|
private int resolveStartIndex(List<Long> axis, long windowMs) {
|
||||||
|
long threshold = axis.get(axis.size() - 1) - windowMs;
|
||||||
|
for (int index = 0; index < axis.size(); index++) {
|
||||||
|
if (threshold <= axis.get(index)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return axis.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将周期累计值转换为每秒速率
|
||||||
|
*
|
||||||
|
* @param source 原始列表
|
||||||
|
* @param sampleRateMs 采样周期
|
||||||
|
* @return 速率列表
|
||||||
|
*/
|
||||||
|
private List<Long> toRate(List<Long> source, int sampleRateMs) {
|
||||||
|
List<Long> result = new ArrayList<>(source.size());
|
||||||
|
for (Long value : source) {
|
||||||
|
result.add(value == null ? null : value * 1000 / sampleRateMs);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取与时间轴尾部对齐的值
|
||||||
|
*
|
||||||
|
* @param values 数据列表
|
||||||
|
* @param axisSize 时间轴长度
|
||||||
|
* @param axisIndex 时间轴下标
|
||||||
|
* @param <T> 数据类型
|
||||||
|
* @return 对齐后的值
|
||||||
|
*/
|
||||||
|
private <T> T getAlignedValue(List<T> values, int axisSize, int axisIndex) {
|
||||||
|
int valueIndex = axisIndex - (axisSize - values.size());
|
||||||
|
if (valueIndex < 0 || values.size() <= valueIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return values.get(valueIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制长整型列表
|
||||||
|
*
|
||||||
|
* @param source 原始队列
|
||||||
|
* @return 列表
|
||||||
|
*/
|
||||||
|
private List<Long> copyLongs(Deque<Number> source) {
|
||||||
|
List<Long> result = new ArrayList<>(source.size());
|
||||||
|
for (Number number : source) {
|
||||||
|
result.add(number == null ? null : number.longValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制浮点列表
|
||||||
|
*
|
||||||
|
* @param source 原始队列
|
||||||
|
* @return 列表
|
||||||
|
*/
|
||||||
|
private List<Double> copyDoubles(Deque<Number> source) {
|
||||||
|
List<Double> result = new ArrayList<>(source.size());
|
||||||
|
for (Number number : source) {
|
||||||
|
result.add(number == null ? null : number.doubleValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最后一个长整型值
|
||||||
|
*
|
||||||
|
* @param source 队列
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
private Long lastLong(Deque<Number> source) {
|
||||||
|
Number number = source.peekLast();
|
||||||
|
return number == null ? null : number.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最后一个浮点值
|
||||||
|
*
|
||||||
|
* @param source 队列
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
private Double lastDouble(Deque<Number> source) {
|
||||||
|
Number number = source.peekLast();
|
||||||
|
return number == null ? null : number.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算百分比
|
||||||
|
*
|
||||||
|
* @param used 已用值
|
||||||
|
* @param total 总值
|
||||||
|
* @return 百分比
|
||||||
|
*/
|
||||||
|
private Double toPercent(Long used, long total) {
|
||||||
|
if (used == null || total <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return used * 100D / total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态指标
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
private enum Metric {
|
||||||
|
OS,
|
||||||
|
CPU,
|
||||||
|
MEMORY,
|
||||||
|
JVM,
|
||||||
|
NETWORK,
|
||||||
|
HARDWARE,
|
||||||
|
STORAGE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package com.imyeyu.api.modules.system.service.implement;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.bean.UpsStatusStore;
|
||||||
|
import com.imyeyu.api.modules.system.service.UpsService;
|
||||||
|
import com.imyeyu.api.modules.system.task.UpsStatusTask;
|
||||||
|
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryPointView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryView;
|
||||||
|
import com.imyeyu.api.modules.system.vo.ups.UpsStatusView;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 查询服务实现
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpsServiceImplement implements UpsService {
|
||||||
|
|
||||||
|
private final UpsStatusTask upsStatusTask;
|
||||||
|
private final UpsStatusStore upsStatusStore;
|
||||||
|
|
||||||
|
@Value("${ups.collect-rate-ms:60000}")
|
||||||
|
private long collectRateMs;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UpsStatusView getStatus() {
|
||||||
|
ensureCurrentSnapshot();
|
||||||
|
synchronized (upsStatusStore) {
|
||||||
|
UpsStatusStore.Snapshot snapshot = upsStatusStore.getCurrent();
|
||||||
|
if (snapshot == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
UpsStatusView view = new UpsStatusView();
|
||||||
|
view.setServerTime(System.currentTimeMillis());
|
||||||
|
view.setUpsTime(snapshot.getUpsTime());
|
||||||
|
view.setHostName(snapshot.getHostName());
|
||||||
|
view.setCustomer(snapshot.getCustomer());
|
||||||
|
view.setVersion(snapshot.getVersion());
|
||||||
|
view.setDeviceId(snapshot.getDeviceId());
|
||||||
|
view.setUpsType(snapshot.getUpsType());
|
||||||
|
view.setMorphological(snapshot.getMorphological());
|
||||||
|
view.setIoPhase(snapshot.getIoPhase());
|
||||||
|
view.setWorkMode(snapshot.getWorkMode());
|
||||||
|
view.setInputVoltage(snapshot.getInputVoltage());
|
||||||
|
view.setInputFrequency(snapshot.getInputFrequency());
|
||||||
|
view.setOutputVoltage(snapshot.getOutputVoltage());
|
||||||
|
view.setOutputFrequency(snapshot.getOutputFrequency());
|
||||||
|
view.setOutputLoadPercent(snapshot.getOutputLoadPercent());
|
||||||
|
view.setBatteryVoltage(snapshot.getBatteryVoltage());
|
||||||
|
view.setBatteryCapacity(snapshot.getBatteryCapacity());
|
||||||
|
view.setBatteryRemainTime(snapshot.getBatteryRemainTime());
|
||||||
|
view.setTemperature(snapshot.getTemperature());
|
||||||
|
view.setBypassActive(snapshot.isBypassActive());
|
||||||
|
view.setShutdownActive(snapshot.isShutdownActive());
|
||||||
|
view.setOutputOn(snapshot.isOutputOn());
|
||||||
|
view.setCharging(snapshot.isCharging());
|
||||||
|
view.setFaultType(snapshot.getFaultType());
|
||||||
|
view.setFaultKind(snapshot.getFaultKind());
|
||||||
|
view.setWarnings(snapshot.getWarnings());
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UpsHistoryView getHistory(String window) {
|
||||||
|
ensureCurrentSnapshot();
|
||||||
|
UpsHistoryView view = new UpsHistoryView();
|
||||||
|
view.setServerTime(System.currentTimeMillis());
|
||||||
|
view.setSampleRateMs(collectRateMs);
|
||||||
|
|
||||||
|
long windowMs = parseWindowMs(window);
|
||||||
|
long threshold = view.getServerTime() - windowMs;
|
||||||
|
|
||||||
|
synchronized (upsStatusStore) {
|
||||||
|
for (UpsStatusStore.Point point : upsStatusStore.getHistory()) {
|
||||||
|
if (point.getAt() < threshold) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 == view.getFrom()) {
|
||||||
|
view.setFrom(point.getAt());
|
||||||
|
}
|
||||||
|
view.setTo(point.getAt());
|
||||||
|
UpsHistoryPointView item = new UpsHistoryPointView();
|
||||||
|
item.setAt(point.getAt());
|
||||||
|
item.setUpsTime(point.getUpsTime());
|
||||||
|
item.setWorkMode(point.getWorkMode());
|
||||||
|
item.setInputVoltage(point.getInputVoltage());
|
||||||
|
item.setInputFrequency(point.getInputFrequency());
|
||||||
|
item.setOutputVoltage(point.getOutputVoltage());
|
||||||
|
item.setOutputFrequency(point.getOutputFrequency());
|
||||||
|
item.setOutputLoadPercent(point.getOutputLoadPercent());
|
||||||
|
item.setBatteryVoltage(point.getBatteryVoltage());
|
||||||
|
item.setBatteryCapacity(point.getBatteryCapacity());
|
||||||
|
item.setBatteryRemainTime(point.getBatteryRemainTime());
|
||||||
|
item.setTemperature(point.getTemperature());
|
||||||
|
item.setBypassActive(point.isBypassActive());
|
||||||
|
item.setShutdownActive(point.isShutdownActive());
|
||||||
|
item.setOutputOn(point.isOutputOn());
|
||||||
|
view.getPoints().add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保证当前快照存在
|
||||||
|
*/
|
||||||
|
private void ensureCurrentSnapshot() {
|
||||||
|
synchronized (upsStatusStore) {
|
||||||
|
if (upsStatusStore.getCurrent() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
upsStatusTask.collectOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析历史窗口
|
||||||
|
*
|
||||||
|
* @param window 历史窗口
|
||||||
|
* @return 窗口毫秒数
|
||||||
|
*/
|
||||||
|
private long parseWindowMs(String window) {
|
||||||
|
long defaultWindowMs = 24L * 60 * 60 * 1000;
|
||||||
|
if (window == null || window.isBlank()) {
|
||||||
|
return defaultWindowMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = window.trim().toLowerCase();
|
||||||
|
char suffix = normalized.charAt(normalized.length() - 1);
|
||||||
|
long unit = switch (suffix) {
|
||||||
|
case 's' -> 1000L;
|
||||||
|
case 'm' -> 60_000L;
|
||||||
|
case 'h' -> 3_600_000L;
|
||||||
|
case 'd' -> 86_400_000L;
|
||||||
|
default -> collectRateMs;
|
||||||
|
};
|
||||||
|
String valueText = Character.isDigit(suffix) ? normalized : normalized.substring(0, normalized.length() - 1);
|
||||||
|
try {
|
||||||
|
long parsed = Long.parseLong(valueText) * unit;
|
||||||
|
long maxWindowMs = UpsStatusTask.MAX_HISTORY_MS;
|
||||||
|
return Math.max(collectRateMs, Math.min(parsed, maxWindowMs));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultWindowMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.imyeyu.api.modules.system.bean.DockerStatusStore;
|
||||||
|
import com.imyeyu.api.modules.system.util.DockerEngineClient;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 状态采集任务
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DockerStatusTask implements SchedulingConfigurer {
|
||||||
|
|
||||||
|
private final DockerEngineClient dockerEngineClient;
|
||||||
|
private final DockerStatusStore dockerStatusStore;
|
||||||
|
|
||||||
|
@Value("${docker.engine.collect-enabled:true}")
|
||||||
|
private boolean collectEnabled;
|
||||||
|
|
||||||
|
@Value("${docker.engine.collect-rate-ms:10000}")
|
||||||
|
private long collectRateMs;
|
||||||
|
|
||||||
|
@Value("${docker.engine.history-limit:120}")
|
||||||
|
private int historyLimit;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
||||||
|
if (!collectEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PeriodicTrigger trigger = new PeriodicTrigger(collectRateMs, TimeUnit.MILLISECONDS);
|
||||||
|
trigger.setInitialDelay(0);
|
||||||
|
taskRegistrar.addTriggerTask(this::collect, trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collect() {
|
||||||
|
try {
|
||||||
|
JsonArray containers = dockerEngineClient.getJson("/containers/json", DockerEngineClient.query("all", "true")).getAsJsonArray();
|
||||||
|
long now = Time.now();
|
||||||
|
synchronized (dockerStatusStore) {
|
||||||
|
Set<String> activeIds = new HashSet<>();
|
||||||
|
for (JsonElement item : containers) {
|
||||||
|
try {
|
||||||
|
JsonObject summary = item.getAsJsonObject();
|
||||||
|
String containerId = getAsString(summary, "Id");
|
||||||
|
activeIds.add(containerId);
|
||||||
|
DockerStatusStore.Container container = dockerStatusStore.getContainers().computeIfAbsent(containerId, key -> new DockerStatusStore.Container());
|
||||||
|
updateContainerSummary(container, summary);
|
||||||
|
updateContainerInspect(containerId, container);
|
||||||
|
updateContainerStats(containerId, container, now);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("collect docker container item error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dockerStatusStore.getContainers().entrySet().removeIf(item -> !activeIds.contains(item.getKey()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("collect docker container status error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContainerSummary(DockerStatusStore.Container container, JsonObject summary) {
|
||||||
|
container.setId(getAsString(summary, "Id"));
|
||||||
|
container.setName(trimContainerName(readFirstArrayText(summary, "Names")));
|
||||||
|
container.setImage(getAsString(summary, "Image"));
|
||||||
|
container.setImageId(getAsString(summary, "ImageID"));
|
||||||
|
container.setCreatedAt(getAsLong(summary, "Created") * 1000);
|
||||||
|
container.setState(getAsString(summary, "State"));
|
||||||
|
container.setStatus(getAsString(summary, "Status"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContainerInspect(String containerId, DockerStatusStore.Container container) {
|
||||||
|
JsonObject inspect = dockerEngineClient.getJson("/containers/%s/json".formatted(containerId), Map.of()).getAsJsonObject();
|
||||||
|
JsonObject state = getAsObject(inspect, "State");
|
||||||
|
container.setStartedAt(getAsString(state, "StartedAt"));
|
||||||
|
container.setFinishedAt(getAsString(state, "FinishedAt"));
|
||||||
|
container.setExitCode(getAsInteger(state, "ExitCode"));
|
||||||
|
container.setRestartCount(getAsInteger(inspect, "RestartCount", 0));
|
||||||
|
container.setOomKilled(getAsBoolean(state, "OOMKilled"));
|
||||||
|
JsonObject health = getAsObject(state, "Health");
|
||||||
|
container.setHealthStatus(health == null ? null : getAsString(health, "Status"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContainerStats(String containerId, DockerStatusStore.Container container, long now) {
|
||||||
|
JsonObject stats = dockerEngineClient.getJson("/containers/%s/stats".formatted(containerId), DockerEngineClient.query("stream", "false")).getAsJsonObject();
|
||||||
|
Double cpuPercent = calculateCpuPercent(stats);
|
||||||
|
Long memoryUsageBytes = getNestedLong(stats, "memory_stats", "usage");
|
||||||
|
Long memoryLimitBytes = getNestedLong(stats, "memory_stats", "limit");
|
||||||
|
Double memoryPercent = null;
|
||||||
|
if (memoryUsageBytes != null && memoryLimitBytes != null && 0 < memoryLimitBytes) {
|
||||||
|
memoryPercent = memoryUsageBytes * 100D / memoryLimitBytes;
|
||||||
|
}
|
||||||
|
Long networkRxBytes = 0L;
|
||||||
|
Long networkTxBytes = 0L;
|
||||||
|
JsonObject networks = getAsObject(stats, "networks");
|
||||||
|
if (networks != null) {
|
||||||
|
for (Map.Entry<String, JsonElement> item : networks.entrySet()) {
|
||||||
|
JsonObject network = item.getValue().getAsJsonObject();
|
||||||
|
networkRxBytes += getAsLong(network, "rx_bytes", 0L);
|
||||||
|
networkTxBytes += getAsLong(network, "tx_bytes", 0L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Long blockReadBytes = 0L;
|
||||||
|
Long blockWriteBytes = 0L;
|
||||||
|
JsonObject blkioStats = getAsObject(stats, "blkio_stats");
|
||||||
|
JsonArray ioServiceBytes = blkioStats == null ? null : blkioStats.getAsJsonArray("io_service_bytes_recursive");
|
||||||
|
if (ioServiceBytes != null) {
|
||||||
|
for (JsonElement item : ioServiceBytes) {
|
||||||
|
JsonObject io = item.getAsJsonObject();
|
||||||
|
String op = getAsString(io, "op");
|
||||||
|
long value = getAsLong(io, "value", 0L);
|
||||||
|
if ("Read".equalsIgnoreCase(op)) {
|
||||||
|
blockReadBytes += value;
|
||||||
|
} else if ("Write".equalsIgnoreCase(op)) {
|
||||||
|
blockWriteBytes += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Integer pids = getNestedInteger(stats, "pids_stats", "current");
|
||||||
|
|
||||||
|
container.setCpuPercent(cpuPercent);
|
||||||
|
container.setMemoryUsageBytes(memoryUsageBytes);
|
||||||
|
container.setMemoryLimitBytes(memoryLimitBytes);
|
||||||
|
container.setMemoryPercent(memoryPercent);
|
||||||
|
container.setNetworkRxBytes(networkRxBytes);
|
||||||
|
container.setNetworkTxBytes(networkTxBytes);
|
||||||
|
container.setBlockReadBytes(blockReadBytes);
|
||||||
|
container.setBlockWriteBytes(blockWriteBytes);
|
||||||
|
container.setPids(pids);
|
||||||
|
container.setUpdatedAt(now);
|
||||||
|
|
||||||
|
DockerStatusStore.Point point = new DockerStatusStore.Point();
|
||||||
|
point.setAt(now);
|
||||||
|
point.setCpuPercent(cpuPercent);
|
||||||
|
point.setMemoryUsageBytes(memoryUsageBytes);
|
||||||
|
point.setMemoryPercent(memoryPercent);
|
||||||
|
point.setNetworkRxBytes(networkRxBytes);
|
||||||
|
point.setNetworkTxBytes(networkTxBytes);
|
||||||
|
point.setBlockReadBytes(blockReadBytes);
|
||||||
|
point.setBlockWriteBytes(blockWriteBytes);
|
||||||
|
point.setPids(pids);
|
||||||
|
container.getHistory().addLast(point);
|
||||||
|
while (historyLimit < container.getHistory().size()) {
|
||||||
|
container.getHistory().pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double calculateCpuPercent(JsonObject stats) {
|
||||||
|
Long cpuTotal = getNestedLong(stats, "cpu_stats", "cpu_usage", "total_usage");
|
||||||
|
Long preCpuTotal = getNestedLong(stats, "precpu_stats", "cpu_usage", "total_usage");
|
||||||
|
Long systemTotal = getNestedLong(stats, "cpu_stats", "system_cpu_usage");
|
||||||
|
Long preSystemTotal = getNestedLong(stats, "precpu_stats", "system_cpu_usage");
|
||||||
|
Integer onlineCpus = getNestedInteger(stats, "cpu_stats", "online_cpus");
|
||||||
|
if (onlineCpus == null || onlineCpus <= 0) {
|
||||||
|
JsonArray perCpuUsage = getNestedArray(stats, "cpu_stats", "cpu_usage", "percpu_usage");
|
||||||
|
onlineCpus = perCpuUsage == null ? 1 : perCpuUsage.size();
|
||||||
|
}
|
||||||
|
if (cpuTotal == null || preCpuTotal == null || systemTotal == null || preSystemTotal == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long cpuDelta = cpuTotal - preCpuTotal;
|
||||||
|
long systemDelta = systemTotal - preSystemTotal;
|
||||||
|
if (cpuDelta <= 0 || systemDelta <= 0) {
|
||||||
|
return 0D;
|
||||||
|
}
|
||||||
|
return cpuDelta * 100D * onlineCpus / systemDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonObject getAsObject(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return source.getAsJsonObject(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonArray getNestedArray(JsonObject source, String... keys) {
|
||||||
|
JsonElement current = source;
|
||||||
|
for (String key : keys) {
|
||||||
|
if (current == null || !current.isJsonObject() || !current.getAsJsonObject().has(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
current = current.getAsJsonObject().get(key);
|
||||||
|
}
|
||||||
|
return current != null && current.isJsonArray() ? current.getAsJsonArray() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getNestedLong(JsonObject source, String... keys) {
|
||||||
|
JsonElement current = source;
|
||||||
|
for (String key : keys) {
|
||||||
|
if (current == null || !current.isJsonObject() || !current.getAsJsonObject().has(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
current = current.getAsJsonObject().get(key);
|
||||||
|
}
|
||||||
|
return current == null || current.isJsonNull() ? null : current.getAsLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getNestedInteger(JsonObject source, String... keys) {
|
||||||
|
Long value = getNestedLong(source, keys);
|
||||||
|
return value == null ? null : value.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readFirstArrayText(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || !source.get(key).isJsonArray() || source.getAsJsonArray(key).isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return source.getAsJsonArray(key).get(0).getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAsString(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return source.get(key).getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getAsLong(JsonObject source, String key) {
|
||||||
|
return getAsLong(source, key, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getAsLong(JsonObject source, String key, long defaultValue) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return source.get(key).getAsLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getAsInteger(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return source.get(key).getAsInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getAsInteger(JsonObject source, String key, int defaultValue) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return source.get(key).getAsInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean getAsBoolean(JsonObject source, String key) {
|
||||||
|
return source != null && source.has(key) && !source.get(key).isJsonNull() && source.get(key).getAsBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimContainerName(String name) {
|
||||||
|
if (name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return name.startsWith("/") ? name.substring(1) : name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,235 +1,72 @@
|
|||||||
package com.imyeyu.api.modules.system.task;
|
package com.imyeyu.api.modules.system.task;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import com.imyeyu.utils.OS;
|
|
||||||
import com.imyeyu.utils.Time;
|
|
||||||
import com.imyeyu.java.TimiJava;
|
|
||||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
import com.imyeyu.api.modules.common.service.SettingService;
|
import com.imyeyu.api.modules.common.service.SettingService;
|
||||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
import org.springframework.scheduling.support.CronTrigger;
|
import org.springframework.scheduling.support.CronTrigger;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import oshi.SystemInfo;
|
import oshi.SystemInfo;
|
||||||
import oshi.hardware.CentralProcessor;
|
|
||||||
import oshi.hardware.GlobalMemory;
|
|
||||||
import oshi.hardware.HardwareAbstractionLayer;
|
import oshi.hardware.HardwareAbstractionLayer;
|
||||||
import oshi.hardware.NetworkIF;
|
|
||||||
import oshi.software.os.OSFileStore;
|
|
||||||
import oshi.software.os.OperatingSystem;
|
|
||||||
|
|
||||||
import java.lang.management.GarbageCollectorMXBean;
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.MemoryMXBean;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器状态收集任务
|
* 服务端状态采集任务
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:18
|
* @since 2022-01-31 15:18
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ServerStatusTask implements SchedulingConfigurer, TimiJava {
|
public class ServerStatusTask implements SchedulingConfigurer {
|
||||||
|
|
||||||
private final ServerStatus status;
|
private final ServerStatus status;
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
|
private final List<StatusCollector> statusCollectors;
|
||||||
private GlobalMemory globalMemory; // 内存
|
|
||||||
private MemoryMXBean jvmMemory; // JVM 内存
|
|
||||||
private OperatingSystem os; // 操作系统
|
|
||||||
private CentralProcessor processor; // 中央处理器
|
|
||||||
private HardwareAbstractionLayer hardware; // 硬件
|
|
||||||
|
|
||||||
private long[] lastCPUTicks; // CPU 上一时刻状态
|
|
||||||
private long lastLinuxAPIMemoryUsed; // 上一周期 JVM 内存大小
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
||||||
lastLinuxAPIMemoryUsed = -1;
|
SystemInfo systemInfo = new SystemInfo();
|
||||||
|
HardwareAbstractionLayer hardware = systemInfo.getHardware();
|
||||||
|
|
||||||
// 系统信息
|
StatusCollectContext context = new StatusCollectContext(
|
||||||
SystemInfo system = new SystemInfo();
|
status,
|
||||||
|
hardware.getMemory(),
|
||||||
// 硬件信息
|
ManagementFactory.getMemoryMXBean(),
|
||||||
hardware = system.getHardware();
|
settingService,
|
||||||
|
hardware.getComputerSystem(),
|
||||||
// 操作系统
|
systemInfo.getOperatingSystem(),
|
||||||
os = system.getOperatingSystem();
|
hardware.getProcessor(),
|
||||||
|
hardware,
|
||||||
// ---------- 静态数据 ----------
|
Time.now()
|
||||||
|
);
|
||||||
// 系统
|
synchronized (status) {
|
||||||
status.getOs().setName(OS.NAME);
|
for (StatusCollector collector : statusCollectors) {
|
||||||
status.getOs().setBootAt(os.getSystemBootTime() * 1000);
|
collector.initialize(context);
|
||||||
|
|
||||||
// JVM
|
|
||||||
jvmMemory = ManagementFactory.getMemoryMXBean();
|
|
||||||
status.getJvm().setBootAt(Time.now());
|
|
||||||
status.getJvm().setName(System.getProperty("java.vm.name"));
|
|
||||||
status.getJvm().setVersion(System.getProperty("java.version"));
|
|
||||||
|
|
||||||
// CPU
|
|
||||||
processor = hardware.getProcessor();
|
|
||||||
status.getCpu().setName(processor.getProcessorIdentifier().getName().trim());
|
|
||||||
status.getCpu().setCoreCount(processor.getPhysicalProcessorCount());
|
|
||||||
status.getCpu().setLogicalCount(processor.getLogicalProcessorCount());
|
|
||||||
|
|
||||||
// 内存
|
|
||||||
globalMemory = hardware.getMemory();
|
|
||||||
status.getMemory().setSize(globalMemory.getTotal());
|
|
||||||
status.getMemory().setSwapSize(globalMemory.getVirtualMemory().getSwapTotal());
|
|
||||||
|
|
||||||
// 网卡
|
|
||||||
List<NetworkIF> networkIFs = hardware.getNetworkIFs();
|
|
||||||
{
|
|
||||||
NetworkIF networkIF;
|
|
||||||
boolean isFound = false;
|
|
||||||
for (int i = 0; i < networkIFs.size(); i++) {
|
|
||||||
networkIF = networkIFs.get(i);
|
|
||||||
if (networkIF.getMacaddr().equals(settingService.getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC))) {
|
|
||||||
status.getNetwork().setMac(networkIF.getMacaddr());
|
|
||||||
|
|
||||||
status.getNetwork().setRecvTotal(networkIF.getBytesRecv());
|
|
||||||
status.getNetwork().setSentTotal(networkIF.getBytesSent());
|
|
||||||
|
|
||||||
isFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isFound) {
|
|
||||||
log.error("not found setting networkIF MAC: %s" + settingService.getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC));
|
|
||||||
for (int i = 0; i < networkIFs.size(); i++) {
|
|
||||||
log.info("Network Interface: {} -> {}", networkIFs.get(i).getMacaddr(), networkIFs.get(i).getDisplayName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
taskRegistrar.addTriggerTask(() -> {
|
taskRegistrar.addTriggerTask(() -> {
|
||||||
long now = Time.now();
|
synchronized (status) {
|
||||||
|
context.setCollectAt(Time.now());
|
||||||
// ---------- JVM 内存 ----------
|
for (StatusCollector collector : statusCollectors) {
|
||||||
long nowLinuxAPIMemoryUsed = jvmMemory.getHeapMemoryUsage().getUsed();
|
collector.collect(context);
|
||||||
long linuxAPIMemoryDiff = Math.abs(nowLinuxAPIMemoryUsed - lastLinuxAPIMemoryUsed);
|
|
||||||
lastLinuxAPIMemoryUsed = nowLinuxAPIMemoryUsed;
|
|
||||||
status.getJvm().getMemory().setInit(jvmMemory.getHeapMemoryUsage().getInit());
|
|
||||||
status.getJvm().getMemory().setMax(jvmMemory.getHeapMemoryUsage().getMax());
|
|
||||||
putDeque(status.getJvm().getMemory().getUsed(), nowLinuxAPIMemoryUsed);
|
|
||||||
putDeque(status.getJvm().getMemory().getCommitted(), jvmMemory.getHeapMemoryUsage().getCommitted());
|
|
||||||
|
|
||||||
// ---------- JVM GC ----------
|
|
||||||
long gcSyncCycles = 0;
|
|
||||||
long gcSyncCyclesTime = 0;
|
|
||||||
long gcPauses = 0;
|
|
||||||
long gcPausesTime = 0;
|
|
||||||
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
|
|
||||||
gcSyncCycles += gc.getCollectionCount();
|
|
||||||
gcSyncCyclesTime += gc.getCollectionTime();
|
|
||||||
switch (gc.getName()) {
|
|
||||||
case "ZGC Cycles" -> {
|
|
||||||
gcSyncCycles += gc.getCollectionCount();
|
|
||||||
gcSyncCyclesTime += gc.getCollectionTime();
|
|
||||||
}
|
}
|
||||||
case "ZGC Pauses" -> {
|
status.getUpdateAxis().addLast(context.getCollectAt());
|
||||||
gcPauses += gc.getCollectionCount();
|
if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < status.getUpdateAxis().size()) {
|
||||||
gcPausesTime += gc.getCollectionTime();
|
status.getUpdateAxis().pollFirst();
|
||||||
if (status.getJvm().getZgc().getPauses() < gcPauses) {
|
|
||||||
// 发生 GC 回收
|
|
||||||
status.getJvm().getZgc().setLastPauseAt(now);
|
|
||||||
status.getJvm().getZgc().setLastRecoverySize(linuxAPIMemoryDiff);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(triggerContext));
|
||||||
}
|
|
||||||
putDeque(status.getJvm().getZgc().getSyncCyclesTime(), gcSyncCyclesTime - status.getJvm().getZgc().getSyncCyclesTimeTotal());
|
|
||||||
putDeque(status.getJvm().getZgc().getPausesTime(), gcPausesTime - status.getJvm().getZgc().getPausesTimeTotal());
|
|
||||||
status.getJvm().getZgc().setSyncCycles(gcSyncCycles);
|
|
||||||
status.getJvm().getZgc().setSyncCyclesTimeTotal(gcSyncCyclesTime);
|
|
||||||
status.getJvm().getZgc().setPauses(gcPauses);
|
|
||||||
status.getJvm().getZgc().setPausesTimeTotal(gcPausesTime);
|
|
||||||
|
|
||||||
// ---------- CPU ----------
|
|
||||||
if (lastCPUTicks != null) {
|
|
||||||
long[] ticks = processor.getSystemCpuLoadTicks();
|
|
||||||
|
|
||||||
long user = ticks[CentralProcessor.TickType.USER.getIndex()] - lastCPUTicks[CentralProcessor.TickType.USER.getIndex()];
|
|
||||||
long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - lastCPUTicks[CentralProcessor.TickType.NICE.getIndex()];
|
|
||||||
long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - lastCPUTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
|
|
||||||
long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - lastCPUTicks[CentralProcessor.TickType.IDLE.getIndex()];
|
|
||||||
long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - lastCPUTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
|
|
||||||
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - lastCPUTicks[CentralProcessor.TickType.IRQ.getIndex()];
|
|
||||||
long softIRQ = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - lastCPUTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
|
|
||||||
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCPUTicks[CentralProcessor.TickType.STEAL.getIndex()];
|
|
||||||
long total = user + nice + sys + idle + ioWait + irq + softIRQ + steal;
|
|
||||||
|
|
||||||
putDeque(status.getCpu().getSystem(), 100D * sys / total);
|
|
||||||
putDeque(status.getCpu().getUsed(), 100 - 100D * idle / total);
|
|
||||||
}
|
|
||||||
lastCPUTicks = processor.getSystemCpuLoadTicks();
|
|
||||||
status.getCpu().setTemperature(hardware.getSensors().getCpuTemperature());
|
|
||||||
|
|
||||||
// ---------- 内存 ----------
|
|
||||||
putDeque(status.getMemory().getUsed(), globalMemory.getTotal() - globalMemory.getAvailable());
|
|
||||||
putDeque(status.getMemory().getSwapUsed(), globalMemory.getVirtualMemory().getSwapUsed());
|
|
||||||
|
|
||||||
// ---------- 网络 ----------
|
|
||||||
networkIFs.clear();
|
|
||||||
networkIFs.addAll(hardware.getNetworkIFs());
|
|
||||||
NetworkIF networkIF;
|
|
||||||
for (int i = 0; i < networkIFs.size(); i++) {
|
|
||||||
networkIF = networkIFs.get(i);
|
|
||||||
if (networkIF.getMacaddr().equals(settingService.getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC))) {
|
|
||||||
long recv = networkIF.getBytesRecv() - status.getNetwork().getRecvTotal();
|
|
||||||
long sent = networkIF.getBytesSent() - status.getNetwork().getSentTotal();
|
|
||||||
status.getNetwork().setRecvNow(recv / (settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000));
|
|
||||||
status.getNetwork().setSentNow(sent / (settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000));
|
|
||||||
status.getNetwork().setRecvTotal(networkIF.getBytesRecv());
|
|
||||||
status.getNetwork().setSentTotal(networkIF.getBytesSent());
|
|
||||||
|
|
||||||
putDeque(status.getNetwork().getRecv(), recv);
|
|
||||||
putDeque(status.getNetwork().getSent(), sent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- 磁盘分区 ----------
|
|
||||||
ServerStatus.Partition partition;
|
|
||||||
status.getPartitions().clear();
|
|
||||||
// 分区从文件系统获取,而非物理分区
|
|
||||||
List<OSFileStore> fileStores = os.getFileSystem().getFileStores();
|
|
||||||
for (OSFileStore fileStore : fileStores) {
|
|
||||||
partition = new ServerStatus.Partition();
|
|
||||||
partition.setUuid(fileStore.getUUID());
|
|
||||||
partition.setPath(fileStore.getMount());
|
|
||||||
partition.setType(fileStore.getType());
|
|
||||||
partition.setUsed(fileStore.getTotalSpace() - fileStore.getUsableSpace());
|
|
||||||
partition.setTotal(fileStore.getTotalSpace());
|
|
||||||
|
|
||||||
status.getPartitions().add(partition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- 更新时轴 ----------
|
|
||||||
putDeque(status.getUpdateAxis(), Time.now());
|
|
||||||
}, tc -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(tc));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有所限制地添加队列数据,达到配置 {@link SettingKey#SYSTEM_STATUS_LIMIT} 个时移除最旧的
|
|
||||||
*
|
|
||||||
* @param deque 队列
|
|
||||||
* @param t 数据
|
|
||||||
* @param <T> 数据类型
|
|
||||||
*/
|
|
||||||
private <T> void putDeque(Deque<T> deque, T t) {
|
|
||||||
deque.addLast(t);
|
|
||||||
if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < deque.size()) {
|
|
||||||
deque.pollFirst();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.bean.UpsStatusStore;
|
||||||
|
import com.imyeyu.api.modules.system.util.UpsStatusClient;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 状态采集任务
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpsStatusTask implements SchedulingConfigurer {
|
||||||
|
|
||||||
|
public static final long MAX_HISTORY_MS = 3L * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
private final UpsStatusClient upsStatusClient;
|
||||||
|
private final UpsStatusStore upsStatusStore;
|
||||||
|
|
||||||
|
@Value("${ups.collect-enabled:true}")
|
||||||
|
private boolean collectEnabled;
|
||||||
|
|
||||||
|
@Value("${ups.collect-rate-ms:60000}")
|
||||||
|
private long collectRateMs;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||||
|
if (!collectEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PeriodicTrigger trigger = new PeriodicTrigger(collectRateMs, TimeUnit.MILLISECONDS);
|
||||||
|
trigger.setInitialDelay(0);
|
||||||
|
taskRegistrar.addTriggerTask(this::collect, trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立即采集一次
|
||||||
|
*/
|
||||||
|
public void collectOnce() {
|
||||||
|
collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collect() {
|
||||||
|
try {
|
||||||
|
UpsStatusStore.Snapshot snapshot = upsStatusClient.fetchSnapshot();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
snapshot.setUpdatedAt(now);
|
||||||
|
|
||||||
|
UpsStatusStore.Point point = new UpsStatusStore.Point();
|
||||||
|
point.setAt(now);
|
||||||
|
point.setUpsTime(snapshot.getUpsTime());
|
||||||
|
point.setWorkMode(snapshot.getWorkMode());
|
||||||
|
point.setInputVoltage(snapshot.getInputVoltage());
|
||||||
|
point.setInputFrequency(snapshot.getInputFrequency());
|
||||||
|
point.setOutputVoltage(snapshot.getOutputVoltage());
|
||||||
|
point.setOutputFrequency(snapshot.getOutputFrequency());
|
||||||
|
point.setOutputLoadPercent(snapshot.getOutputLoadPercent());
|
||||||
|
point.setBatteryVoltage(snapshot.getBatteryVoltage());
|
||||||
|
point.setBatteryCapacity(snapshot.getBatteryCapacity());
|
||||||
|
point.setBatteryRemainTime(snapshot.getBatteryRemainTime());
|
||||||
|
point.setTemperature(snapshot.getTemperature());
|
||||||
|
point.setBypassActive(snapshot.isBypassActive());
|
||||||
|
point.setShutdownActive(snapshot.isShutdownActive());
|
||||||
|
point.setOutputOn(snapshot.isOutputOn());
|
||||||
|
|
||||||
|
synchronized (upsStatusStore) {
|
||||||
|
upsStatusStore.setCurrent(snapshot);
|
||||||
|
upsStatusStore.getHistory().addLast(point);
|
||||||
|
long threshold = now - MAX_HISTORY_MS;
|
||||||
|
while (!upsStatusStore.getHistory().isEmpty() && upsStatusStore.getHistory().peekFirst().getAt() < threshold) {
|
||||||
|
upsStatusStore.getHistory().pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("collect ups status error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带有限长队列工具的采集器基类
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDequeStatusCollector implements StatusCollector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有限长度追加队列数据
|
||||||
|
*
|
||||||
|
* @param context 采集上下文
|
||||||
|
* @param deque 队列
|
||||||
|
* @param value 值
|
||||||
|
* @param <T> 类型
|
||||||
|
*/
|
||||||
|
protected <T> void putDeque(StatusCollectContext context, Deque<T> deque, T value) {
|
||||||
|
deque.addLast(value);
|
||||||
|
if (context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < deque.size()) {
|
||||||
|
deque.pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.service.SettingService;
|
||||||
|
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import oshi.hardware.CentralProcessor;
|
||||||
|
import oshi.hardware.ComputerSystem;
|
||||||
|
import oshi.hardware.GlobalMemory;
|
||||||
|
import oshi.hardware.HardwareAbstractionLayer;
|
||||||
|
import oshi.software.os.OperatingSystem;
|
||||||
|
|
||||||
|
import java.lang.management.MemoryMXBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态采集上下文
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class StatusCollectContext {
|
||||||
|
|
||||||
|
private final ServerStatus status;
|
||||||
|
private final GlobalMemory globalMemory;
|
||||||
|
private final MemoryMXBean jvmMemory;
|
||||||
|
private final SettingService settingService;
|
||||||
|
private final ComputerSystem computerSystem;
|
||||||
|
private final OperatingSystem operatingSystem;
|
||||||
|
private final CentralProcessor processor;
|
||||||
|
private final HardwareAbstractionLayer hardware;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private long collectAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态采集器
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
public interface StatusCollector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化采集器
|
||||||
|
*
|
||||||
|
* @param context 采集上下文
|
||||||
|
*/
|
||||||
|
default void initialize(StatusCollectContext context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 采集状态
|
||||||
|
*
|
||||||
|
* @param context 采集上下文
|
||||||
|
*/
|
||||||
|
void collect(StatusCollectContext context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status.collector;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import oshi.hardware.CentralProcessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CPU 状态采集器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-07 11:18
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(20)
|
||||||
|
public class CpuStatusCollector extends AbstractDequeStatusCollector {
|
||||||
|
|
||||||
|
private long[] lastCpuTicks;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(StatusCollectContext context) {
|
||||||
|
context.getStatus().getCpu().setName(context.getProcessor().getProcessorIdentifier().getName().trim());
|
||||||
|
context.getStatus().getCpu().setCoreCount(context.getProcessor().getPhysicalProcessorCount());
|
||||||
|
context.getStatus().getCpu().setLogicalCount(context.getProcessor().getLogicalProcessorCount());
|
||||||
|
lastCpuTicks = context.getProcessor().getSystemCpuLoadTicks();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(StatusCollectContext context) {
|
||||||
|
long[] ticks = context.getProcessor().getSystemCpuLoadTicks();
|
||||||
|
if (lastCpuTicks != null) {
|
||||||
|
long user = ticks[CentralProcessor.TickType.USER.getIndex()] - lastCpuTicks[CentralProcessor.TickType.USER.getIndex()];
|
||||||
|
long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - lastCpuTicks[CentralProcessor.TickType.NICE.getIndex()];
|
||||||
|
long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - lastCpuTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
|
||||||
|
long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IDLE.getIndex()];
|
||||||
|
long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
|
||||||
|
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IRQ.getIndex()];
|
||||||
|
long softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
|
||||||
|
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCpuTicks[CentralProcessor.TickType.STEAL.getIndex()];
|
||||||
|
long total = user + nice + sys + idle + ioWait + irq + softIrq + steal;
|
||||||
|
if (0 < total) {
|
||||||
|
putDeque(context, context.getStatus().getCpu().getSystem(), 100D * sys / total);
|
||||||
|
putDeque(context, context.getStatus().getCpu().getUsed(), 100 - 100D * idle / total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastCpuTicks = ticks;
|
||||||
|
context.getStatus().getCpu().setTemperature(context.getHardware().getSensors().getCpuTemperature());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status.collector;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 硬件状态采集器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-07 11:18
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(35)
|
||||||
|
public class HardwareStatusCollector implements StatusCollector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(StatusCollectContext context) {
|
||||||
|
ServerStatus.Baseboard baseboard = context.getStatus().getHardware().getBaseboard();
|
||||||
|
baseboard.setManufacturer(context.getComputerSystem().getBaseboard().getManufacturer());
|
||||||
|
baseboard.setModel(context.getComputerSystem().getBaseboard().getModel());
|
||||||
|
baseboard.setVersion(context.getComputerSystem().getBaseboard().getVersion());
|
||||||
|
baseboard.setSerialNumber(context.getComputerSystem().getBaseboard().getSerialNumber());
|
||||||
|
|
||||||
|
ServerStatus.Firmware firmware = context.getStatus().getHardware().getFirmware();
|
||||||
|
firmware.setManufacturer(context.getComputerSystem().getFirmware().getManufacturer());
|
||||||
|
firmware.setName(context.getComputerSystem().getFirmware().getName());
|
||||||
|
firmware.setDescription(context.getComputerSystem().getFirmware().getDescription());
|
||||||
|
firmware.setVersion(context.getComputerSystem().getFirmware().getVersion());
|
||||||
|
firmware.setReleaseDate(context.getComputerSystem().getFirmware().getReleaseDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(StatusCollectContext context) {
|
||||||
|
int[] fanSpeeds = context.getHardware().getSensors().getFanSpeeds();
|
||||||
|
ArrayList<Integer> values = new ArrayList<>(fanSpeeds.length);
|
||||||
|
for (int fanSpeed : fanSpeeds) {
|
||||||
|
values.add(fanSpeed);
|
||||||
|
}
|
||||||
|
context.getStatus().getHardware().setFanSpeeds(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status.collector;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.management.GarbageCollectorMXBean;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM 状态采集器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-07 11:17
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(40)
|
||||||
|
public class JvmStatusCollector extends AbstractDequeStatusCollector {
|
||||||
|
|
||||||
|
private long lastHeapUsed = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(StatusCollectContext context) {
|
||||||
|
context.getStatus().getJvm().setBootAt(Time.now());
|
||||||
|
context.getStatus().getJvm().setName(System.getProperty("java.vm.name"));
|
||||||
|
context.getStatus().getJvm().setVersion(System.getProperty("java.version"));
|
||||||
|
context.getStatus().getJvm().setGcName(resolveGcName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(StatusCollectContext context) {
|
||||||
|
long heapUsed = context.getJvmMemory().getHeapMemoryUsage().getUsed();
|
||||||
|
context.getStatus().getJvm().getMemory().setInit(context.getJvmMemory().getHeapMemoryUsage().getInit());
|
||||||
|
context.getStatus().getJvm().getMemory().setMax(context.getJvmMemory().getHeapMemoryUsage().getMax());
|
||||||
|
putDeque(context, context.getStatus().getJvm().getMemory().getUsed(), heapUsed);
|
||||||
|
putDeque(context, context.getStatus().getJvm().getMemory().getCommitted(), context.getJvmMemory().getHeapMemoryUsage().getCommitted());
|
||||||
|
|
||||||
|
long recoverySize = 0;
|
||||||
|
if (0 <= lastHeapUsed) {
|
||||||
|
recoverySize = Math.abs(heapUsed - lastHeapUsed);
|
||||||
|
}
|
||||||
|
lastHeapUsed = heapUsed;
|
||||||
|
|
||||||
|
long gcSyncCycles = 0;
|
||||||
|
long gcSyncCyclesTime = 0;
|
||||||
|
long gcPauses = 0;
|
||||||
|
long gcPausesTime = 0;
|
||||||
|
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||||
|
switch (gc.getName()) {
|
||||||
|
case "ZGC Cycles" -> {
|
||||||
|
gcSyncCycles += gc.getCollectionCount();
|
||||||
|
gcSyncCyclesTime += gc.getCollectionTime();
|
||||||
|
}
|
||||||
|
case "ZGC Pauses" -> {
|
||||||
|
gcPauses += gc.getCollectionCount();
|
||||||
|
gcPausesTime += gc.getCollectionTime();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.getStatus().getJvm().getGc().getPauses() < gcPauses) {
|
||||||
|
context.getStatus().getJvm().getGc().setLastPauseAt(context.getCollectAt());
|
||||||
|
context.getStatus().getJvm().getGc().setLastRecoverySize(recoverySize);
|
||||||
|
}
|
||||||
|
putDeque(context, context.getStatus().getJvm().getGc().getSyncCyclesTime(), gcSyncCyclesTime - context.getStatus().getJvm().getGc().getSyncCyclesTimeTotal());
|
||||||
|
putDeque(context, context.getStatus().getJvm().getGc().getPausesTime(), gcPausesTime - context.getStatus().getJvm().getGc().getPausesTimeTotal());
|
||||||
|
context.getStatus().getJvm().getGc().setSyncCycles(gcSyncCycles);
|
||||||
|
context.getStatus().getJvm().getGc().setSyncCyclesTimeTotal(gcSyncCyclesTime);
|
||||||
|
context.getStatus().getJvm().getGc().setPauses(gcPauses);
|
||||||
|
context.getStatus().getJvm().getGc().setPausesTimeTotal(gcPausesTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析当前 JVM 的主要 GC 名称
|
||||||
|
*
|
||||||
|
* @return GC 名称
|
||||||
|
*/
|
||||||
|
private String resolveGcName() {
|
||||||
|
List<GarbageCollectorMXBean> collectors = ManagementFactory.getGarbageCollectorMXBeans();
|
||||||
|
if (collectors.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (1 == collectors.size()) {
|
||||||
|
return collectors.get(0).getName();
|
||||||
|
}
|
||||||
|
StringBuilder gcName = new StringBuilder();
|
||||||
|
for (GarbageCollectorMXBean collector : collectors) {
|
||||||
|
if (!gcName.isEmpty()) {
|
||||||
|
gcName.append(", ");
|
||||||
|
}
|
||||||
|
gcName.append(collector.getName());
|
||||||
|
}
|
||||||
|
return gcName.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status.collector;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统内存状态采集器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-07 11:17
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(30)
|
||||||
|
public class MemoryStatusCollector extends AbstractDequeStatusCollector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(StatusCollectContext context) {
|
||||||
|
context.getStatus().getMemory().setSize(context.getGlobalMemory().getTotal());
|
||||||
|
context.getStatus().getMemory().setSwapSize(context.getGlobalMemory().getVirtualMemory().getSwapTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(StatusCollectContext context) {
|
||||||
|
putDeque(context, context.getStatus().getMemory().getUsed(), context.getGlobalMemory().getTotal() - context.getGlobalMemory().getAvailable());
|
||||||
|
putDeque(context, context.getStatus().getMemory().getSwapUsed(), context.getGlobalMemory().getVirtualMemory().getSwapUsed());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status.collector;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bytedeco.librealsense.context;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import oshi.hardware.NetworkIF;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络状态采集器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-07 11:15
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Order(50)
|
||||||
|
public class NetworkStatusCollector extends AbstractDequeStatusCollector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(StatusCollectContext context) {
|
||||||
|
List<NetworkIF> networkIFs = context.getHardware().getNetworkIFs();
|
||||||
|
String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC);
|
||||||
|
for (NetworkIF networkIF : networkIFs) {
|
||||||
|
if (networkIF.getMacaddr().equals(targetMac)) {
|
||||||
|
networkIF.updateAttributes();
|
||||||
|
updateNetworkStatus(context, networkIF);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error("not found setting networkIF MAC: {}", targetMac);
|
||||||
|
for (NetworkIF networkIF : networkIFs) {
|
||||||
|
log.info("Network Interface: {} -> {}", networkIF.getMacaddr(), networkIF.getDisplayName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(StatusCollectContext context) {
|
||||||
|
String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC);
|
||||||
|
int sampleRateMs = context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_RATE);
|
||||||
|
for (NetworkIF networkIF : context.getHardware().getNetworkIFs()) {
|
||||||
|
if (networkIF.getMacaddr().equals(targetMac)) {
|
||||||
|
networkIF.updateAttributes();
|
||||||
|
long recv = networkIF.getBytesRecv() - context.getStatus().getNetwork().getRecvTotal();
|
||||||
|
long sent = networkIF.getBytesSent() - context.getStatus().getNetwork().getSentTotal();
|
||||||
|
context.getStatus().getNetwork().setRecvNow(recv * 1000 / sampleRateMs);
|
||||||
|
context.getStatus().getNetwork().setSentNow(sent * 1000 / sampleRateMs);
|
||||||
|
updateNetworkStatus(context, networkIF);
|
||||||
|
putDeque(context, context.getStatus().getNetwork().getRecv(), recv);
|
||||||
|
putDeque(context, context.getStatus().getNetwork().getSent(), sent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新网络状态缓存
|
||||||
|
*
|
||||||
|
* @param context 采集上下文
|
||||||
|
* @param networkIF 网卡
|
||||||
|
*/
|
||||||
|
private void updateNetworkStatus(StatusCollectContext context, NetworkIF networkIF) {
|
||||||
|
context.getStatus().getNetwork().setName(networkIF.getDisplayName());
|
||||||
|
context.getStatus().getNetwork().setMac(networkIF.getMacaddr());
|
||||||
|
context.getStatus().getNetwork().setRecvTotal(networkIF.getBytesRecv());
|
||||||
|
context.getStatus().getNetwork().setSentTotal(networkIF.getBytesSent());
|
||||||
|
context.getStatus().getNetwork().setRecvPacketsTotal(networkIF.getPacketsRecv());
|
||||||
|
context.getStatus().getNetwork().setSentPacketsTotal(networkIF.getPacketsSent());
|
||||||
|
context.getStatus().getNetwork().setInErrors(networkIF.getInErrors());
|
||||||
|
context.getStatus().getNetwork().setOutErrors(networkIF.getOutErrors());
|
||||||
|
context.getStatus().getNetwork().setInDrops(networkIF.getInDrops());
|
||||||
|
context.getStatus().getNetwork().setCollisions(networkIF.getCollisions());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status.collector;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||||
|
import com.imyeyu.utils.OS;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统状态采集器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-07 11:15
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(10)
|
||||||
|
public class OSStatusCollector implements StatusCollector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(StatusCollectContext context) {
|
||||||
|
context.getStatus().getOs().setName(OS.NAME);
|
||||||
|
context.getStatus().getOs().setBootAt(context.getOperatingSystem().getSystemBootTime() * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(StatusCollectContext context) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.imyeyu.api.modules.system.task.status.collector;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||||
|
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import oshi.hardware.HWDiskStore;
|
||||||
|
import oshi.hardware.HWPartition;
|
||||||
|
import oshi.software.os.OSFileStore;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储状态采集器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-07 11:14
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(60)
|
||||||
|
public class StorageStatusCollector implements StatusCollector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(StatusCollectContext context) {
|
||||||
|
Map<String, OSFileStore> fileStoreMap = createFileStoreMap(context);
|
||||||
|
context.getStatus().getStoragePartitions().clear();
|
||||||
|
for (HWDiskStore diskStore : context.getHardware().getDiskStores()) {
|
||||||
|
diskStore.updateAttributes();
|
||||||
|
for (HWPartition partition : diskStore.getPartitions()) {
|
||||||
|
ServerStatus.StoragePartition item = new ServerStatus.StoragePartition();
|
||||||
|
item.setDiskName(diskStore.getName());
|
||||||
|
item.setDiskModel(diskStore.getModel());
|
||||||
|
item.setDiskSerial(diskStore.getSerial());
|
||||||
|
item.setPartitionId(partition.getIdentification());
|
||||||
|
item.setPartitionName(partition.getName());
|
||||||
|
item.setPartitionType(partition.getType());
|
||||||
|
item.setUuid(partition.getUuid());
|
||||||
|
item.setMountPoint(partition.getMountPoint());
|
||||||
|
item.setTotalBytes(partition.getSize());
|
||||||
|
item.setTransferTimeMs(diskStore.getTransferTime());
|
||||||
|
|
||||||
|
OSFileStore fileStore = matchFileStore(partition, fileStoreMap);
|
||||||
|
if (fileStore != null) {
|
||||||
|
fileStore.updateAttributes();
|
||||||
|
item.setUsedBytes(fileStore.getTotalSpace() - fileStore.getUsableSpace());
|
||||||
|
}
|
||||||
|
context.getStatus().getStoragePartitions().add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建文件系统映射
|
||||||
|
*
|
||||||
|
* @param context 采集上下文
|
||||||
|
* @return 映射表
|
||||||
|
*/
|
||||||
|
private Map<String, OSFileStore> createFileStoreMap(StatusCollectContext context) {
|
||||||
|
Map<String, OSFileStore> result = new HashMap<>();
|
||||||
|
for (OSFileStore fileStore : context.getOperatingSystem().getFileSystem().getFileStores()) {
|
||||||
|
result.put("mount:" + fileStore.getMount(), fileStore);
|
||||||
|
result.put("volume:" + fileStore.getVolume(), fileStore);
|
||||||
|
result.put("name:" + fileStore.getName(), fileStore);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配文件系统
|
||||||
|
*
|
||||||
|
* @param partition 物理分区
|
||||||
|
* @param fileStoreMap 文件系统映射
|
||||||
|
* @return 文件系统
|
||||||
|
*/
|
||||||
|
private OSFileStore matchFileStore(HWPartition partition, Map<String, OSFileStore> fileStoreMap) {
|
||||||
|
if (partition.getMountPoint() != null && !partition.getMountPoint().isBlank()) {
|
||||||
|
OSFileStore byMount = fileStoreMap.get("mount:" + partition.getMountPoint());
|
||||||
|
if (byMount != null) {
|
||||||
|
return byMount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OSFileStore byName = fileStoreMap.get("name:" + partition.getName());
|
||||||
|
if (byName != null) {
|
||||||
|
return byName;
|
||||||
|
}
|
||||||
|
return fileStoreMap.get("volume:" + partition.getIdentification());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package com.imyeyu.api.modules.system.util;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.UnixDomainSocketAddress;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker Engine API 客户端
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class DockerEngineClient {
|
||||||
|
|
||||||
|
private final String host;
|
||||||
|
private final String apiVersion;
|
||||||
|
private final Duration timeout;
|
||||||
|
private final HttpClient httpClient = HttpClient.newBuilder().build();
|
||||||
|
|
||||||
|
public DockerEngineClient(
|
||||||
|
@Value("${docker.engine.host:unix:///var/run/docker.sock}") String host,
|
||||||
|
@Value("${docker.engine.api-version:v1.41}") String apiVersion,
|
||||||
|
@Value("${docker.engine.timeout-ms:5000}") long timeoutMs
|
||||||
|
) {
|
||||||
|
this.host = host;
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
this.timeout = Duration.ofMillis(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 JSON 响应
|
||||||
|
*
|
||||||
|
* @param path 接口路径
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @return JSON 数据
|
||||||
|
*/
|
||||||
|
public JsonElement getJson(String path, Map<String, String> queryParams) {
|
||||||
|
String requestPath = buildRequestPath(path, queryParams);
|
||||||
|
try {
|
||||||
|
String body = host.startsWith("unix://")
|
||||||
|
? executeUnixGet(requestPath)
|
||||||
|
: executeHttpGet(requestPath);
|
||||||
|
return JsonParser.parseString(body);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("docker engine request interrupted: {}", requestPath, e);
|
||||||
|
throw new TimiException(TimiCode.ERROR).msgKey("TODO docker engine request interrupted");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("docker engine request error: {}", requestPath, e);
|
||||||
|
throw new TimiException(TimiCode.ERROR).msgKey("TODO docker engine request error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建请求路径
|
||||||
|
*
|
||||||
|
* @param path 接口路径
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @return 请求路径
|
||||||
|
*/
|
||||||
|
private String buildRequestPath(String path, Map<String, String> queryParams) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("/");
|
||||||
|
builder.append(apiVersion);
|
||||||
|
if (!path.startsWith("/")) {
|
||||||
|
builder.append("/");
|
||||||
|
}
|
||||||
|
builder.append(path);
|
||||||
|
if (queryParams != null && !queryParams.isEmpty()) {
|
||||||
|
builder.append("?");
|
||||||
|
boolean first = true;
|
||||||
|
for (Map.Entry<String, String> item : queryParams.entrySet()) {
|
||||||
|
if (!first) {
|
||||||
|
builder.append("&");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
builder.append(item.getKey()).append("=").append(item.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 HTTP/TCP 执行 GET
|
||||||
|
*
|
||||||
|
* @param requestPath 请求路径
|
||||||
|
* @return 响应体
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
* @throws InterruptedException 中断异常
|
||||||
|
*/
|
||||||
|
private String executeHttpGet(String requestPath) throws IOException, InterruptedException {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(host + requestPath))
|
||||||
|
.timeout(timeout)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
|
if (400 <= response.statusCode()) {
|
||||||
|
throw new IOException("docker engine http error: " + response.statusCode());
|
||||||
|
}
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 Unix Socket 执行 GET
|
||||||
|
*
|
||||||
|
* @param requestPath 请求路径
|
||||||
|
* @return 响应体
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
|
private String executeUnixGet(String requestPath) throws IOException {
|
||||||
|
String socketPath = host.substring("unix://".length());
|
||||||
|
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(Path.of(socketPath));
|
||||||
|
try (SocketChannel channel = SocketChannel.open(address)) {
|
||||||
|
channel.configureBlocking(true);
|
||||||
|
String requestText = """
|
||||||
|
GET %s HTTP/1.1\r
|
||||||
|
Host: docker\r
|
||||||
|
Accept: application/json\r
|
||||||
|
Connection: close\r
|
||||||
|
\r
|
||||||
|
""".formatted(requestPath);
|
||||||
|
channel.write(StandardCharsets.UTF_8.encode(requestText));
|
||||||
|
byte[] responseBytes = readAll(channel);
|
||||||
|
return parseHttpBody(responseBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取全部响应字节
|
||||||
|
*
|
||||||
|
* @param channel 通道
|
||||||
|
* @return 字节数组
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
|
private byte[] readAll(SocketChannel channel) throws IOException {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(8192);
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
while (true) {
|
||||||
|
int readLength = channel.read(buffer);
|
||||||
|
if (readLength < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (0 == readLength) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buffer.flip();
|
||||||
|
output.write(buffer.array(), 0, buffer.remaining());
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
return output.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 HTTP 响应体
|
||||||
|
*
|
||||||
|
* @param responseBytes 响应字节
|
||||||
|
* @return 响应体
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
|
private String parseHttpBody(byte[] responseBytes) throws IOException {
|
||||||
|
int splitIndex = indexOf(responseBytes, new byte[] {'\r', '\n', '\r', '\n'});
|
||||||
|
if (splitIndex < 0) {
|
||||||
|
throw new IOException("invalid docker engine response");
|
||||||
|
}
|
||||||
|
String headerText = new String(responseBytes, 0, splitIndex, StandardCharsets.UTF_8);
|
||||||
|
byte[] bodyBytes = new byte[responseBytes.length - splitIndex - 4];
|
||||||
|
System.arraycopy(responseBytes, splitIndex + 4, bodyBytes, 0, bodyBytes.length);
|
||||||
|
if (headerText.contains("Transfer-Encoding: chunked")) {
|
||||||
|
bodyBytes = decodeChunkedBody(bodyBytes);
|
||||||
|
}
|
||||||
|
if (!headerText.startsWith("HTTP/1.1 200") && !headerText.startsWith("HTTP/1.1 204")) {
|
||||||
|
throw new IOException("docker engine http error: " + headerText.lines().findFirst().orElse(headerText));
|
||||||
|
}
|
||||||
|
return new String(bodyBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码 Chunked 响应体
|
||||||
|
*
|
||||||
|
* @param bodyBytes 原始响应体
|
||||||
|
* @return 解码后的响应体
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
|
private byte[] decodeChunkedBody(byte[] bodyBytes) throws IOException {
|
||||||
|
int offset = 0;
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
while (offset < bodyBytes.length) {
|
||||||
|
int lineEnd = indexOf(bodyBytes, offset, new byte[] {'\r', '\n'});
|
||||||
|
if (lineEnd < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String sizeText = new String(bodyBytes, offset, lineEnd - offset, StandardCharsets.UTF_8).trim();
|
||||||
|
int chunkSize = Integer.parseInt(sizeText, 16);
|
||||||
|
offset = lineEnd + 2;
|
||||||
|
if (0 == chunkSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output.write(bodyBytes, offset, chunkSize);
|
||||||
|
offset += chunkSize + 2;
|
||||||
|
}
|
||||||
|
return output.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找字节序列
|
||||||
|
*
|
||||||
|
* @param source 原数组
|
||||||
|
* @param pattern 目标数组
|
||||||
|
* @return 下标
|
||||||
|
*/
|
||||||
|
private int indexOf(byte[] source, byte[] pattern) {
|
||||||
|
return indexOf(source, 0, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从指定位置查找字节序列
|
||||||
|
*
|
||||||
|
* @param source 原数组
|
||||||
|
* @param start 起始位置
|
||||||
|
* @param pattern 目标数组
|
||||||
|
* @return 下标
|
||||||
|
*/
|
||||||
|
private int indexOf(byte[] source, int start, byte[] pattern) {
|
||||||
|
for (int i = start; i <= source.length - pattern.length; i++) {
|
||||||
|
boolean matched = true;
|
||||||
|
for (int j = 0; j < pattern.length; j++) {
|
||||||
|
if (source[i + j] != pattern[j]) {
|
||||||
|
matched = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matched) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建查询参数映射
|
||||||
|
*
|
||||||
|
* @param entries 键值对
|
||||||
|
* @return 映射
|
||||||
|
*/
|
||||||
|
public static Map<String, String> query(String... entries) {
|
||||||
|
LinkedHashMap<String, String> result = new LinkedHashMap<>();
|
||||||
|
for (int i = 0; i + 1 < entries.length; i += 2) {
|
||||||
|
result.put(entries[i], entries[i + 1]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,9 +29,9 @@ public class SystemAPIInterceptor implements HandlerInterceptor {
|
|||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
|
|
||||||
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
|
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
|
||||||
String key = TimiSpring.getHeader("Key");
|
String key = TimiSpring.getHeader("Token");
|
||||||
if (TimiJava.isEmpty(key)) {
|
if (TimiJava.isEmpty(key)) {
|
||||||
key = req.getParameter("key");
|
key = req.getParameter("token");
|
||||||
}
|
}
|
||||||
String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_KEY);
|
String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_KEY);
|
||||||
String dbSuperKey = settingService.getAsString(SettingKey.SYSTEM_API_SUPER_KEY);
|
String dbSuperKey = settingService.getAsString(SettingKey.SYSTEM_API_SUPER_KEY);
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
package com.imyeyu.api.modules.system.util;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.imyeyu.api.modules.system.bean.UpsStatusStore;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 状态客户端
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class UpsStatusClient {
|
||||||
|
|
||||||
|
private final String statusUrl;
|
||||||
|
private final Duration timeout;
|
||||||
|
private final HttpClient httpClient = HttpClient.newBuilder().build();
|
||||||
|
|
||||||
|
public UpsStatusClient(
|
||||||
|
@Value("${ups.status-url:}") String statusUrl,
|
||||||
|
@Value("${ups.timeout-ms:5000}") long timeoutMs
|
||||||
|
) {
|
||||||
|
this.statusUrl = statusUrl;
|
||||||
|
this.timeout = Duration.ofMillis(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 UPS 状态 JSON
|
||||||
|
*
|
||||||
|
* @return JSON 数据
|
||||||
|
*/
|
||||||
|
public JsonElement getStatusJson() {
|
||||||
|
if (statusUrl == null || statusUrl.isBlank()) {
|
||||||
|
throw new TimiException(TimiCode.ARG_MISS).msgKey("缺少配置:ups.status-url");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(statusUrl))
|
||||||
|
.timeout(timeout)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
|
if (400 <= response.statusCode()) {
|
||||||
|
throw new IOException("ups http error: " + response.statusCode());
|
||||||
|
}
|
||||||
|
return JsonParser.parseString(response.body());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("request ups status interrupted: {}", statusUrl, e);
|
||||||
|
throw new TimiException(TimiCode.ERROR).msgKey("UPS 状态请求被中断");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("request ups status error: {}", statusUrl, e);
|
||||||
|
throw new TimiException(TimiCode.ERROR).msgKey("UPS 状态请求失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取并解析 UPS 当前快照
|
||||||
|
*
|
||||||
|
* @return UPS 当前快照
|
||||||
|
*/
|
||||||
|
public UpsStatusStore.Snapshot fetchSnapshot() {
|
||||||
|
JsonObject root = getStatusJson().getAsJsonObject();
|
||||||
|
JsonObject workInfo = getAsObject(root, "workInfo");
|
||||||
|
|
||||||
|
UpsStatusStore.Snapshot snapshot = new UpsStatusStore.Snapshot();
|
||||||
|
snapshot.setUpsTime(readUpsTime(workInfo));
|
||||||
|
snapshot.setHostName(readMeaningfulText(root, "hostName"));
|
||||||
|
snapshot.setCustomer(readMeaningfulText(root, "customer"));
|
||||||
|
snapshot.setVersion(readMeaningfulText(root, "version"));
|
||||||
|
snapshot.setDeviceId(readMeaningfulText(workInfo, "deviceId"));
|
||||||
|
snapshot.setUpsType(readMeaningfulText(workInfo, "upsType"));
|
||||||
|
snapshot.setMorphological(readMeaningfulText(workInfo, "morphological"));
|
||||||
|
snapshot.setIoPhase(readMeaningfulText(workInfo, "ioPhase"));
|
||||||
|
snapshot.setWorkMode(readMeaningfulText(workInfo, "workMode"));
|
||||||
|
snapshot.setInputVoltage(readMeaningfulDouble(workInfo, "inputVoltage"));
|
||||||
|
snapshot.setInputFrequency(readMeaningfulDouble(workInfo, "inputFrequency"));
|
||||||
|
snapshot.setOutputVoltage(readMeaningfulDouble(workInfo, "outputVoltage"));
|
||||||
|
snapshot.setOutputFrequency(readMeaningfulDouble(workInfo, "outputFrequency"));
|
||||||
|
snapshot.setOutputLoadPercent(readMeaningfulInteger(workInfo, "outputLoadPercent"));
|
||||||
|
snapshot.setBatteryVoltage(readMeaningfulDouble(workInfo, "batteryVoltage"));
|
||||||
|
snapshot.setBatteryCapacity(readMeaningfulInteger(workInfo, "batteryCapacity"));
|
||||||
|
snapshot.setBatteryRemainTime(readMeaningfulInteger(workInfo, "batteryRemainTime"));
|
||||||
|
snapshot.setTemperature(readMeaningfulDouble(workInfo, "temperatureView"));
|
||||||
|
snapshot.setBypassActive(readAsBoolean(workInfo, "bypassActive"));
|
||||||
|
snapshot.setShutdownActive(readAsBoolean(workInfo, "shutdownActive"));
|
||||||
|
snapshot.setOutputOn(readAsBoolean(workInfo, "outputON"));
|
||||||
|
snapshot.setCharging(readAsBoolean(workInfo, "chargeON"));
|
||||||
|
snapshot.setFaultType(readMeaningfulText(workInfo, "faultType"));
|
||||||
|
snapshot.setFaultKind(readMeaningfulText(workInfo, "faultKind"));
|
||||||
|
snapshot.setWarnings(readWarnings(workInfo));
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取 UPS 时间戳
|
||||||
|
*
|
||||||
|
* @param workInfo 工作信息
|
||||||
|
* @return 时间戳
|
||||||
|
*/
|
||||||
|
private Long readUpsTime(JsonObject workInfo) {
|
||||||
|
JsonObject currentTime = getAsObject(workInfo, "currentTime");
|
||||||
|
if (currentTime == null || !currentTime.has("time") || currentTime.get("time").isJsonNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return currentTime.get("time").getAsLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取告警列表
|
||||||
|
*
|
||||||
|
* @param workInfo 工作信息
|
||||||
|
* @return 告警列表
|
||||||
|
*/
|
||||||
|
private java.util.List<String> readWarnings(JsonObject workInfo) {
|
||||||
|
java.util.List<String> warnings = new java.util.ArrayList<>();
|
||||||
|
if (workInfo == null || !workInfo.has("warnings") || !workInfo.get("warnings").isJsonArray()) {
|
||||||
|
return warnings;
|
||||||
|
}
|
||||||
|
JsonArray array = workInfo.getAsJsonArray("warnings");
|
||||||
|
for (JsonElement item : array) {
|
||||||
|
if (!item.isJsonPrimitive()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String warning = normalizeText(item.getAsString());
|
||||||
|
if (warning != null) {
|
||||||
|
warnings.add(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取对象字段
|
||||||
|
*
|
||||||
|
* @param source 源对象
|
||||||
|
* @param key 键
|
||||||
|
* @return 子对象
|
||||||
|
*/
|
||||||
|
private JsonObject getAsObject(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull() || !source.get(key).isJsonObject()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return source.getAsJsonObject(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取有效文本
|
||||||
|
*
|
||||||
|
* @param source 源对象
|
||||||
|
* @param key 键
|
||||||
|
* @return 文本
|
||||||
|
*/
|
||||||
|
private String readMeaningfulText(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalizeText(source.get(key).getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取有效整数
|
||||||
|
*
|
||||||
|
* @param source 源对象
|
||||||
|
* @param key 键
|
||||||
|
* @return 整数
|
||||||
|
*/
|
||||||
|
private Integer readMeaningfulInteger(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonElement element = source.get(key);
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
|
||||||
|
return element.getAsInt();
|
||||||
|
}
|
||||||
|
String text = normalizeText(element.getAsString());
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(text);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取有效小数
|
||||||
|
*
|
||||||
|
* @param source 源对象
|
||||||
|
* @param key 键
|
||||||
|
* @return 小数
|
||||||
|
*/
|
||||||
|
private Double readMeaningfulDouble(JsonObject source, String key) {
|
||||||
|
if (source == null || !source.has(key) || source.get(key).isJsonNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonElement element = source.get(key);
|
||||||
|
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
|
||||||
|
return element.getAsDouble();
|
||||||
|
}
|
||||||
|
String text = normalizeText(element.getAsString());
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(text);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取布尔值
|
||||||
|
*
|
||||||
|
* @param source 源对象
|
||||||
|
* @param key 键
|
||||||
|
* @return 布尔值
|
||||||
|
*/
|
||||||
|
private boolean readAsBoolean(JsonObject source, String key) {
|
||||||
|
return source != null && source.has(key) && !source.get(key).isJsonNull() && source.get(key).getAsBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规范化文本
|
||||||
|
*
|
||||||
|
* @param text 原始文本
|
||||||
|
* @return 规范化后的文本
|
||||||
|
*/
|
||||||
|
private String normalizeText(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = text.trim();
|
||||||
|
if (normalized.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ("----".equals(normalized)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (normalized.startsWith("----:")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统状态数据视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
public class SystemStatusDataView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Snapshot {
|
||||||
|
|
||||||
|
/** 操作系统 */
|
||||||
|
private OS os;
|
||||||
|
|
||||||
|
/** CPU */
|
||||||
|
private CPU cpu;
|
||||||
|
|
||||||
|
/** 系统内存 */
|
||||||
|
private Memory memory;
|
||||||
|
|
||||||
|
/** JVM */
|
||||||
|
private JVM jvm;
|
||||||
|
|
||||||
|
/** 网络 */
|
||||||
|
private Network network;
|
||||||
|
|
||||||
|
/** 硬件 */
|
||||||
|
private Hardware hardware;
|
||||||
|
|
||||||
|
/** 存储分区 */
|
||||||
|
private List<StoragePartition> storagePartitions = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史点
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Point {
|
||||||
|
|
||||||
|
/** 采样时间 */
|
||||||
|
private long at;
|
||||||
|
|
||||||
|
/** CPU 总占用 */
|
||||||
|
private Double cpuUsagePercent;
|
||||||
|
|
||||||
|
/** CPU 系统占用 */
|
||||||
|
private Double cpuSystemPercent;
|
||||||
|
|
||||||
|
/** 系统已用内存 */
|
||||||
|
private Long memoryUsedBytes;
|
||||||
|
|
||||||
|
/** 已用交换分区 */
|
||||||
|
private Long swapUsedBytes;
|
||||||
|
|
||||||
|
/** JVM 已用堆内存 */
|
||||||
|
private Long heapUsedBytes;
|
||||||
|
|
||||||
|
/** JVM 已提交堆内存 */
|
||||||
|
private Long heapCommittedBytes;
|
||||||
|
|
||||||
|
/** GC 周期耗时 */
|
||||||
|
private Long gcCycleTimeMs;
|
||||||
|
|
||||||
|
/** GC 暂停耗时 */
|
||||||
|
private Long gcPauseTimeMs;
|
||||||
|
|
||||||
|
/** 接收速率 */
|
||||||
|
private Long rxBytesPerSecond;
|
||||||
|
|
||||||
|
/** 发送速率 */
|
||||||
|
private Long txBytesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class OS {
|
||||||
|
|
||||||
|
/** 系统名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 启动时间 */
|
||||||
|
private long bootAt;
|
||||||
|
|
||||||
|
/** 运行时长 */
|
||||||
|
private long uptimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CPU 快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class CPU {
|
||||||
|
|
||||||
|
/** 型号 */
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
/** 物理核心数 */
|
||||||
|
private int physicalCores;
|
||||||
|
|
||||||
|
/** 逻辑核心数 */
|
||||||
|
private int logicalCores;
|
||||||
|
|
||||||
|
/** 总占用 */
|
||||||
|
private Double usagePercent;
|
||||||
|
|
||||||
|
/** 系统占用 */
|
||||||
|
private Double systemPercent;
|
||||||
|
|
||||||
|
/** 温度 */
|
||||||
|
private double temperatureCelsius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内存快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Memory {
|
||||||
|
|
||||||
|
/** 总内存 */
|
||||||
|
private long totalBytes;
|
||||||
|
|
||||||
|
/** 已用内存 */
|
||||||
|
private Long usedBytes;
|
||||||
|
|
||||||
|
/** 使用率 */
|
||||||
|
private Double usagePercent;
|
||||||
|
|
||||||
|
/** 交换分区总量 */
|
||||||
|
private long swapTotalBytes;
|
||||||
|
|
||||||
|
/** 已用交换分区 */
|
||||||
|
private Long swapUsedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM 快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class JVM {
|
||||||
|
|
||||||
|
/** 名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 版本 */
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/** 启动时间 */
|
||||||
|
private long bootAt;
|
||||||
|
|
||||||
|
/** 初始堆大小 */
|
||||||
|
private long heapInitBytes;
|
||||||
|
|
||||||
|
/** 最大堆大小 */
|
||||||
|
private long heapMaxBytes;
|
||||||
|
|
||||||
|
/** 已用堆大小 */
|
||||||
|
private Long heapUsedBytes;
|
||||||
|
|
||||||
|
/** 已提交堆大小 */
|
||||||
|
private Long heapCommittedBytes;
|
||||||
|
|
||||||
|
/** GC */
|
||||||
|
private GC gc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GC 快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class GC {
|
||||||
|
|
||||||
|
/** 收集器名称 */
|
||||||
|
private String collector;
|
||||||
|
|
||||||
|
/** 周期次数 */
|
||||||
|
private long cycleCount;
|
||||||
|
|
||||||
|
/** 暂停次数 */
|
||||||
|
private long pauseCount;
|
||||||
|
|
||||||
|
/** 上次暂停时间 */
|
||||||
|
private long lastPauseAt;
|
||||||
|
|
||||||
|
/** 上次回收大小 */
|
||||||
|
private long lastRecoveredBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Network {
|
||||||
|
|
||||||
|
/** 网卡名称 */
|
||||||
|
private String interfaceName;
|
||||||
|
|
||||||
|
/** MAC 地址 */
|
||||||
|
private String mac;
|
||||||
|
|
||||||
|
/** 接收速率 */
|
||||||
|
private long rxBytesPerSecond;
|
||||||
|
|
||||||
|
/** 发送速率 */
|
||||||
|
private long txBytesPerSecond;
|
||||||
|
|
||||||
|
/** 累计接收 */
|
||||||
|
private long rxTotalBytes;
|
||||||
|
|
||||||
|
/** 累计发送 */
|
||||||
|
private long txTotalBytes;
|
||||||
|
|
||||||
|
/** 接收包总数 */
|
||||||
|
private long rxPacketsTotal;
|
||||||
|
|
||||||
|
/** 发送包总数 */
|
||||||
|
private long txPacketsTotal;
|
||||||
|
|
||||||
|
/** 输入错误包总数 */
|
||||||
|
private long inErrors;
|
||||||
|
|
||||||
|
/** 输出错误包总数 */
|
||||||
|
private long outErrors;
|
||||||
|
|
||||||
|
/** 输入丢弃包总数 */
|
||||||
|
private long inDrops;
|
||||||
|
|
||||||
|
/** 碰撞总数 */
|
||||||
|
private long collisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 硬件快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Hardware {
|
||||||
|
|
||||||
|
/** 风扇转速 */
|
||||||
|
private List<Integer> fanSpeeds = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 主板信息 */
|
||||||
|
private Baseboard baseboard;
|
||||||
|
|
||||||
|
/** BIOS 信息 */
|
||||||
|
private Firmware firmware;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主板快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Baseboard {
|
||||||
|
|
||||||
|
/** 厂商 */
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
/** 型号 */
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
/** 版本 */
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/** 序列号 */
|
||||||
|
private String serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIOS 快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Firmware {
|
||||||
|
|
||||||
|
/** 厂商 */
|
||||||
|
private String manufacturer;
|
||||||
|
|
||||||
|
/** 名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 描述 */
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/** 版本 */
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/** 发布时间 */
|
||||||
|
private String releaseDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储分区快照
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class StoragePartition {
|
||||||
|
|
||||||
|
/** 物理磁盘名称 */
|
||||||
|
private String diskName;
|
||||||
|
|
||||||
|
/** 物理磁盘型号 */
|
||||||
|
private String diskModel;
|
||||||
|
|
||||||
|
/** 物理磁盘序列号 */
|
||||||
|
private String diskSerial;
|
||||||
|
|
||||||
|
/** 分区标识 */
|
||||||
|
private String partitionId;
|
||||||
|
|
||||||
|
/** 分区名称 */
|
||||||
|
private String partitionName;
|
||||||
|
|
||||||
|
/** 分区类型 */
|
||||||
|
private String partitionType;
|
||||||
|
|
||||||
|
/** 分区 UUID */
|
||||||
|
private String uuid;
|
||||||
|
|
||||||
|
/** 挂载点 */
|
||||||
|
private String mountPoint;
|
||||||
|
|
||||||
|
/** 分区总空间 */
|
||||||
|
private long totalBytes;
|
||||||
|
|
||||||
|
/** 已用空间 */
|
||||||
|
private Long usedBytes;
|
||||||
|
|
||||||
|
/** 使用率 */
|
||||||
|
private Double usagePercent;
|
||||||
|
|
||||||
|
/** 传输耗时 */
|
||||||
|
private long transferTimeMs;
|
||||||
|
|
||||||
|
/** 健康状态 */
|
||||||
|
private String healthStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统状态历史视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SystemStatusHistoryView {
|
||||||
|
|
||||||
|
/** 服务端当前时间 */
|
||||||
|
private long serverTime;
|
||||||
|
|
||||||
|
/** 采样周期 */
|
||||||
|
private int sampleRateMs;
|
||||||
|
|
||||||
|
/** 起始时间 */
|
||||||
|
private long from;
|
||||||
|
|
||||||
|
/** 结束时间 */
|
||||||
|
private long to;
|
||||||
|
|
||||||
|
/** 历史点 */
|
||||||
|
private List<SystemStatusDataView.Point> points = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统状态快照视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SystemStatusSnapshotView {
|
||||||
|
|
||||||
|
/** 服务端当前时间 */
|
||||||
|
private long serverTime;
|
||||||
|
|
||||||
|
/** 采样周期 */
|
||||||
|
private int sampleRateMs;
|
||||||
|
|
||||||
|
/** 当前快照 */
|
||||||
|
private SystemStatusDataView.Snapshot snapshot = new SystemStatusDataView.Snapshot();
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo.docker;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 容器历史点视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DockerContainerHistoryPointView {
|
||||||
|
|
||||||
|
/** 时间 */
|
||||||
|
private long at;
|
||||||
|
|
||||||
|
/** CPU 百分比 */
|
||||||
|
private Double cpuPercent;
|
||||||
|
|
||||||
|
/** 内存使用量 */
|
||||||
|
private Long memoryUsageBytes;
|
||||||
|
|
||||||
|
/** 内存百分比 */
|
||||||
|
private Double memoryPercent;
|
||||||
|
|
||||||
|
/** 网络接收字节 */
|
||||||
|
private Long networkRxBytes;
|
||||||
|
|
||||||
|
/** 网络发送字节 */
|
||||||
|
private Long networkTxBytes;
|
||||||
|
|
||||||
|
/** 块设备读取字节 */
|
||||||
|
private Long blockReadBytes;
|
||||||
|
|
||||||
|
/** 块设备写入字节 */
|
||||||
|
private Long blockWriteBytes;
|
||||||
|
|
||||||
|
/** 进程数 */
|
||||||
|
private Integer pids;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo.docker;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 容器历史视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DockerContainerHistoryView {
|
||||||
|
|
||||||
|
/** 容器 ID */
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/** 容器名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 服务端当前时间 */
|
||||||
|
private long serverTime;
|
||||||
|
|
||||||
|
/** 采样周期 */
|
||||||
|
private long sampleRateMs;
|
||||||
|
|
||||||
|
/** 起始时间 */
|
||||||
|
private long from;
|
||||||
|
|
||||||
|
/** 结束时间 */
|
||||||
|
private long to;
|
||||||
|
|
||||||
|
/** 历史点 */
|
||||||
|
private List<DockerContainerHistoryPointView> points = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo.docker;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 容器状态视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DockerContainerStatusView {
|
||||||
|
|
||||||
|
/** 容器 ID */
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/** 容器名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 镜像 */
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
/** 镜像 ID */
|
||||||
|
private String imageId;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private long createdAt;
|
||||||
|
|
||||||
|
/** 运行状态 */
|
||||||
|
private String state;
|
||||||
|
|
||||||
|
/** 状态描述 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 健康状态 */
|
||||||
|
private String healthStatus;
|
||||||
|
|
||||||
|
/** 启动时间 */
|
||||||
|
private String startedAt;
|
||||||
|
|
||||||
|
/** 结束时间 */
|
||||||
|
private String finishedAt;
|
||||||
|
|
||||||
|
/** 退出码 */
|
||||||
|
private Integer exitCode;
|
||||||
|
|
||||||
|
/** 重启次数 */
|
||||||
|
private int restartCount;
|
||||||
|
|
||||||
|
/** OOM 标记 */
|
||||||
|
private boolean oomKilled;
|
||||||
|
|
||||||
|
/** CPU 百分比 */
|
||||||
|
private Double cpuPercent;
|
||||||
|
|
||||||
|
/** 内存使用量 */
|
||||||
|
private Long memoryUsageBytes;
|
||||||
|
|
||||||
|
/** 内存限制 */
|
||||||
|
private Long memoryLimitBytes;
|
||||||
|
|
||||||
|
/** 内存百分比 */
|
||||||
|
private Double memoryPercent;
|
||||||
|
|
||||||
|
/** 网络接收字节 */
|
||||||
|
private Long networkRxBytes;
|
||||||
|
|
||||||
|
/** 网络发送字节 */
|
||||||
|
private Long networkTxBytes;
|
||||||
|
|
||||||
|
/** 块设备读取字节 */
|
||||||
|
private Long blockReadBytes;
|
||||||
|
|
||||||
|
/** 块设备写入字节 */
|
||||||
|
private Long blockWriteBytes;
|
||||||
|
|
||||||
|
/** 进程数 */
|
||||||
|
private Integer pids;
|
||||||
|
|
||||||
|
/** 更新时间 */
|
||||||
|
private long updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo.docker;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker 容器摘要视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DockerContainerSummaryView {
|
||||||
|
|
||||||
|
/** 容器 ID */
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/** 容器名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 镜像 */
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
/** 运行状态 */
|
||||||
|
private String state;
|
||||||
|
|
||||||
|
/** 状态描述 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 健康状态 */
|
||||||
|
private String healthStatus;
|
||||||
|
|
||||||
|
/** CPU 百分比 */
|
||||||
|
private Double cpuPercent;
|
||||||
|
|
||||||
|
/** 内存使用量 */
|
||||||
|
private Long memoryUsageBytes;
|
||||||
|
|
||||||
|
/** 内存限制 */
|
||||||
|
private Long memoryLimitBytes;
|
||||||
|
|
||||||
|
/** 内存百分比 */
|
||||||
|
private Double memoryPercent;
|
||||||
|
|
||||||
|
/** 网络接收字节 */
|
||||||
|
private Long networkRxBytes;
|
||||||
|
|
||||||
|
/** 网络发送字节 */
|
||||||
|
private Long networkTxBytes;
|
||||||
|
|
||||||
|
/** 更新时间 */
|
||||||
|
private long updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo.ups;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 历史点视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UpsHistoryPointView {
|
||||||
|
|
||||||
|
/** 采样时间 */
|
||||||
|
private long at;
|
||||||
|
|
||||||
|
/** UPS 数据时间 */
|
||||||
|
private Long upsTime;
|
||||||
|
|
||||||
|
/** 工作模式 */
|
||||||
|
private String workMode;
|
||||||
|
|
||||||
|
/** 输入电压 */
|
||||||
|
private Double inputVoltage;
|
||||||
|
|
||||||
|
/** 输入频率 */
|
||||||
|
private Double inputFrequency;
|
||||||
|
|
||||||
|
/** 输出电压 */
|
||||||
|
private Double outputVoltage;
|
||||||
|
|
||||||
|
/** 输出频率 */
|
||||||
|
private Double outputFrequency;
|
||||||
|
|
||||||
|
/** 输出负载百分比 */
|
||||||
|
private Integer outputLoadPercent;
|
||||||
|
|
||||||
|
/** 电池电压 */
|
||||||
|
private Double batteryVoltage;
|
||||||
|
|
||||||
|
/** 电池容量百分比 */
|
||||||
|
private Integer batteryCapacity;
|
||||||
|
|
||||||
|
/** 电池剩余时间 */
|
||||||
|
private Integer batteryRemainTime;
|
||||||
|
|
||||||
|
/** 温度 */
|
||||||
|
private Double temperature;
|
||||||
|
|
||||||
|
/** 是否旁路运行 */
|
||||||
|
private boolean bypassActive;
|
||||||
|
|
||||||
|
/** 是否关机中 */
|
||||||
|
private boolean shutdownActive;
|
||||||
|
|
||||||
|
/** 是否输出开启 */
|
||||||
|
private boolean outputOn;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo.ups;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 历史视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UpsHistoryView {
|
||||||
|
|
||||||
|
/** 服务端当前时间 */
|
||||||
|
private long serverTime;
|
||||||
|
|
||||||
|
/** 采样周期 */
|
||||||
|
private long sampleRateMs;
|
||||||
|
|
||||||
|
/** 查询开始时间 */
|
||||||
|
private long from;
|
||||||
|
|
||||||
|
/** 查询结束时间 */
|
||||||
|
private long to;
|
||||||
|
|
||||||
|
/** 历史点 */
|
||||||
|
private List<UpsHistoryPointView> points = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.imyeyu.api.modules.system.vo.ups;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPS 状态视图
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UpsStatusView {
|
||||||
|
|
||||||
|
/** 服务端当前时间 */
|
||||||
|
private long serverTime;
|
||||||
|
|
||||||
|
/** UPS 数据时间 */
|
||||||
|
private Long upsTime;
|
||||||
|
|
||||||
|
/** 上游主机地址 */
|
||||||
|
private String hostName;
|
||||||
|
|
||||||
|
/** 厂商名称 */
|
||||||
|
private String customer;
|
||||||
|
|
||||||
|
/** 上游版本 */
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/** 设备标识 */
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
/** UPS 类型 */
|
||||||
|
private String upsType;
|
||||||
|
|
||||||
|
/** UPS 形态 */
|
||||||
|
private String morphological;
|
||||||
|
|
||||||
|
/** 输入输出相位 */
|
||||||
|
private String ioPhase;
|
||||||
|
|
||||||
|
/** 工作模式 */
|
||||||
|
private String workMode;
|
||||||
|
|
||||||
|
/** 输入电压 */
|
||||||
|
private Double inputVoltage;
|
||||||
|
|
||||||
|
/** 输入频率 */
|
||||||
|
private Double inputFrequency;
|
||||||
|
|
||||||
|
/** 输出电压 */
|
||||||
|
private Double outputVoltage;
|
||||||
|
|
||||||
|
/** 输出频率 */
|
||||||
|
private Double outputFrequency;
|
||||||
|
|
||||||
|
/** 输出负载百分比 */
|
||||||
|
private Integer outputLoadPercent;
|
||||||
|
|
||||||
|
/** 电池电压 */
|
||||||
|
private Double batteryVoltage;
|
||||||
|
|
||||||
|
/** 电池容量百分比 */
|
||||||
|
private Integer batteryCapacity;
|
||||||
|
|
||||||
|
/** 电池剩余时间 */
|
||||||
|
private Integer batteryRemainTime;
|
||||||
|
|
||||||
|
/** 温度 */
|
||||||
|
private Double temperature;
|
||||||
|
|
||||||
|
/** 是否旁路运行 */
|
||||||
|
private boolean bypassActive;
|
||||||
|
|
||||||
|
/** 是否关机中 */
|
||||||
|
private boolean shutdownActive;
|
||||||
|
|
||||||
|
/** 是否输出开启 */
|
||||||
|
private boolean outputOn;
|
||||||
|
|
||||||
|
/** 是否充电中 */
|
||||||
|
private boolean charging;
|
||||||
|
|
||||||
|
/** 故障类型 */
|
||||||
|
private String faultType;
|
||||||
|
|
||||||
|
/** 故障明细 */
|
||||||
|
private String faultKind;
|
||||||
|
|
||||||
|
/** 告警列表 */
|
||||||
|
private List<String> warnings = new ArrayList<>();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user