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;
|
||||
|
||||
import lombok.Data;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
@@ -11,22 +11,24 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 服务器状态数据,所有动态数据左出右进,此对象由 IOC 托管
|
||||
* 服务端状态缓存
|
||||
*
|
||||
* <p>该对象只用于采集任务内部缓存,不直接作为接口协议返回。</p>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:35
|
||||
* @since 2022-01-31 15:35
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
public class ServerStatus implements TimiJava {
|
||||
|
||||
/** 动态数据更新时轴 */
|
||||
/** 采样时间轴 */
|
||||
private LinkedList<Number> updateAxis = new LinkedList<>();
|
||||
|
||||
/** 系统 */
|
||||
/** 操作系统 */
|
||||
private OS os = new OS();
|
||||
|
||||
/** CPU 使用率 */
|
||||
/** CPU */
|
||||
private CPU cpu = new CPU();
|
||||
|
||||
/** 系统内存 */
|
||||
@@ -35,17 +37,20 @@ public class ServerStatus implements TimiJava {
|
||||
/** 网络 */
|
||||
private Network network = new Network();
|
||||
|
||||
/** 本程序状态 */
|
||||
/** 硬件 */
|
||||
private Hardware hardware = new Hardware();
|
||||
|
||||
/** JVM */
|
||||
private JVM jvm = new JVM();
|
||||
|
||||
/** 磁盘 */
|
||||
private List<Partition> partitions = new ArrayList<>();
|
||||
/** 存储分区 */
|
||||
private List<StoragePartition> storagePartitions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 系统
|
||||
* 操作系统
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-12 20:55
|
||||
* @since 2022-08-12 20:55
|
||||
*/
|
||||
@Data
|
||||
public static class OS {
|
||||
@@ -58,10 +63,10 @@ public class ServerStatus implements TimiJava {
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟机状态
|
||||
* JVM
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 21:10
|
||||
* @since 2022-01-31 21:10
|
||||
*/
|
||||
@Data
|
||||
public static class JVM {
|
||||
@@ -69,80 +74,83 @@ public class ServerStatus implements TimiJava {
|
||||
/** 启动时间 */
|
||||
private long bootAt;
|
||||
|
||||
/** JVM 名称 */
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** JVM 版本 */
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 内存 */
|
||||
/** GC 名称 */
|
||||
private String gcName;
|
||||
|
||||
/** 堆内存 */
|
||||
private Memory memory = new Memory();
|
||||
|
||||
/** 内存回收 */
|
||||
private ZGC zgc = new ZGC();
|
||||
/** GC 状态 */
|
||||
private GC gc = new GC();
|
||||
|
||||
/**
|
||||
* 内存
|
||||
* JVM 内存
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-12 20:32
|
||||
* @since 2022-08-12 20:32
|
||||
*/
|
||||
@Data
|
||||
public static class Memory {
|
||||
|
||||
/** 初始化 */
|
||||
/** 初始大小 */
|
||||
private long init;
|
||||
|
||||
/** 最大 */
|
||||
/** 最大大小 */
|
||||
private long max;
|
||||
|
||||
/** 已使用 */
|
||||
/** 已用大小 */
|
||||
private final Deque<Number> used = new ArrayDeque<>();
|
||||
|
||||
/** 已提交 */
|
||||
/** 已提交大小 */
|
||||
private final Deque<Number> committed = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存回收
|
||||
* GC 状态
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-12 20:32
|
||||
* @since 2022-08-12 20:32
|
||||
*/
|
||||
@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> pausesTime = new ArrayDeque<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中央处理器
|
||||
* CPU
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:40
|
||||
* @since 2022-01-31 15:40
|
||||
*/
|
||||
@Data
|
||||
public static class CPU {
|
||||
@@ -150,19 +158,19 @@ public class ServerStatus implements TimiJava {
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 物理核心数量 */
|
||||
/** 物理核心数 */
|
||||
private int coreCount;
|
||||
|
||||
/** 线程数量 */
|
||||
/** 逻辑核心数 */
|
||||
private int logicalCount;
|
||||
|
||||
/** 温度 */
|
||||
private double temperature;
|
||||
|
||||
/** 系统使用 */
|
||||
/** 系统占用 */
|
||||
private final Deque<Number> system = new ArrayDeque<>();
|
||||
|
||||
/** 总共已使用 */
|
||||
/** 总占用 */
|
||||
private final Deque<Number> used = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
@@ -170,7 +178,7 @@ public class ServerStatus implements TimiJava {
|
||||
* 系统内存
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:50
|
||||
* @since 2022-01-31 15:50
|
||||
*/
|
||||
@Data
|
||||
public static class Memory {
|
||||
@@ -178,69 +186,174 @@ public class ServerStatus implements TimiJava {
|
||||
/** 物理内存大小 */
|
||||
private long size;
|
||||
|
||||
/** 交换区大小 */
|
||||
/** 交换分区大小 */
|
||||
private long swapSize;
|
||||
|
||||
/** 已使用 */
|
||||
/** 已用内存 */
|
||||
private final Deque<Number> used = new ArrayDeque<>();
|
||||
|
||||
/** 交换区已使用 */
|
||||
/** 已用交换分区 */
|
||||
private final Deque<Number> swapUsed = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 网卡网速
|
||||
* 网络
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-10 21:41
|
||||
* @since 2022-08-10 21:41
|
||||
*/
|
||||
@Data
|
||||
public static class Network {
|
||||
|
||||
/** 网卡名称 */
|
||||
private String name;
|
||||
|
||||
/** 累计接收 */
|
||||
private long recvTotal;
|
||||
|
||||
/** 累计发送 */
|
||||
private long sentTotal;
|
||||
|
||||
/** 实时接收速度 */
|
||||
/** 实时接收速率 */
|
||||
private long recvNow;
|
||||
|
||||
/** 实时发送速度 */
|
||||
/** 实时发送速率 */
|
||||
private long sentNow;
|
||||
|
||||
/** 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> recv = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分区
|
||||
* 硬件信息
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 20:19
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@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 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.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.vo.SystemStatusHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView;
|
||||
import com.imyeyu.api.modules.system.vo.TempAttachRequest;
|
||||
import com.imyeyu.io.IO;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
@@ -12,6 +14,7 @@ import com.imyeyu.spring.annotation.AOPLog;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -23,10 +26,10 @@ import java.io.IOException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
/**
|
||||
* 服务器控制接口
|
||||
* 服务端控制器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 22:47
|
||||
* @since 2022-01-31 22:47
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@@ -34,7 +37,7 @@ import java.util.concurrent.Semaphore;
|
||||
@RequestMapping("/system/server")
|
||||
public class SystemController {
|
||||
|
||||
private final ServerStatus serverStatus;
|
||||
private final StatusService statusService;
|
||||
private final SystemService service;
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
@@ -42,16 +45,36 @@ public class SystemController {
|
||||
private final Semaphore rebootSemaphore = 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
|
||||
@PostMapping("/update")
|
||||
@@ -85,8 +108,7 @@ public class SystemController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止系统
|
||||
*
|
||||
* 关闭系统
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestMapping("/shutdown")
|
||||
@@ -111,7 +133,11 @@ public class SystemController {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 临时接口
|
||||
/**
|
||||
* 上传临时附件
|
||||
*
|
||||
* @param request 上传请求
|
||||
*/
|
||||
@AOPLog
|
||||
@PostMapping("/attach")
|
||||
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;
|
||||
|
||||
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.service.SettingService;
|
||||
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.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.scheduling.support.CronTrigger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.GlobalMemory;
|
||||
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.MemoryMXBean;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 服务器状态收集任务
|
||||
* 服务端状态采集任务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:18
|
||||
* @since 2022-01-31 15:18
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ServerStatusTask implements SchedulingConfigurer, TimiJava {
|
||||
public class ServerStatusTask implements SchedulingConfigurer {
|
||||
|
||||
private final ServerStatus status;
|
||||
private final SettingService settingService;
|
||||
|
||||
private GlobalMemory globalMemory; // 内存
|
||||
private MemoryMXBean jvmMemory; // JVM 内存
|
||||
private OperatingSystem os; // 操作系统
|
||||
private CentralProcessor processor; // 中央处理器
|
||||
private HardwareAbstractionLayer hardware; // 硬件
|
||||
|
||||
private long[] lastCPUTicks; // CPU 上一时刻状态
|
||||
private long lastLinuxAPIMemoryUsed; // 上一周期 JVM 内存大小
|
||||
private final List<StatusCollector> statusCollectors;
|
||||
|
||||
@Override
|
||||
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
||||
lastLinuxAPIMemoryUsed = -1;
|
||||
SystemInfo systemInfo = new SystemInfo();
|
||||
HardwareAbstractionLayer hardware = systemInfo.getHardware();
|
||||
|
||||
// 系统信息
|
||||
SystemInfo system = new SystemInfo();
|
||||
|
||||
// 硬件信息
|
||||
hardware = system.getHardware();
|
||||
|
||||
// 操作系统
|
||||
os = system.getOperatingSystem();
|
||||
|
||||
// ---------- 静态数据 ----------
|
||||
|
||||
// 系统
|
||||
status.getOs().setName(OS.NAME);
|
||||
status.getOs().setBootAt(os.getSystemBootTime() * 1000);
|
||||
|
||||
// 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());
|
||||
}
|
||||
StatusCollectContext context = new StatusCollectContext(
|
||||
status,
|
||||
hardware.getMemory(),
|
||||
ManagementFactory.getMemoryMXBean(),
|
||||
settingService,
|
||||
hardware.getComputerSystem(),
|
||||
systemInfo.getOperatingSystem(),
|
||||
hardware.getProcessor(),
|
||||
hardware,
|
||||
Time.now()
|
||||
);
|
||||
synchronized (status) {
|
||||
for (StatusCollector collector : statusCollectors) {
|
||||
collector.initialize(context);
|
||||
}
|
||||
}
|
||||
|
||||
taskRegistrar.addTriggerTask(() -> {
|
||||
long now = Time.now();
|
||||
|
||||
// ---------- JVM 内存 ----------
|
||||
long nowLinuxAPIMemoryUsed = jvmMemory.getHeapMemoryUsage().getUsed();
|
||||
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" -> {
|
||||
gcPauses += gc.getCollectionCount();
|
||||
gcPausesTime += gc.getCollectionTime();
|
||||
if (status.getJvm().getZgc().getPauses() < gcPauses) {
|
||||
// 发生 GC 回收
|
||||
status.getJvm().getZgc().setLastPauseAt(now);
|
||||
status.getJvm().getZgc().setLastRecoverySize(linuxAPIMemoryDiff);
|
||||
}
|
||||
}
|
||||
synchronized (status) {
|
||||
context.setCollectAt(Time.now());
|
||||
for (StatusCollector collector : statusCollectors) {
|
||||
collector.collect(context);
|
||||
}
|
||||
status.getUpdateAxis().addLast(context.getCollectAt());
|
||||
if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < status.getUpdateAxis().size()) {
|
||||
status.getUpdateAxis().pollFirst();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(triggerContext));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
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)) {
|
||||
key = req.getParameter("key");
|
||||
key = req.getParameter("token");
|
||||
}
|
||||
String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_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