mirror of https://github.com/1Panel-dev/1Panel
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.
326 lines
9.0 KiB
326 lines
9.0 KiB
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
|
"github.com/shirou/gopsutil/v3/cpu"
|
|
"github.com/shirou/gopsutil/v3/disk"
|
|
"github.com/shirou/gopsutil/v3/host"
|
|
"github.com/shirou/gopsutil/v3/load"
|
|
"github.com/shirou/gopsutil/v3/mem"
|
|
"github.com/shirou/gopsutil/v3/net"
|
|
)
|
|
|
|
type DashboardService struct{}
|
|
|
|
type IDashboardService interface {
|
|
LoadOsInfo() (*dto.OsInfo, error)
|
|
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
|
|
LoadCurrentInfo(ioOption string, netOption string) *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)
|
|
|
|
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(ioOption, netOption)
|
|
return &baseInfo, nil
|
|
}
|
|
|
|
func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent {
|
|
var currentInfo dto.DashboardCurrent
|
|
hostInfo, _ := host.Info()
|
|
currentInfo.Uptime = hostInfo.Uptime
|
|
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(0, false)
|
|
if len(totalPercent) == 1 {
|
|
currentInfo.CPUUsedPercent = totalPercent[0]
|
|
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
|
|
}
|
|
currentInfo.CPUPercent, _ = cpu.Percent(0, 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()
|
|
currentInfo.GPUData = loadGPUInfo()
|
|
|
|
if 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(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 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 == netOption {
|
|
currentInfo.NetBytesSent = state.BytesSent
|
|
currentInfo.NetBytesRecv = state.BytesRecv
|
|
}
|
|
}
|
|
}
|
|
|
|
currentInfo.ShotTime = time.Now()
|
|
return ¤tInfo
|
|
}
|
|
|
|
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
|
|
}
|