mirror of https://github.com/1Panel-dev/1Panel
appstorecrontabdatabasedockerdocker-composedocker-containerdocker-imagedocker-uifilemanagerlamplnmppanel
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
10 KiB
374 lines
10 KiB
package service |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"github.com/1Panel-dev/1Panel/backend/constant" |
|
"github.com/shirou/gopsutil/v3/load" |
|
"github.com/shirou/gopsutil/v3/mem" |
|
"github.com/shirou/gopsutil/v3/net" |
|
network "net" |
|
"os" |
|
"sort" |
|
"strings" |
|
"sync" |
|
"time" |
|
|
|
"github.com/1Panel-dev/1Panel/backend/app/dto" |
|
"github.com/1Panel-dev/1Panel/backend/global" |
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd" |
|
"github.com/1Panel-dev/1Panel/backend/utils/copier" |
|
"github.com/1Panel-dev/1Panel/backend/utils/xpack" |
|
"github.com/shirou/gopsutil/v3/cpu" |
|
"github.com/shirou/gopsutil/v3/disk" |
|
"github.com/shirou/gopsutil/v3/host" |
|
) |
|
|
|
type DashboardService struct{} |
|
|
|
type IDashboardService interface { |
|
LoadOsInfo() (*dto.OsInfo, error) |
|
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) |
|
LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent |
|
|
|
Restart(operation string) error |
|
} |
|
|
|
func NewIDashboardService() IDashboardService { |
|
return &DashboardService{} |
|
} |
|
|
|
func (u *DashboardService) Restart(operation string) error { |
|
if operation != "1panel" && operation != "system" { |
|
return fmt.Errorf("handle restart operation %s failed, err: nonsupport such operation", operation) |
|
} |
|
itemCmd := fmt.Sprintf("%s 1pctl restart", cmd.SudoHandleCmd()) |
|
if operation == "system" { |
|
itemCmd = fmt.Sprintf("%s reboot", cmd.SudoHandleCmd()) |
|
} |
|
go func() { |
|
stdout, err := cmd.Exec(itemCmd) |
|
if err != nil { |
|
global.LOG.Errorf("handle %s failed, err: %v", itemCmd, stdout) |
|
} |
|
}() |
|
return nil |
|
} |
|
|
|
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) { |
|
var baseInfo dto.OsInfo |
|
hostInfo, err := host.Info() |
|
if err != nil { |
|
return nil, err |
|
} |
|
baseInfo.OS = hostInfo.OS |
|
baseInfo.Platform = hostInfo.Platform |
|
baseInfo.PlatformFamily = hostInfo.PlatformFamily |
|
baseInfo.KernelArch = hostInfo.KernelArch |
|
baseInfo.KernelVersion = hostInfo.KernelVersion |
|
|
|
diskInfo, err := disk.Usage(global.CONF.System.BaseDir) |
|
if err == nil { |
|
baseInfo.DiskSize = int64(diskInfo.Free) |
|
} |
|
|
|
if baseInfo.KernelArch == "armv7l" { |
|
baseInfo.KernelArch = "armv7" |
|
} |
|
if baseInfo.KernelArch == "x86_64" { |
|
baseInfo.KernelArch = "amd64" |
|
} |
|
return &baseInfo, nil |
|
} |
|
|
|
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) { |
|
var baseInfo dto.DashboardBase |
|
hostInfo, err := host.Info() |
|
if err != nil { |
|
return nil, err |
|
} |
|
baseInfo.Hostname = hostInfo.Hostname |
|
baseInfo.OS = hostInfo.OS |
|
baseInfo.Platform = hostInfo.Platform |
|
baseInfo.PlatformFamily = hostInfo.PlatformFamily |
|
baseInfo.PlatformVersion = hostInfo.PlatformVersion |
|
baseInfo.KernelArch = hostInfo.KernelArch |
|
baseInfo.KernelVersion = hostInfo.KernelVersion |
|
ss, _ := json.Marshal(hostInfo) |
|
baseInfo.VirtualizationSystem = string(ss) |
|
baseInfo.IpV4Addr = GetOutboundIP() |
|
httpProxy := os.Getenv("http_proxy") |
|
if httpProxy == "" { |
|
httpProxy = os.Getenv("HTTP_PROXY") |
|
} |
|
if httpProxy != "" { |
|
baseInfo.SystemProxy = httpProxy |
|
} |
|
baseInfo.SystemProxy = "noProxy" |
|
appInstall, err := appInstallRepo.ListBy() |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
baseInfo.AppInstalledNumber = len(appInstall) |
|
postgresqlDbs, err := postgresqlRepo.List() |
|
if err != nil { |
|
return nil, err |
|
} |
|
mysqlDbs, err := mysqlRepo.List() |
|
if err != nil { |
|
return nil, err |
|
} |
|
baseInfo.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs) |
|
website, err := websiteRepo.GetBy() |
|
if err != nil { |
|
return nil, err |
|
} |
|
baseInfo.WebsiteNumber = len(website) |
|
cronjobs, err := cronjobRepo.List() |
|
if err != nil { |
|
return nil, err |
|
} |
|
baseInfo.CronjobNumber = len(cronjobs) |
|
|
|
cpuInfo, err := cpu.Info() |
|
if err == nil { |
|
baseInfo.CPUModelName = cpuInfo[0].ModelName |
|
} |
|
|
|
baseInfo.CPUCores, _ = cpu.Counts(false) |
|
baseInfo.CPULogicalCores, _ = cpu.Counts(true) |
|
|
|
baseInfo.CurrentInfo = *u.LoadCurrentInfo(dto.DashboardReq{ |
|
Scope: "ioNet", |
|
IoOption: ioOption, |
|
NetOption: netOption, |
|
}) |
|
return &baseInfo, nil |
|
} |
|
|
|
func (u *DashboardService) LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent { |
|
var currentInfo dto.DashboardCurrent |
|
if req.Scope == "gpu" { |
|
currentInfo.GPUData = loadGPUInfo() |
|
currentInfo.XPUData = loadXpuInfo() |
|
} |
|
|
|
hostInfo, _ := host.Info() |
|
currentInfo.Uptime = hostInfo.Uptime |
|
if req.Scope == "basic" { |
|
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout) |
|
currentInfo.Procs = hostInfo.Procs |
|
currentInfo.CPUTotal, _ = cpu.Counts(true) |
|
totalPercent, _ := cpu.Percent(100*time.Millisecond, false) |
|
if len(totalPercent) == 1 { |
|
currentInfo.CPUUsedPercent = totalPercent[0] |
|
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal) |
|
} |
|
currentInfo.CPUPercent, _ = cpu.Percent(100*time.Millisecond, true) |
|
|
|
loadInfo, _ := load.Avg() |
|
currentInfo.Load1 = loadInfo.Load1 |
|
currentInfo.Load5 = loadInfo.Load5 |
|
currentInfo.Load15 = loadInfo.Load15 |
|
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100 |
|
|
|
memoryInfo, _ := mem.VirtualMemory() |
|
currentInfo.MemoryTotal = memoryInfo.Total |
|
currentInfo.MemoryAvailable = memoryInfo.Available |
|
currentInfo.MemoryUsed = memoryInfo.Used |
|
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent |
|
|
|
swapInfo, _ := mem.SwapMemory() |
|
currentInfo.SwapMemoryTotal = swapInfo.Total |
|
currentInfo.SwapMemoryAvailable = swapInfo.Free |
|
currentInfo.SwapMemoryUsed = swapInfo.Used |
|
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent |
|
currentInfo.DiskData = loadDiskInfo() |
|
} |
|
|
|
if req.Scope == "ioNet" { |
|
if req.IoOption == "all" { |
|
diskInfo, _ := disk.IOCounters() |
|
for _, state := range diskInfo { |
|
currentInfo.IOReadBytes += state.ReadBytes |
|
currentInfo.IOWriteBytes += state.WriteBytes |
|
currentInfo.IOCount += (state.ReadCount + state.WriteCount) |
|
currentInfo.IOReadTime += state.ReadTime |
|
currentInfo.IOWriteTime += state.WriteTime |
|
} |
|
} else { |
|
diskInfo, _ := disk.IOCounters(req.IoOption) |
|
for _, state := range diskInfo { |
|
currentInfo.IOReadBytes += state.ReadBytes |
|
currentInfo.IOWriteBytes += state.WriteBytes |
|
currentInfo.IOCount += (state.ReadCount + state.WriteCount) |
|
currentInfo.IOReadTime += state.ReadTime |
|
currentInfo.IOWriteTime += state.WriteTime |
|
} |
|
} |
|
|
|
if req.NetOption == "all" { |
|
netInfo, _ := net.IOCounters(false) |
|
if len(netInfo) != 0 { |
|
currentInfo.NetBytesSent = netInfo[0].BytesSent |
|
currentInfo.NetBytesRecv = netInfo[0].BytesRecv |
|
} |
|
} else { |
|
netInfo, _ := net.IOCounters(true) |
|
for _, state := range netInfo { |
|
if state.Name == req.NetOption { |
|
currentInfo.NetBytesSent = state.BytesSent |
|
currentInfo.NetBytesRecv = state.BytesRecv |
|
} |
|
} |
|
} |
|
} |
|
|
|
currentInfo.ShotTime = time.Now() |
|
return ¤tInfo |
|
} |
|
|
|
func GetOutboundIP() string { |
|
conn, err := network.Dial("udp", "8.8.8.8:80") |
|
|
|
if err != nil { |
|
return "IPNotFound" |
|
} |
|
defer conn.Close() |
|
|
|
localAddr := conn.LocalAddr().(*network.UDPAddr) |
|
return localAddr.IP.String() |
|
} |
|
|
|
type diskInfo struct { |
|
Type string |
|
Mount string |
|
Device string |
|
} |
|
|
|
func loadDiskInfo() []dto.DiskInfo { |
|
var datas []dto.DiskInfo |
|
stdout, err := cmd.ExecWithTimeOut("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 2*time.Second) |
|
if err != nil { |
|
stdout, err = cmd.ExecWithTimeOut("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 1*time.Second) |
|
if err != nil { |
|
return datas |
|
} |
|
} |
|
lines := strings.Split(stdout, "\n") |
|
|
|
var mounts []diskInfo |
|
var excludes = []string{"/mnt/cdrom", "/boot", "/boot/efi", "/dev", "/dev/shm", "/run/lock", "/run", "/run/shm", "/run/user"} |
|
for _, line := range lines { |
|
fields := strings.Fields(line) |
|
if len(fields) < 7 { |
|
continue |
|
} |
|
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 { |
|
continue |
|
} |
|
if strings.TrimSpace(fields[1]) == "tmpfs" { |
|
continue |
|
} |
|
if strings.Contains(fields[2], "K") { |
|
continue |
|
} |
|
if strings.Contains(fields[6], "docker") { |
|
continue |
|
} |
|
isExclude := false |
|
for _, exclude := range excludes { |
|
if exclude == fields[6] { |
|
isExclude = true |
|
} |
|
} |
|
if isExclude { |
|
continue |
|
} |
|
mounts = append(mounts, diskInfo{Type: fields[1], Device: fields[0], Mount: strings.Join(fields[6:], " ")}) |
|
} |
|
|
|
var ( |
|
wg sync.WaitGroup |
|
mu sync.Mutex |
|
) |
|
wg.Add(len(mounts)) |
|
for i := 0; i < len(mounts); i++ { |
|
go func(timeoutCh <-chan time.Time, mount diskInfo) { |
|
defer wg.Done() |
|
|
|
var itemData dto.DiskInfo |
|
itemData.Path = mount.Mount |
|
itemData.Type = mount.Type |
|
itemData.Device = mount.Device |
|
select { |
|
case <-timeoutCh: |
|
mu.Lock() |
|
datas = append(datas, itemData) |
|
mu.Unlock() |
|
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount) |
|
default: |
|
state, err := disk.Usage(mount.Mount) |
|
if err != nil { |
|
mu.Lock() |
|
datas = append(datas, itemData) |
|
mu.Unlock() |
|
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err) |
|
return |
|
} |
|
itemData.Total = state.Total |
|
itemData.Free = state.Free |
|
itemData.Used = state.Used |
|
itemData.UsedPercent = state.UsedPercent |
|
itemData.InodesTotal = state.InodesTotal |
|
itemData.InodesUsed = state.InodesUsed |
|
itemData.InodesFree = state.InodesFree |
|
itemData.InodesUsedPercent = state.InodesUsedPercent |
|
mu.Lock() |
|
datas = append(datas, itemData) |
|
mu.Unlock() |
|
} |
|
}(time.After(5*time.Second), mounts[i]) |
|
} |
|
wg.Wait() |
|
|
|
sort.Slice(datas, func(i, j int) bool { |
|
return datas[i].Path < datas[j].Path |
|
}) |
|
return datas |
|
} |
|
|
|
func loadGPUInfo() []dto.GPUInfo { |
|
list := xpack.LoadGpuInfo() |
|
if len(list) == 0 { |
|
return nil |
|
} |
|
var data []dto.GPUInfo |
|
for _, gpu := range list { |
|
var dataItem dto.GPUInfo |
|
if err := copier.Copy(&dataItem, &gpu); err != nil { |
|
continue |
|
} |
|
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit |
|
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal |
|
data = append(data, dataItem) |
|
} |
|
return data |
|
} |
|
|
|
func loadXpuInfo() []dto.XPUInfo { |
|
list := xpack.LoadXpuInfo() |
|
if len(list) == 0 { |
|
return nil |
|
} |
|
var data []dto.XPUInfo |
|
for _, gpu := range list { |
|
var dataItem dto.XPUInfo |
|
if err := copier.Copy(&dataItem, &gpu); err != nil { |
|
continue |
|
} |
|
data = append(data, dataItem) |
|
} |
|
return data |
|
}
|
|
|