mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-05 05:36:07 +08:00
first commit
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"clicd/internal/lxc"
|
||||
)
|
||||
|
||||
type HostInfo struct {
|
||||
CPU CpuInfo `json:"cpu"`
|
||||
RAM MemoryInfo `json:"ram"`
|
||||
Disk DiskInfo `json:"disk"`
|
||||
Network NetworkInfo `json:"network"`
|
||||
DiskIO DiskIOInfo `json:"disk_io"`
|
||||
Load LoadInfo `json:"load"`
|
||||
}
|
||||
|
||||
type LoadInfo struct {
|
||||
Load1 float64 `json:"load1"`
|
||||
Load5 float64 `json:"load5"`
|
||||
Load15 float64 `json:"load15"`
|
||||
}
|
||||
|
||||
type CpuInfo struct {
|
||||
Cores int `json:"cores"`
|
||||
Usage float64 `json:"usage_pct"`
|
||||
}
|
||||
|
||||
type MemoryInfo struct {
|
||||
TotalMB int64 `json:"total_mb"`
|
||||
UsedMB int64 `json:"used_mb"`
|
||||
FreeMB int64 `json:"free_mb"`
|
||||
}
|
||||
|
||||
type DiskInfo struct {
|
||||
TotalGB float64 `json:"total_gb"`
|
||||
UsedGB float64 `json:"used_gb"`
|
||||
FreeGB float64 `json:"free_gb"`
|
||||
}
|
||||
|
||||
type NetworkInfo struct {
|
||||
RXBytes uint64 `json:"rx_bytes"`
|
||||
TXBytes uint64 `json:"tx_bytes"`
|
||||
RXBps float64 `json:"rx_bps"`
|
||||
TXBps float64 `json:"tx_bps"`
|
||||
PublicIPv4 string `json:"public_ipv4"`
|
||||
PublicIPv4Interface string `json:"public_ipv4_interface"`
|
||||
PublicIPv6 string `json:"public_ipv6"`
|
||||
PublicIPv6Interface string `json:"public_ipv6_interface"`
|
||||
IPv6Prefixes []lxc.IPv6PrefixInfo `json:"ipv6_prefixes"`
|
||||
}
|
||||
|
||||
type DiskIOInfo struct {
|
||||
ReadBytes uint64 `json:"read_bytes"`
|
||||
WriteBytes uint64 `json:"write_bytes"`
|
||||
ReadBps float64 `json:"read_bps"`
|
||||
WriteBps float64 `json:"write_bps"`
|
||||
}
|
||||
|
||||
var hostCPUMu sync.Mutex
|
||||
var lastHostCPU cpuTimes
|
||||
var hostIOMu sync.Mutex
|
||||
var lastHostIO hostIOSample
|
||||
|
||||
type cpuTimes struct {
|
||||
Total uint64
|
||||
Idle uint64
|
||||
}
|
||||
|
||||
type hostIOSample struct {
|
||||
RXBytes uint64
|
||||
TXBytes uint64
|
||||
ReadBytes uint64
|
||||
WriteBytes uint64
|
||||
At int64
|
||||
}
|
||||
|
||||
func getHostInfo() HostInfo {
|
||||
info := HostInfo{
|
||||
CPU: CpuInfo{Cores: runtime.NumCPU()},
|
||||
}
|
||||
|
||||
info.RAM = getMemoryInfo()
|
||||
info.Disk = getDiskInfo()
|
||||
info.CPU.Usage = getCPUUsage()
|
||||
info.Network, info.DiskIO = getHostRates()
|
||||
info.Load = getLoadInfo()
|
||||
return info
|
||||
}
|
||||
|
||||
func getMemoryInfo() MemoryInfo {
|
||||
f, err := os.Open("/proc/meminfo")
|
||||
if err != nil {
|
||||
return MemoryInfo{TotalMB: 0, UsedMB: 0, FreeMB: 0}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var total, available, free int64
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
val, _ := strconv.ParseInt(fields[1], 10, 64)
|
||||
switch fields[0] {
|
||||
case "MemTotal:":
|
||||
total = val / 1024
|
||||
case "MemAvailable:":
|
||||
available = val / 1024
|
||||
case "MemFree:":
|
||||
free = val / 1024
|
||||
}
|
||||
}
|
||||
|
||||
used := total - available
|
||||
if available == 0 {
|
||||
used = total - free
|
||||
}
|
||||
|
||||
return MemoryInfo{
|
||||
TotalMB: total,
|
||||
UsedMB: used,
|
||||
FreeMB: available,
|
||||
}
|
||||
}
|
||||
|
||||
func getDiskInfo() DiskInfo {
|
||||
var stat syscall.Statfs_t
|
||||
if err := syscall.Statfs("/", &stat); err != nil {
|
||||
// Try command-based fallback
|
||||
cmd := exec.Command("df", "-BG", "/")
|
||||
output, err := cmd.Output()
|
||||
if err == nil {
|
||||
lines := strings.Split(string(output), "\n")
|
||||
if len(lines) >= 2 {
|
||||
fields := strings.Fields(lines[1])
|
||||
if len(fields) >= 4 {
|
||||
total, _ := parseSizeGBf(fields[1])
|
||||
used, _ := parseSizeGBf(fields[2])
|
||||
free, _ := parseSizeGBf(fields[3])
|
||||
return DiskInfo{TotalGB: total, UsedGB: used, FreeGB: free}
|
||||
}
|
||||
}
|
||||
}
|
||||
return DiskInfo{}
|
||||
}
|
||||
|
||||
total := float64(int64(stat.Blocks)*int64(stat.Bsize)) / (1024 * 1024 * 1024)
|
||||
free := float64(int64(stat.Bavail)*int64(stat.Bsize)) / (1024 * 1024 * 1024)
|
||||
used := total - free
|
||||
|
||||
return DiskInfo{
|
||||
TotalGB: total,
|
||||
UsedGB: used,
|
||||
FreeGB: free,
|
||||
}
|
||||
}
|
||||
|
||||
func getCPUUsage() float64 {
|
||||
current, err := readCPUTimes()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
hostCPUMu.Lock()
|
||||
defer hostCPUMu.Unlock()
|
||||
|
||||
if lastHostCPU.Total == 0 {
|
||||
lastHostCPU = current
|
||||
return 0
|
||||
}
|
||||
|
||||
totalDelta := current.Total - lastHostCPU.Total
|
||||
idleDelta := current.Idle - lastHostCPU.Idle
|
||||
lastHostCPU = current
|
||||
|
||||
if totalDelta == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
usage := (1 - float64(idleDelta)/float64(totalDelta)) * 100
|
||||
if usage < 0 {
|
||||
return 0
|
||||
}
|
||||
if usage > 100 {
|
||||
return 100
|
||||
}
|
||||
return usage
|
||||
}
|
||||
|
||||
func readCPUTimes() (cpuTimes, error) {
|
||||
f, err := os.Open("/proc/stat")
|
||||
if err != nil {
|
||||
return cpuTimes{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
if !scanner.Scan() {
|
||||
return cpuTimes{}, scanner.Err()
|
||||
}
|
||||
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) < 8 || fields[0] != "cpu" {
|
||||
return cpuTimes{}, nil
|
||||
}
|
||||
|
||||
var values []uint64
|
||||
for _, field := range fields[1:] {
|
||||
value, _ := strconv.ParseUint(field, 10, 64)
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
var total uint64
|
||||
for _, value := range values {
|
||||
total += value
|
||||
}
|
||||
|
||||
idle := values[3]
|
||||
if len(values) > 4 {
|
||||
idle += values[4]
|
||||
}
|
||||
|
||||
return cpuTimes{Total: total, Idle: idle}, nil
|
||||
}
|
||||
|
||||
func parseSizeGB(s string) (int64, error) {
|
||||
s = strings.TrimSuffix(s, "G")
|
||||
s = strings.TrimSpace(s)
|
||||
val, err := strconv.ParseInt(s, 10, 64)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func parseSizeGBf(s string) (float64, error) {
|
||||
s = strings.TrimSuffix(s, "G")
|
||||
s = strings.TrimSpace(s)
|
||||
val, err := strconv.ParseFloat(s, 64)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func getHostRates() (NetworkInfo, DiskIOInfo) {
|
||||
rx, tx := readHostNetworkBytes()
|
||||
readBytes, writeBytes := readHostDiskBytes()
|
||||
now := unixNano()
|
||||
|
||||
network := NetworkInfo{RXBytes: rx, TXBytes: tx}
|
||||
publicIPv4 := lxc.DetectPublicIPv4()
|
||||
network.PublicIPv4 = publicIPv4.Address
|
||||
network.PublicIPv4Interface = publicIPv4.Interface
|
||||
network.IPv6Prefixes = lxc.DetectPublicIPv6Prefixes()
|
||||
if len(network.IPv6Prefixes) > 0 {
|
||||
network.PublicIPv6 = network.IPv6Prefixes[0].Address
|
||||
network.PublicIPv6Interface = network.IPv6Prefixes[0].Interface
|
||||
}
|
||||
diskIO := DiskIOInfo{ReadBytes: readBytes, WriteBytes: writeBytes}
|
||||
|
||||
hostIOMu.Lock()
|
||||
defer hostIOMu.Unlock()
|
||||
|
||||
if lastHostIO.At == 0 {
|
||||
lastHostIO = hostIOSample{RXBytes: rx, TXBytes: tx, ReadBytes: readBytes, WriteBytes: writeBytes, At: now}
|
||||
return network, diskIO
|
||||
}
|
||||
|
||||
elapsed := float64(now-lastHostIO.At) / 1_000_000_000
|
||||
if elapsed > 0 {
|
||||
if rx >= lastHostIO.RXBytes {
|
||||
network.RXBps = float64(rx-lastHostIO.RXBytes) / elapsed
|
||||
}
|
||||
if tx >= lastHostIO.TXBytes {
|
||||
network.TXBps = float64(tx-lastHostIO.TXBytes) / elapsed
|
||||
}
|
||||
if readBytes >= lastHostIO.ReadBytes {
|
||||
diskIO.ReadBps = float64(readBytes-lastHostIO.ReadBytes) / elapsed
|
||||
}
|
||||
if writeBytes >= lastHostIO.WriteBytes {
|
||||
diskIO.WriteBps = float64(writeBytes-lastHostIO.WriteBytes) / elapsed
|
||||
}
|
||||
}
|
||||
|
||||
lastHostIO = hostIOSample{RXBytes: rx, TXBytes: tx, ReadBytes: readBytes, WriteBytes: writeBytes, At: now}
|
||||
return network, diskIO
|
||||
}
|
||||
|
||||
func readHostNetworkBytes() (uint64, uint64) {
|
||||
entries, err := os.ReadDir("/sys/class/net")
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
var rx, tx uint64
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if name == "lo" {
|
||||
continue
|
||||
}
|
||||
rx += readUintFile("/sys/class/net/" + name + "/statistics/rx_bytes")
|
||||
tx += readUintFile("/sys/class/net/" + name + "/statistics/tx_bytes")
|
||||
}
|
||||
return rx, tx
|
||||
}
|
||||
|
||||
func readHostDiskBytes() (uint64, uint64) {
|
||||
f, err := os.Open("/proc/diskstats")
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var readSectors, writeSectors uint64
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) < 14 {
|
||||
continue
|
||||
}
|
||||
device := fields[2]
|
||||
if strings.HasPrefix(device, "loop") ||
|
||||
strings.HasPrefix(device, "ram") ||
|
||||
strings.HasPrefix(device, "fd") ||
|
||||
strings.HasPrefix(device, "sr") {
|
||||
continue
|
||||
}
|
||||
read, _ := strconv.ParseUint(fields[5], 10, 64)
|
||||
write, _ := strconv.ParseUint(fields[9], 10, 64)
|
||||
readSectors += read
|
||||
writeSectors += write
|
||||
}
|
||||
return readSectors * 512, writeSectors * 512
|
||||
}
|
||||
|
||||
func readUintFile(path string) uint64 {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
value, _ := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||
return value
|
||||
}
|
||||
|
||||
func unixNano() int64 {
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
|
||||
func getLoadInfo() LoadInfo {
|
||||
f, err := os.Open("/proc/loadavg")
|
||||
if err != nil {
|
||||
return LoadInfo{}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
if !scanner.Scan() {
|
||||
return LoadInfo{}
|
||||
}
|
||||
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) < 3 {
|
||||
return LoadInfo{}
|
||||
}
|
||||
|
||||
load1, _ := strconv.ParseFloat(fields[0], 64)
|
||||
load5, _ := strconv.ParseFloat(fields[1], 64)
|
||||
load15, _ := strconv.ParseFloat(fields[2], 64)
|
||||
return LoadInfo{Load1: load1, Load5: load5, Load15: load15}
|
||||
}
|
||||
Reference in New Issue
Block a user