package service import ( "encoding/json" "fmt" "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/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 { 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) 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) dbs, err := mysqlRepo.List() if err != nil { return nil, err } baseInfo.DatabaseNumber = len(dbs) 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("2006-01-02 15:04:05") 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() 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 { 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 fields[1] == "tmpfs" { continue } if strings.Contains(fields[2], "M") || 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: 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 }