Compare commits
2 Commits
master
...
760784a5ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
760784a5ac | ||
|
|
fcbaa5be2f |
@@ -1,6 +1,8 @@
|
|||||||
package installcmd
|
package installcmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"yizhisec.com/hsv2/forge/internal/controller/installer"
|
"yizhisec.com/hsv2/forge/internal/controller/installer"
|
||||||
)
|
)
|
||||||
@@ -8,7 +10,6 @@ import (
|
|||||||
func Check() *cobra.Command {
|
func Check() *cobra.Command {
|
||||||
var (
|
var (
|
||||||
workdir string
|
workdir string
|
||||||
target string
|
|
||||||
ignoreDisk bool
|
ignoreDisk bool
|
||||||
ignoreMemory bool
|
ignoreMemory bool
|
||||||
ignoreCPU bool
|
ignoreCPU bool
|
||||||
@@ -18,9 +19,20 @@ func Check() *cobra.Command {
|
|||||||
Use: "check",
|
Use: "check",
|
||||||
Short: "Check system requirements",
|
Short: "Check system requirements",
|
||||||
Long: `Check system requirements for the project.`,
|
Long: `Check system requirements for the project.`,
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
if os.Getenv("FORGE_NO_CHECK_DISK") == "true" {
|
||||||
|
ignoreDisk = true
|
||||||
|
}
|
||||||
|
if os.Getenv("FORGE_NO_CHECK_MEMORY") == "true" {
|
||||||
|
ignoreMemory = true
|
||||||
|
}
|
||||||
|
if os.Getenv("FORGE_NO_CHECK_CPU") == "true" {
|
||||||
|
ignoreCPU = true
|
||||||
|
}
|
||||||
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
_installer := installer.NewInstaller(workdir, target)
|
_installer := installer.NewInstaller(workdir)
|
||||||
return _installer.Check(
|
return _installer.HardwareCheck(
|
||||||
cmd.Context(),
|
cmd.Context(),
|
||||||
installer.WithIgnoreDiskCheck(ignoreDisk),
|
installer.WithIgnoreDiskCheck(ignoreDisk),
|
||||||
installer.WithIgnoreMemoryCheck(ignoreMemory),
|
installer.WithIgnoreMemoryCheck(ignoreMemory),
|
||||||
@@ -30,10 +42,6 @@ func Check() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
|
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
|
||||||
_cmd.Flags().StringVar(&target, "target", "self", "Target")
|
|
||||||
_cmd.Flags().BoolVar(&ignoreDisk, "ignore-kheck-disk", false, "ignore disk requirement check result")
|
|
||||||
_cmd.Flags().BoolVar(&ignoreMemory, "ignore-check-memory", false, "ignore memory requirement check result")
|
|
||||||
_cmd.Flags().BoolVar(&ignoreCPU, "ignore-check-cpu", false, "ignore cpu requirement check result")
|
|
||||||
|
|
||||||
return _cmd
|
return _cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,21 +9,23 @@ func K0s() *cobra.Command {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
workdir string
|
workdir string
|
||||||
target string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_cmd := &cobra.Command{
|
_cmd := &cobra.Command{
|
||||||
Use: "k0s",
|
Use: "k0s",
|
||||||
Short: "Install k0s",
|
Short: "Install k0s",
|
||||||
Long: "Install k0s",
|
Long: "Install k0s",
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
i := installer.NewInstaller(workdir)
|
||||||
|
return i.CheckOK(cmd.Context())
|
||||||
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
_installer := installer.NewInstaller(workdir, target)
|
_installer := installer.NewInstaller(workdir)
|
||||||
return _installer.K0s(cmd.Context())
|
return _installer.K0s(cmd.Context())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_cmd.PersistentFlags().StringVar(&workdir, "workdir", "/root/hs-installation", "working directory")
|
_cmd.PersistentFlags().StringVar(&workdir, "workdir", "/root/hs-installation", "working directory")
|
||||||
_cmd.PersistentFlags().StringVar(&target, "target", "self", "target directory")
|
|
||||||
|
|
||||||
return _cmd
|
return _cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,24 @@ func Prepare() *cobra.Command {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
workdir string
|
workdir string
|
||||||
target string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_cmd := &cobra.Command{
|
_cmd := &cobra.Command{
|
||||||
Use: "prepare",
|
Use: "prepare",
|
||||||
Short: "Prepare for installation",
|
Short: "Prepare for installation",
|
||||||
Long: "Prepare for installation",
|
Long: "Prepare for installation",
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
i := installer.NewInstaller(workdir)
|
||||||
|
return i.CheckOK(cmd.Context())
|
||||||
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
_installer := installer.NewInstaller(workdir, target)
|
_installer := installer.NewInstaller(workdir)
|
||||||
_installer.Prepare(cmd.Context())
|
_installer.Prepare(cmd.Context())
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
|
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
|
||||||
_cmd.Flags().StringVar(&target, "target", "self", "Target")
|
|
||||||
|
|
||||||
return _cmd
|
return _cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package installer
|
package installer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"yizhisec.com/hsv2/forge/pkg/logger"
|
"yizhisec.com/hsv2/forge/pkg/logger"
|
||||||
"yizhisec.com/hsv2/forge/pkg/syscheck"
|
"yizhisec.com/hsv2/forge/pkg/syscheck"
|
||||||
|
"yizhisec.com/hsv2/forge/pkg/tool/human"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckOption func(*checkOpt)
|
type CheckOption func(*checkOpt)
|
||||||
@@ -13,6 +20,7 @@ type checkOpt struct {
|
|||||||
ignoreDisk bool
|
ignoreDisk bool
|
||||||
ignoreMemory bool
|
ignoreMemory bool
|
||||||
ignoreCPU bool
|
ignoreCPU bool
|
||||||
|
noWriteDown bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithIgnoreDiskCheck(ignore bool) CheckOption {
|
func WithIgnoreDiskCheck(ignore bool) CheckOption {
|
||||||
@@ -33,9 +41,177 @@ func WithIgnoreCPUCheck(ignore bool) CheckOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *installer) Check(ctx context.Context, opts ...CheckOption) error {
|
func WithNoWriteDown(noWriteDown bool) CheckOption {
|
||||||
|
return func(o *checkOpt) {
|
||||||
|
o.noWriteDown = noWriteDown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HardwareCheckResult struct {
|
||||||
|
Timestamp int64 // ms
|
||||||
|
DiskSize int64 // bytes, if err, -1; if ignore, 0
|
||||||
|
DiskReadSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
||||||
|
DiskWriteSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
||||||
|
MemorySize int64 // bytes, if err, -1; if ignore, 0
|
||||||
|
MemoryReadSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
||||||
|
MemoryWriteSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
||||||
|
CPUCores int64 // if err, -1; if ignore, 0
|
||||||
|
CPUFrequency int64 // MHz, if err, -1; if ignore, 0
|
||||||
|
CPUSupportAES bool
|
||||||
|
CPUIsX86V2 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HardwareCheckResult) Write(filename string) error {
|
||||||
|
content := []byte(fmt.Sprintf(`timestamp=%d
|
||||||
|
disk_size=%d
|
||||||
|
disk_read_speed=%d
|
||||||
|
disk_write_speed=%d
|
||||||
|
memory_size=%d
|
||||||
|
memory_read_speed=%d
|
||||||
|
memory_write_speed=%d
|
||||||
|
cpu_cores=%d
|
||||||
|
cpu_frequency=%d
|
||||||
|
cpu_support_aes=%t
|
||||||
|
cpu_is_x86_v2=%t`,
|
||||||
|
h.Timestamp,
|
||||||
|
h.DiskSize,
|
||||||
|
h.DiskReadSpeed,
|
||||||
|
h.DiskWriteSpeed,
|
||||||
|
h.MemorySize,
|
||||||
|
h.MemoryReadSpeed,
|
||||||
|
h.MemoryWriteSpeed,
|
||||||
|
h.CPUCores,
|
||||||
|
h.CPUFrequency,
|
||||||
|
h.CPUSupportAES,
|
||||||
|
h.CPUIsX86V2,
|
||||||
|
))
|
||||||
|
return os.WriteFile(filename, content, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HardwareCheckResult) Load(filename string) error {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "timestamp":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.Timestamp = -1
|
||||||
|
} else {
|
||||||
|
h.Timestamp = v
|
||||||
|
}
|
||||||
|
case "disk_size":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.DiskSize = -1
|
||||||
|
} else {
|
||||||
|
h.DiskSize = v
|
||||||
|
}
|
||||||
|
case "disk_read_speed":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.DiskReadSpeed = -1
|
||||||
|
} else {
|
||||||
|
h.DiskReadSpeed = v
|
||||||
|
}
|
||||||
|
case "disk_write_speed":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.DiskWriteSpeed = -1
|
||||||
|
} else {
|
||||||
|
h.DiskWriteSpeed = v
|
||||||
|
}
|
||||||
|
case "memory_size":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.MemorySize = -1
|
||||||
|
} else {
|
||||||
|
h.MemorySize = v
|
||||||
|
}
|
||||||
|
case "memory_read_speed":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.MemoryReadSpeed = -1
|
||||||
|
} else {
|
||||||
|
h.MemoryReadSpeed = v
|
||||||
|
}
|
||||||
|
case "memory_write_speed":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.MemoryWriteSpeed = -1
|
||||||
|
} else {
|
||||||
|
h.MemoryWriteSpeed = v
|
||||||
|
}
|
||||||
|
case "cpu_cores":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.CPUCores = -1
|
||||||
|
} else {
|
||||||
|
h.CPUCores = v
|
||||||
|
}
|
||||||
|
case "cpu_frequency":
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
h.CPUFrequency = -1
|
||||||
|
} else {
|
||||||
|
h.CPUFrequency = v
|
||||||
|
}
|
||||||
|
case "cpu_support_aes":
|
||||||
|
v, err := strconv.ParseBool(value)
|
||||||
|
if err == nil {
|
||||||
|
h.CPUSupportAES = v
|
||||||
|
}
|
||||||
|
// bool fields don't use -1, keep default false on error
|
||||||
|
case "cpu_is_x86_v2":
|
||||||
|
v, err := strconv.ParseBool(value)
|
||||||
|
if err == nil {
|
||||||
|
h.CPUIsX86V2 = v
|
||||||
|
}
|
||||||
|
// bool fields don't use -1, keep default false on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return fmt.Errorf("failed to read file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HardwareCheckResult) Pass(opts ...CheckOption) error {
|
||||||
|
const (
|
||||||
|
DISK_SIZE int64 = 480 * 1024 * 1024 * 1024 // 480GB
|
||||||
|
DISK_READ_SPEED int64 = 1024 * 1024 * 1024 // 1024MB/s
|
||||||
|
DISK_WRITE_SPEED int64 = 500 * 1024 * 1024 // 500MB/s
|
||||||
|
MEMORY_SIZE int64 = 15.5 * 1024 * 1024 * 1024 // 15.5GB
|
||||||
|
MEMORY_READ_SPEED int64 = 1024 * 1024 * 1024 // 1024MB/s
|
||||||
|
MEMORY_WRITE_SPEED int64 = 500 * 1024 * 1024 // 500MB/s
|
||||||
|
CPUCORES int64 = 8
|
||||||
|
CPU_FREQUENCY int64 = 2.0 * 1024 // 2.0GHz
|
||||||
|
CPU_NEED_V2 = true
|
||||||
|
CPU_NEED_AES = true
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
now = time.Now()
|
||||||
o = &checkOpt{}
|
o = &checkOpt{}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,98 +219,195 @@ func (i *installer) Check(ctx context.Context, opts ...CheckOption) error {
|
|||||||
fn(o)
|
fn(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("☑️ installer.Check: Starting system checks...")
|
if h.Timestamp < now.AddDate(0, 0, -1).UnixMilli() {
|
||||||
|
return fmt.Errorf("上次检测结果已失效, 请重新检测")
|
||||||
if err = i.targetOK(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Check disk space: >= 500GB
|
if o.ignoreDisk || h.DiskSize+h.DiskReadSpeed+h.DiskWriteSpeed == -3 {
|
||||||
logger.Info("☑️ installer.Check: Checking disk space...")
|
goto PASS_MEMORY
|
||||||
diskSpaceResult, err := syscheck.CheckDiskSpace(ctx, i, 500)
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("❌ installer.Check: Failed to check disk space: %v", err)
|
|
||||||
return fmt.Errorf("failed to check disk space: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !diskSpaceResult.Passed {
|
if h.DiskSize < DISK_SIZE {
|
||||||
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", diskSpaceResult.Name, diskSpaceResult.Message, diskSpaceResult.Expected, diskSpaceResult.Actual)
|
return fmt.Errorf("磁盘大小不满足, 需要: %s, 实际: %s", human.Size(DISK_SIZE), human.Size(h.DiskSize))
|
||||||
if !o.ignoreDisk {
|
|
||||||
return fmt.Errorf("disk space check failed: %s", diskSpaceResult.Message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("✅ %s: %s", diskSpaceResult.Name, diskSpaceResult.Actual)
|
if h.DiskReadSpeed < DISK_READ_SPEED {
|
||||||
|
return fmt.Errorf("磁盘读速度不满足, 需要: %s, 实际: %s", human.Size(DISK_READ_SPEED), human.Size(h.DiskReadSpeed))
|
||||||
// 2. Check disk performance: write >= 500MB/s, read >= 500MB/s
|
|
||||||
logger.Info("☑️ installer.Check: Checking disk performance...")
|
|
||||||
diskPerfResult, err := syscheck.CheckDiskPerformance(ctx, i, 500, 500)
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("❌ installer.Check: Failed to check disk performance: %v", err)
|
|
||||||
return fmt.Errorf("failed to check disk performance: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !diskPerfResult.Passed {
|
if h.DiskWriteSpeed < DISK_WRITE_SPEED {
|
||||||
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", diskPerfResult.Name, diskPerfResult.Message, diskPerfResult.Expected, diskPerfResult.Actual)
|
return fmt.Errorf("磁盘写速度不满足, 需要: %s, 实际: %s", human.Size(DISK_WRITE_SPEED), human.Size(h.DiskWriteSpeed))
|
||||||
if !o.ignoreDisk {
|
|
||||||
return fmt.Errorf("disk performance check failed: %s", diskPerfResult.Message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("✅ %s: %s", diskPerfResult.Name, diskPerfResult.Actual)
|
PASS_MEMORY:
|
||||||
|
|
||||||
// 3. Check memory size: >= 15.5GB
|
if o.ignoreMemory || h.MemorySize+h.MemoryReadSpeed+h.MemoryWriteSpeed == -3 {
|
||||||
logger.Info("☑️ installer.Check: Checking memory size...")
|
goto PASS_CPU
|
||||||
memResult, err := syscheck.CheckMemory(ctx, i, 15.5)
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("❌ installer.Check: Failed to check memory: %v", err)
|
|
||||||
return fmt.Errorf("failed to check memory: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !memResult.Passed {
|
if h.MemorySize < MEMORY_SIZE {
|
||||||
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", memResult.Name, memResult.Message, memResult.Expected, memResult.Actual)
|
return fmt.Errorf("内存大小不满足, 需要: %s, 实际: %s", human.Size(MEMORY_SIZE), human.Size(h.MemorySize))
|
||||||
if !o.ignoreMemory {
|
|
||||||
return fmt.Errorf("memory check failed: %s", memResult.Message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("✅ %s: %s", memResult.Name, memResult.Actual)
|
if h.MemoryReadSpeed < MEMORY_READ_SPEED {
|
||||||
|
return fmt.Errorf("内存读速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_READ_SPEED), human.Size(h.MemoryReadSpeed))
|
||||||
// 4. Check CPU cores: >= 8
|
|
||||||
logger.Info("☑️ installer.Check: Checking CPU cores...")
|
|
||||||
cpuCoresResult, err := syscheck.CheckCPUCores(ctx, i, 8)
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("❌ installer.Check: Failed to check CPU cores: %v", err)
|
|
||||||
return fmt.Errorf("failed to check CPU cores: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cpuCoresResult.Passed {
|
if h.MemoryWriteSpeed < MEMORY_WRITE_SPEED {
|
||||||
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", cpuCoresResult.Name, cpuCoresResult.Message, cpuCoresResult.Expected, cpuCoresResult.Actual)
|
return fmt.Errorf("内存写速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_WRITE_SPEED), human.Size(h.MemoryWriteSpeed))
|
||||||
if !o.ignoreCPU {
|
|
||||||
return fmt.Errorf("CPU cores check failed: %s", cpuCoresResult.Message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("✅ %s: %s", cpuCoresResult.Name, cpuCoresResult.Actual)
|
PASS_CPU:
|
||||||
|
|
||||||
// 5. Check CPU frequency: >= 2GHz
|
if o.ignoreCPU || h.CPUCores+h.CPUFrequency == -2 {
|
||||||
logger.Info("☑️ installer.Check: Checking CPU frequency...")
|
goto END
|
||||||
cpuFreqResult, err := syscheck.CheckCPUFrequency(ctx, i, 2.0)
|
}
|
||||||
if err != nil {
|
if h.CPUCores < CPUCORES {
|
||||||
logger.Debug("❌ installer.Check: Failed to check CPU frequency: %v", err)
|
return fmt.Errorf("cpu 核心数不满足, 需要: %d, 实际: %d", CPUCORES, h.CPUCores)
|
||||||
return fmt.Errorf("failed to check CPU frequency: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cpuFreqResult.Passed {
|
if h.CPUFrequency < CPU_FREQUENCY {
|
||||||
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", cpuFreqResult.Name, cpuFreqResult.Message, cpuFreqResult.Expected, cpuFreqResult.Actual)
|
return fmt.Errorf("cpu 频率不满足, 需要: %s, 实际: %s", human.Size(CPU_FREQUENCY), human.Size(h.CPUFrequency))
|
||||||
if !o.ignoreCPU {
|
|
||||||
return fmt.Errorf("CPU frequency check failed: %s", cpuFreqResult.Message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("✅ %s: %s", cpuFreqResult.Name, cpuFreqResult.Actual)
|
if CPU_NEED_V2 && !h.CPUIsX86V2 {
|
||||||
|
return fmt.Errorf("cpu 不支持aes")
|
||||||
|
}
|
||||||
|
|
||||||
logger.Info("✅ installer.Check: All system checks passed successfully!")
|
if CPU_NEED_AES && !h.CPUSupportAES {
|
||||||
|
return fmt.Errorf("cpu 不支持 x86_v2s")
|
||||||
|
}
|
||||||
|
|
||||||
|
END:
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *installer) HardwareCheck(ctx context.Context, opts ...CheckOption) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
o = &checkOpt{}
|
||||||
|
now = time.Now()
|
||||||
|
result = &HardwareCheckResult{
|
||||||
|
Timestamp: now.UnixMilli(),
|
||||||
|
}
|
||||||
|
cpuInfo syscheck.CPUInfo
|
||||||
|
dir string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, fn := range opts {
|
||||||
|
fn(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("✅ 开始目标机器系统检测...")
|
||||||
|
|
||||||
|
if o.ignoreDisk {
|
||||||
|
logger.Warn("⚠️ 跳过磁盘检测")
|
||||||
|
result.DiskSize = -1
|
||||||
|
result.DiskReadSpeed = -1
|
||||||
|
result.DiskWriteSpeed = -1
|
||||||
|
goto CHECK_MEMORY
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.DiskSize, err = syscheck.GetDiskSpace(ctx); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to get disk space: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("💾 磁盘容量: %s", human.Size(result.DiskSize))
|
||||||
|
|
||||||
|
if result.DiskReadSpeed, result.DiskWriteSpeed, err = syscheck.GetDiskSpeed(ctx); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to get disk speed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("💾 磁盘速率: 读取(%s/每秒), 写入(%s/每秒)", human.Size(result.DiskReadSpeed), human.Size(result.DiskWriteSpeed))
|
||||||
|
|
||||||
|
CHECK_MEMORY:
|
||||||
|
|
||||||
|
if o.ignoreMemory {
|
||||||
|
logger.Warn("⚠️ 跳过内存检测")
|
||||||
|
result.MemorySize = -1
|
||||||
|
result.MemoryReadSpeed = -1
|
||||||
|
result.MemoryWriteSpeed = -1
|
||||||
|
goto CHECK_CPU
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.MemorySize, err = syscheck.GetMemorySpace(ctx); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to get memory size: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("💿 内存容量: %s", human.Size(result.MemorySize))
|
||||||
|
|
||||||
|
if result.MemoryReadSpeed, result.MemoryWriteSpeed, err = syscheck.GetMemorySpeed(ctx); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to get memory speed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("💿 内存速率: 读取(%s/每秒), 写入(%s/每秒)", human.Size(result.MemoryReadSpeed), human.Size(result.MemoryWriteSpeed))
|
||||||
|
|
||||||
|
CHECK_CPU:
|
||||||
|
if o.ignoreCPU {
|
||||||
|
logger.Warn("⚠️ 跳过 CPU 检测")
|
||||||
|
result.CPUCores = -1
|
||||||
|
result.CPUFrequency = -1
|
||||||
|
result.CPUSupportAES = false
|
||||||
|
result.CPUIsX86V2 = false
|
||||||
|
goto END
|
||||||
|
}
|
||||||
|
|
||||||
|
if cpuInfo, err = syscheck.GetCPUInfo(ctx); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to get CPU info: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.CPUCores = cpuInfo.Cores
|
||||||
|
result.CPUFrequency = cpuInfo.FrequencyMHz
|
||||||
|
result.CPUSupportAES = cpuInfo.SupportAES
|
||||||
|
result.CPUIsX86V2 = cpuInfo.IsX86V2
|
||||||
|
|
||||||
|
logger.Info("🧮 CPU 核心数: %d", result.CPUCores)
|
||||||
|
logger.Info("🧮 CPU 频率: %d Mhz", result.CPUFrequency)
|
||||||
|
logger.Info("🧮 CPU 支持 AES: %t", result.CPUSupportAES)
|
||||||
|
END:
|
||||||
|
|
||||||
|
if dir, err = os.UserHomeDir(); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to get user home directory: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = result.Write(filepath.Join(dir, ".hsv2-installation")); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to write installation file: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = result.Pass(opts...); err != nil {
|
||||||
|
logger.Error("❌ %s", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("✅ 检测完成")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*installer) CheckOK(ctx context.Context) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
h = &HardwareCheckResult{}
|
||||||
|
dir string
|
||||||
|
)
|
||||||
|
|
||||||
|
if dir, err = os.UserHomeDir(); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to get user home directory: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.Load(filepath.Join(dir, ".hsv2-installation")); err != nil {
|
||||||
|
logger.Debug("❌ installer.HardwareCheck: Failed to load installation file: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Pass()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,41 +2,27 @@ package installer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"strings"
|
||||||
|
|
||||||
"yizhisec.com/hsv2/forge/pkg/logger"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type installer struct {
|
type installer struct {
|
||||||
workdir string
|
workdir string
|
||||||
target string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *installer) buildCommand(ctx context.Context, cmds ...string) *exec.Cmd {
|
|
||||||
if len(cmds) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.target == "self" {
|
|
||||||
return exec.CommandContext(ctx, cmds[0], cmds[1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshArgs := append([]string{i.target}, cmds...)
|
|
||||||
return exec.CommandContext(ctx, "ssh", sshArgs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteCommand implements syscheck.CommandExecutor interface
|
// ExecuteCommand implements syscheck.CommandExecutor interface
|
||||||
func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string, error) {
|
func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string, error) {
|
||||||
cmd := i.buildCommand(ctx, cmds...)
|
fcs := lo.Filter(cmds, func(item string, _ int) bool { return item != "" })
|
||||||
if cmd == nil {
|
|
||||||
return "", fmt.Errorf("failed to build command")
|
if len(fcs) == 0 {
|
||||||
|
return "", fmt.Errorf("empty commands")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "sh", "-c", strings.Join(fcs, " "))
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("command failed: %w, output: %s", err, string(output))
|
return "", fmt.Errorf("command failed: %w, output: %s", err, string(output))
|
||||||
@@ -45,109 +31,6 @@ func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string,
|
|||||||
return string(output), nil
|
return string(output), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CopyFileOption func(*copyFileOptions)
|
func NewInstaller(workdir string) *installer {
|
||||||
type copyFileOptions struct {
|
return &installer{workdir: workdir}
|
||||||
addExecutable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func withCopyFileExecutable() func(*copyFileOptions) {
|
|
||||||
return func(o *copyFileOptions) {
|
|
||||||
o.addExecutable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *installer) copyFile(ctx context.Context, src, dst string, opts ...CopyFileOption) error {
|
|
||||||
logger.Debug("☑️ installer.copyFile: Copying file from %s to %s (target: %s)", src, dst, i.target)
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
o = ©FileOptions{}
|
|
||||||
srcFile, dstFile *os.File
|
|
||||||
srcInfo os.FileInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, fn := range opts {
|
|
||||||
fn(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.target == "self" {
|
|
||||||
// Simply copy file locally
|
|
||||||
logger.Debug("Copying file locally: %s -> %s", src, dst)
|
|
||||||
|
|
||||||
// Open source file
|
|
||||||
if srcFile, err = os.Open(src); err != nil {
|
|
||||||
logger.Error("❌ Failed to open source file %s: %v", src, err)
|
|
||||||
return fmt.Errorf("failed to open source file: %w", err)
|
|
||||||
}
|
|
||||||
defer srcFile.Close()
|
|
||||||
|
|
||||||
// Create destination directory if needed
|
|
||||||
dstDir := filepath.Dir(dst)
|
|
||||||
if err = os.MkdirAll(dstDir, 0755); err != nil {
|
|
||||||
logger.Error("❌ Failed to create destination directory %s: %v", dstDir, err)
|
|
||||||
return fmt.Errorf("failed to create destination directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create destination file
|
|
||||||
if dstFile, err = os.Create(dst); err != nil {
|
|
||||||
logger.Error("❌ Failed to create destination file %s: %v", dst, err)
|
|
||||||
return fmt.Errorf("failed to create destination file: %w", err)
|
|
||||||
}
|
|
||||||
defer dstFile.Close()
|
|
||||||
|
|
||||||
// Copy file content
|
|
||||||
if _, err = io.Copy(dstFile, srcFile); err != nil {
|
|
||||||
logger.Error("❌ Failed to copy file content: %v", err)
|
|
||||||
return fmt.Errorf("failed to copy file content: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get source file permissions
|
|
||||||
if srcInfo, err = os.Stat(src); err == nil {
|
|
||||||
if err = os.Chmod(dst, srcInfo.Mode()); err != nil {
|
|
||||||
logger.Debug("⚠️ Failed to set file permissions: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("✅ File copied locally: %s -> %s", src, dst)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy file via scp to remote target
|
|
||||||
logger.Debug("Copying file via scp: %s -> %s:%s", src, i.target, dst)
|
|
||||||
|
|
||||||
// Format: scp <src> <target>:<dst>
|
|
||||||
cmd := exec.CommandContext(ctx, "scp", src, fmt.Sprintf("%s:%s", i.target, dst))
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("❌ Failed to copy file via scp: %v, output: %s", err, string(output))
|
|
||||||
return fmt.Errorf("failed to copy file via scp: %w, output: %s", err, string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("✅ File copied via scp: %s -> %s:%s", src, i.target, dst)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *installer) targetOK(ctx context.Context) error {
|
|
||||||
cmd := i.buildCommand(ctx, "whoami")
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("❌ installer.targetOK: check target %s failed, err = %v", i.target, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(output) != "root\n" {
|
|
||||||
logger.Debug("❌ installer.targetOK: check target %s failed, output = %s", i.target, string(output))
|
|
||||||
return errors.New("target is not root user")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInstaller(workdir, target string) *installer {
|
|
||||||
if target == "" {
|
|
||||||
logger.Warn("🎯 NewInstaller: target empty, set to default(self)")
|
|
||||||
target = "self"
|
|
||||||
}
|
|
||||||
|
|
||||||
return &installer{workdir: workdir, target: target}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ package installer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"yizhisec.com/hsv2/forge/pkg/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type K0sOpt func(*k0sOpt)
|
type K0sOpt func(*k0sOpt)
|
||||||
@@ -41,83 +37,5 @@ func WithK0sWorkerTokenFile(filename string) K0sOpt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *installer) K0s(ctx context.Context, opts ...K0sOpt) error {
|
func (i *installer) K0s(ctx context.Context, opts ...K0sOpt) error {
|
||||||
var (
|
panic("plz impl")
|
||||||
err error
|
|
||||||
o = &k0sOpt{
|
|
||||||
Type: "controller",
|
|
||||||
controllerAsWorker: false,
|
|
||||||
WorkerTokenFile: "/etc/k0s/worker.token",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if err = i.targetOK(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range opts {
|
|
||||||
fn(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
binaries := []string{
|
|
||||||
"dependency/bin/k0s",
|
|
||||||
"dependency/bin/k9s", "dependency/bin/kubectl", "dependency/bin/helm"}
|
|
||||||
if err = i.checkFiles(binaries...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check image tar files:
|
|
||||||
images := []string{
|
|
||||||
"dependency/image/k0s.apiserver-network-proxy-agent.tar",
|
|
||||||
"dependency/image/k0s.cni-node.tar",
|
|
||||||
"dependency/image/k0s.coredns.tar",
|
|
||||||
"dependency/image/k0s.kube-proxy.tar",
|
|
||||||
"dependency/image/k0s.kube-router.tar",
|
|
||||||
"dependency/image/k0s.metrics-server.tar",
|
|
||||||
"dependency/image/k0s.pause.tar",
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = i.checkFiles(images...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy binaries to /usr/local/bin and add executable permissions
|
|
||||||
if err = i.copyFile(ctx, "dependency/bin/k0s", "/usr/local/bin/k0s", withCopyFileExecutable()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = i.copyFile(ctx, "dependency/bin/k9s", "/usr/local/bin/k9s", withCopyFileExecutable()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = i.copyFile(ctx, "dependency/bin/kubectl", "/usr/local/bin/kubectl", withCopyFileExecutable()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = i.copyFile(ctx, "dependency/bin/helm", "/usr/local/bin/helm", withCopyFileExecutable()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
i.ExecuteCommand(ctx, "k0s", "")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkBinaryFiles checks if the required binary files exist in the dependency/bin directory
|
|
||||||
func (i *installer) checkFiles(fileBaseName ...string) error {
|
|
||||||
logger.Info("☑️ installer.checkFiles: Checking files in %s...", i.workdir)
|
|
||||||
|
|
||||||
for _, file := range fileBaseName {
|
|
||||||
filename := filepath.Join(i.workdir, file)
|
|
||||||
logger.Debug("Checking file: %s", filename)
|
|
||||||
|
|
||||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
|
||||||
logger.Error("❌ File not found: %s", filename)
|
|
||||||
return fmt.Errorf("file not found: %s", filename)
|
|
||||||
} else if err != nil {
|
|
||||||
logger.Error("❌ Failed to check file %s: %v", filename, err)
|
|
||||||
return fmt.Errorf("failed to check file %s: %w", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("✅ File found: %s", file)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("✅ installer.checkBinaryFiles: All binary files verified successfully!")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ func (i *installer) Prepare(ctx context.Context) error {
|
|||||||
|
|
||||||
logger.Info("☑️ installer.Prepare: Starting system preparation...")
|
logger.Info("☑️ installer.Prepare: Starting system preparation...")
|
||||||
|
|
||||||
if err = i.targetOK(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Set timezone to Asia/Shanghai
|
// 1. Set timezone to Asia/Shanghai
|
||||||
logger.Info("☑️ installer.Prepare: Setting timezone to Asia/Shanghai...")
|
logger.Info("☑️ installer.Prepare: Setting timezone to Asia/Shanghai...")
|
||||||
if err = i.setTimezone(ctx); err != nil {
|
if err = i.setTimezone(ctx); err != nil {
|
||||||
@@ -65,31 +61,23 @@ func (i *installer) Prepare(ctx context.Context) error {
|
|||||||
|
|
||||||
// setTimezone sets the system timezone to Asia/Shanghai
|
// setTimezone sets the system timezone to Asia/Shanghai
|
||||||
func (i *installer) setTimezone(ctx context.Context) error {
|
func (i *installer) setTimezone(ctx context.Context) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
output string
|
||||||
|
)
|
||||||
// Check if timezone file exists
|
// Check if timezone file exists
|
||||||
cmd := i.buildCommand(ctx, "test", "-f", "/usr/share/zoneinfo/Asia/Shanghai")
|
if output, err = i.ExecuteCommand(ctx, "test", "-f", "/usr/share/zoneinfo/Asia/Shanghai"); err != nil {
|
||||||
if cmd == nil {
|
return fmt.Errorf("failed to set timezone, err =%s, raw = %s", err.Error(), output)
|
||||||
return fmt.Errorf("failed to build command")
|
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("timezone file /usr/share/zoneinfo/Asia/Shanghai not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old localtime link/file
|
// Remove old localtime link/file
|
||||||
cmd = i.buildCommand(ctx, "rm", "-f", "/etc/localtime")
|
if output, err = i.ExecuteCommand(ctx, "rm", "-f", "/etc/localtime"); err != nil {
|
||||||
if cmd == nil {
|
return fmt.Errorf("failed to set timezone, err =%s, raw = %s", err.Error(), output)
|
||||||
return fmt.Errorf("failed to build command")
|
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
logger.Debug("Failed to remove /etc/localtime: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create symlink
|
// Create symlink
|
||||||
cmd = i.buildCommand(ctx, "ln", "-s", "/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime")
|
if output, err = i.ExecuteCommand(ctx, "ln", "-s", "/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime"); err != nil {
|
||||||
if cmd == nil {
|
return fmt.Errorf("failed to set timezone, err =%s, raw = %s", err.Error(), output)
|
||||||
return fmt.Errorf("failed to build command")
|
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to create symlink: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -97,22 +85,19 @@ func (i *installer) setTimezone(ctx context.Context) error {
|
|||||||
|
|
||||||
// disableSwap disables all swap partitions and removes swap entries from /etc/fstab
|
// disableSwap disables all swap partitions and removes swap entries from /etc/fstab
|
||||||
func (i *installer) disableSwap(ctx context.Context) error {
|
func (i *installer) disableSwap(ctx context.Context) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
output string
|
||||||
|
)
|
||||||
|
|
||||||
// Turn off all swap
|
// Turn off all swap
|
||||||
cmd := i.buildCommand(ctx, "swapoff", "-a")
|
if output, err = i.ExecuteCommand(ctx, "swapoff", "-a"); err != nil {
|
||||||
if cmd == nil {
|
logger.Debug("Failed to swapoff: %v (may be already off), raw = %s", err, output)
|
||||||
return fmt.Errorf("failed to build command")
|
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
logger.Debug("Failed to swapoff: %v (may be already off)", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment out swap entries in /etc/fstab to make it persistent
|
// Comment out swap entries in /etc/fstab to make it persistent
|
||||||
cmd = i.buildCommand(ctx, "sed", "-i", "/swap/s/^/#/", "/etc/fstab")
|
if output, err = i.ExecuteCommand(ctx, "sed", "-i", "/swap/s/^/#/", "/etc/fstab"); err != nil {
|
||||||
if cmd == nil {
|
logger.Debug("Failed to comment swap in /etc/fstab: %v, raw = %s", err, output)
|
||||||
return fmt.Errorf("failed to build command")
|
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
logger.Debug("Failed to comment swap in /etc/fstab: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -120,23 +105,21 @@ func (i *installer) disableSwap(ctx context.Context) error {
|
|||||||
|
|
||||||
// loadKernelModule loads a kernel module and ensures it's loaded on boot
|
// loadKernelModule loads a kernel module and ensures it's loaded on boot
|
||||||
func (i *installer) loadKernelModule(ctx context.Context, moduleName string) error {
|
func (i *installer) loadKernelModule(ctx context.Context, moduleName string) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
output string
|
||||||
|
)
|
||||||
|
|
||||||
// Load the module immediately
|
// Load the module immediately
|
||||||
cmd := i.buildCommand(ctx, "modprobe", moduleName)
|
if output, err = i.ExecuteCommand(ctx, "modprobe", moduleName); err != nil {
|
||||||
if cmd == nil {
|
return fmt.Errorf("failed to load module %s: %w, raw = %s", moduleName, err, output)
|
||||||
return fmt.Errorf("failed to build command")
|
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to load module %s: %w", moduleName, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to /etc/modules-load.d/ to load on boot
|
// Add to /etc/modules-load.d/ to load on boot
|
||||||
filePath := fmt.Sprintf("/etc/modules-load.d/%s.conf", moduleName)
|
filePath := fmt.Sprintf("/etc/modules-load.d/%s.conf", moduleName)
|
||||||
cmd = i.buildCommand(ctx, "bash", "-c", fmt.Sprintf("echo '%s' > %s", moduleName, filePath))
|
command := fmt.Sprintf("echo '%s' > %s", moduleName, filePath)
|
||||||
if cmd == nil {
|
if output, err = i.ExecuteCommand(ctx, "bash", "-c", command); err != nil {
|
||||||
return fmt.Errorf("failed to build command")
|
logger.Debug("Failed to add module to modules-load.d: %v, raw = %s", err, output)
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
logger.Debug("Failed to add module to modules-load.d: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -144,6 +127,11 @@ func (i *installer) loadKernelModule(ctx context.Context, moduleName string) err
|
|||||||
|
|
||||||
// applySysctlSettings applies required sysctl settings for Kubernetes
|
// applySysctlSettings applies required sysctl settings for Kubernetes
|
||||||
func (i *installer) applySysctlSettings(ctx context.Context) error {
|
func (i *installer) applySysctlSettings(ctx context.Context) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
output string
|
||||||
|
)
|
||||||
|
|
||||||
const sysctlConfig = `# Kubernetes required settings
|
const sysctlConfig = `# Kubernetes required settings
|
||||||
net.bridge.bridge-nf-call-iptables = 1
|
net.bridge.bridge-nf-call-iptables = 1
|
||||||
net.bridge.bridge-nf-call-ip6tables = 1
|
net.bridge.bridge-nf-call-ip6tables = 1
|
||||||
@@ -163,21 +151,14 @@ net.ipv4.neigh.default.gc_thresh3 = 8192
|
|||||||
`
|
`
|
||||||
|
|
||||||
// Write sysctl config file
|
// Write sysctl config file
|
||||||
cmd := i.buildCommand(ctx, "bash", "-c", fmt.Sprintf("cat > /etc/sysctl.d/99-kubernetes.conf << 'EOF'\n%sEOF", sysctlConfig))
|
command := fmt.Sprintf("cat > /etc/sysctl.d/99-kubernetes.conf << 'EOF'\n%sEOF", sysctlConfig)
|
||||||
if cmd == nil {
|
if output, err = i.ExecuteCommand(ctx, "bash", "-c", command); err != nil {
|
||||||
return fmt.Errorf("failed to build command")
|
return fmt.Errorf("failed to write sysctl config: %w, raw = %s", err, output)
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to write sysctl config: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply sysctl settings
|
// Apply sysctl settings
|
||||||
cmd = i.buildCommand(ctx, "sysctl", "--system")
|
if output, err = i.ExecuteCommand(ctx, "sysctl", "--system"); err != nil {
|
||||||
if cmd == nil {
|
return fmt.Errorf("failed to apply sysctl settings: %w, raw = %s", err, output)
|
||||||
return fmt.Errorf("failed to build command")
|
|
||||||
}
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to apply sysctl settings: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -102,8 +102,24 @@ server {
|
|||||||
proxy_pass http://app-helper-service.hsv2/api/v2_2/user/profile/avatar/update;
|
proxy_pass http://app-helper-service.hsv2/api/v2_2/user/profile/avatar/update;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/admin/business-center/network-app/tunnel {
|
# create layer4 resource
|
||||||
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource/icon$request_uri;
|
location = /api/admin/business-center/network-app/tunnel {
|
||||||
|
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource4/create/icon$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
# update layer4 resource
|
||||||
|
location ~ ^/api/admin/business-center/network-app/tunnel/\d+/info$ {
|
||||||
|
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource4/update/icon$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
# create layer7 resource
|
||||||
|
location = /api/admin/strategy/osi-resource {
|
||||||
|
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource7/create/icon$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
# update layer7 resource
|
||||||
|
location ~ ^/api/admin/strategy/osi-resource/\d+/info$ {
|
||||||
|
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource7/update/icon$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/v2_1/user {
|
location /api/v2_1/user {
|
||||||
|
|||||||
74
pkg/syscheck/cpu.go
Normal file
74
pkg/syscheck/cpu.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package syscheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CPUInfo struct {
|
||||||
|
Cores int64
|
||||||
|
FrequencyMHz int64
|
||||||
|
SupportAES bool
|
||||||
|
IsX86V2 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCPUInfo(ctx context.Context) (CPUInfo, error) {
|
||||||
|
info := CPUInfo{
|
||||||
|
Cores: int64(runtime.NumCPU()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse /proc/cpuinfo to get CPU frequency and model info
|
||||||
|
file, err := os.Open("/proc/cpuinfo")
|
||||||
|
if err != nil {
|
||||||
|
return info, fmt.Errorf("failed to open /proc/cpuinfo: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
// Parse CPU MHz
|
||||||
|
if strings.HasPrefix(line, "cpu MHz") {
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
freqStr := strings.TrimSpace(parts[1])
|
||||||
|
if freq, err := strconv.ParseFloat(freqStr, 64); err == nil {
|
||||||
|
info.FrequencyMHz = int64(freq)
|
||||||
|
break // Get first CPU frequency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return info, fmt.Errorf("failed to read /proc/cpuinfo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CPU features using x/sys/cpu package
|
||||||
|
if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
|
||||||
|
// Check AES-NI support
|
||||||
|
info.SupportAES = cpu.X86.HasAES
|
||||||
|
|
||||||
|
// Check x86-64-v2 support
|
||||||
|
// x86-64-v2 requires: SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT
|
||||||
|
info.IsX86V2 = cpu.X86.HasSSE3 &&
|
||||||
|
cpu.X86.HasSSSE3 &&
|
||||||
|
cpu.X86.HasSSE41 &&
|
||||||
|
cpu.X86.HasSSE42 &&
|
||||||
|
cpu.X86.HasPOPCNT
|
||||||
|
} else {
|
||||||
|
// For ARM or other architectures
|
||||||
|
info.SupportAES = false
|
||||||
|
info.IsX86V2 = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
44
pkg/syscheck/cpu_test.go
Normal file
44
pkg/syscheck/cpu_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package syscheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCPUInfo(t *testing.T) {
|
||||||
|
info, err := GetCPUInfo(t.Context())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get CPU info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CPU cores
|
||||||
|
if info.Cores <= 0 {
|
||||||
|
t.Errorf("expected CPU cores > 0, got %d", info.Cores)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CPU frequency (should be reasonable, e.g., 500MHz - 10000MHz)
|
||||||
|
if info.FrequencyMHz <= 0 {
|
||||||
|
t.Errorf("expected CPU frequency > 0, got %.2f", info.FrequencyMHz)
|
||||||
|
}
|
||||||
|
if info.FrequencyMHz < 500 || info.FrequencyMHz > 10000 {
|
||||||
|
t.Logf("Warning: CPU frequency seems unusual: %.2f MHz", info.FrequencyMHz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log CPU information
|
||||||
|
t.Logf("CPU Cores: %d", info.Cores)
|
||||||
|
t.Logf("CPU Frequency: %.2f MHz (%.2f GHz)", info.FrequencyMHz, info.FrequencyMHz/1000)
|
||||||
|
t.Logf("AES-NI Support: %v", info.SupportAES)
|
||||||
|
t.Logf("x86-64-v2 Compatible: %v", info.IsX86V2)
|
||||||
|
|
||||||
|
// Log feature support
|
||||||
|
if info.SupportAES {
|
||||||
|
t.Log("✓ CPU supports AES-NI hardware acceleration")
|
||||||
|
} else {
|
||||||
|
t.Log("✗ CPU does not support AES-NI")
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsX86V2 {
|
||||||
|
t.Log("✓ CPU is x86-64-v2 compatible (SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT)")
|
||||||
|
} else {
|
||||||
|
t.Log("✗ CPU is not x86-64-v2 compatible")
|
||||||
|
}
|
||||||
|
}
|
||||||
157
pkg/syscheck/disk.go
Normal file
157
pkg/syscheck/disk.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package syscheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDiskSpace returns available disk space in bytes for the root partition
|
||||||
|
func GetDiskSpace(ctx context.Context) (int64, error) {
|
||||||
|
var stat unix.Statfs_t
|
||||||
|
if err := unix.Statfs("/", &stat); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get disk space: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available space = Available blocks * Block size
|
||||||
|
availableSpace := int64(stat.Bavail) * int64(stat.Bsize)
|
||||||
|
return availableSpace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiskSpeed measures disk read/write speed by writing and reading a 1GB test file
|
||||||
|
// Returns read speed and write speed in bytes per second
|
||||||
|
func GetDiskSpeed(ctx context.Context) (int64, int64, error) {
|
||||||
|
const (
|
||||||
|
testSize = 1024 * 1024 * 1024 // 1GB
|
||||||
|
bufferSize = 1024 * 1024 // 1MB buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
tmpFile := "/tmp/diskspeed_test_" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
|
defer func() {
|
||||||
|
// Clean up test file
|
||||||
|
_ = os.Remove(tmpFile)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Test write speed
|
||||||
|
writeSpeed, err := measureWriteSpeed(ctx, tmpFile, testSize, bufferSize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to measure write speed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test read speed
|
||||||
|
readSpeed, err := measureReadSpeed(ctx, tmpFile, bufferSize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to measure read speed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return readSpeed, writeSpeed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// measureWriteSpeed writes test data to a file and measures the speed
|
||||||
|
func measureWriteSpeed(ctx context.Context, filename string, totalSize, bufferSize int64) (int64, error) {
|
||||||
|
// Create file
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to create test file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Prepare buffer with random data
|
||||||
|
buffer := make([]byte, bufferSize)
|
||||||
|
if _, err := rand.Read(buffer); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to generate random data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timing
|
||||||
|
startTime := time.Now()
|
||||||
|
var written int64
|
||||||
|
|
||||||
|
// Write data in chunks
|
||||||
|
for written < totalSize {
|
||||||
|
// Check context cancellation
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write buffer
|
||||||
|
n, err := file.Write(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to write data: %w", err)
|
||||||
|
}
|
||||||
|
written += int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync to ensure data is written to disk
|
||||||
|
if err := file.Sync(); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to sync file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate speed
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
speed := int64(float64(written) / duration.Seconds())
|
||||||
|
|
||||||
|
return speed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// measureReadSpeed reads the test file and measures the speed
|
||||||
|
func measureReadSpeed(ctx context.Context, filename string, bufferSize int64) (int64, error) {
|
||||||
|
// Open file
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to open test file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Get file size
|
||||||
|
fileInfo, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to stat file: %w", err)
|
||||||
|
}
|
||||||
|
totalSize := fileInfo.Size()
|
||||||
|
|
||||||
|
// Prepare buffer
|
||||||
|
buffer := make([]byte, bufferSize)
|
||||||
|
|
||||||
|
// Start timing
|
||||||
|
startTime := time.Now()
|
||||||
|
var totalRead int64
|
||||||
|
|
||||||
|
// Read data in chunks
|
||||||
|
for {
|
||||||
|
// Check context cancellation
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read buffer
|
||||||
|
n, err := file.Read(buffer)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to read data: %w", err)
|
||||||
|
}
|
||||||
|
totalRead += int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate speed
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
speed := int64(float64(totalRead) / duration.Seconds())
|
||||||
|
|
||||||
|
// Verify we read the entire file
|
||||||
|
if totalRead != totalSize {
|
||||||
|
return 0, fmt.Errorf("read size mismatch: expected %d, got %d", totalSize, totalRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
return speed, nil
|
||||||
|
}
|
||||||
42
pkg/syscheck/disk_test.go
Normal file
42
pkg/syscheck/disk_test.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package syscheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetDiskSpace(t *testing.T) {
|
||||||
|
size, err := GetDiskSpace(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get disk space: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
t.Errorf("expected disk space to be greater than 0, got %d", size)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Available disk space: %d bytes (%.2f GB)", size, float64(size)/(1024*1024*1024))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDiskSpeed(t *testing.T) {
|
||||||
|
// Test with real disk I/O (warning: this writes 1GB to disk)
|
||||||
|
// Skip in short mode
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skipping disk speed test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, ws, err := GetDiskSpeed(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get disk speed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs <= 0 {
|
||||||
|
t.Errorf("expected read speed > 0, got %d", rs)
|
||||||
|
}
|
||||||
|
if ws <= 0 {
|
||||||
|
t.Errorf("expected write speed > 0, got %d", ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Read speed: %d bytes/s (%.2f MB/s)", rs, float64(rs)/(1024*1024))
|
||||||
|
t.Logf("Write speed: %d bytes/s (%.2f MB/s)", ws, float64(ws)/(1024*1024))
|
||||||
|
}
|
||||||
144
pkg/syscheck/mem.go
Normal file
144
pkg/syscheck/mem.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package syscheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetMemorySpace returns total physical memory (RAM) in bytes, excluding swap
|
||||||
|
func GetMemorySpace(ctx context.Context) (int64, error) {
|
||||||
|
var info unix.Sysinfo_t
|
||||||
|
if err := unix.Sysinfo(&info); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get memory info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total physical RAM (excluding swap)
|
||||||
|
// info.Totalram is in memory unit size (info.Unit)
|
||||||
|
totalMemory := int64(info.Totalram) * int64(info.Unit)
|
||||||
|
return totalMemory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemorySpeed measures memory read/write speed by allocating and accessing memory
|
||||||
|
// Returns read speed and write speed in bytes per second
|
||||||
|
func GetMemorySpeed(ctx context.Context) (int64, int64, error) {
|
||||||
|
const (
|
||||||
|
testSize = 512 * 1024 * 1024 // 512MB test size
|
||||||
|
iterations = 5 // Number of iterations for averaging
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test write speed
|
||||||
|
writeSpeed, err := measureMemoryWriteSpeed(ctx, testSize, iterations)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to measure memory write speed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test read speed
|
||||||
|
readSpeed, err := measureMemoryReadSpeed(ctx, testSize, iterations)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to measure memory read speed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return readSpeed, writeSpeed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// measureMemoryWriteSpeed measures memory write speed
|
||||||
|
func measureMemoryWriteSpeed(ctx context.Context, size int64, iterations int) (int64, error) {
|
||||||
|
var totalDuration time.Duration
|
||||||
|
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
// Check context cancellation
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate memory buffer
|
||||||
|
buffer := make([]byte, size)
|
||||||
|
|
||||||
|
// Generate random data
|
||||||
|
source := make([]byte, 1024*1024) // 1MB source buffer
|
||||||
|
if _, err := rand.Read(source); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to generate random data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timing
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
// Write data to memory buffer
|
||||||
|
for offset := int64(0); offset < size; offset += int64(len(source)) {
|
||||||
|
remaining := size - offset
|
||||||
|
if remaining < int64(len(source)) {
|
||||||
|
copy(buffer[offset:], source[:remaining])
|
||||||
|
} else {
|
||||||
|
copy(buffer[offset:offset+int64(len(source))], source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop timing
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
totalDuration += duration
|
||||||
|
|
||||||
|
// Force the buffer to be used to prevent optimization
|
||||||
|
_ = buffer[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate average speed
|
||||||
|
avgDuration := totalDuration / time.Duration(iterations)
|
||||||
|
speed := int64(float64(size) / avgDuration.Seconds())
|
||||||
|
|
||||||
|
return speed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// measureMemoryReadSpeed measures memory read speed
|
||||||
|
func measureMemoryReadSpeed(ctx context.Context, size int64, iterations int) (int64, error) {
|
||||||
|
var totalDuration time.Duration
|
||||||
|
|
||||||
|
// Pre-allocate and fill buffer
|
||||||
|
buffer := make([]byte, size)
|
||||||
|
if _, err := rand.Read(buffer); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to initialize buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
// Check context cancellation
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timing
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
// Read data from memory buffer
|
||||||
|
var sum int64
|
||||||
|
for offset := int64(0); offset < size; offset += 1024 {
|
||||||
|
// Read in chunks to simulate real access patterns
|
||||||
|
end := offset + 1024
|
||||||
|
if end > size {
|
||||||
|
end = size
|
||||||
|
}
|
||||||
|
for j := offset; j < end; j++ {
|
||||||
|
sum += int64(buffer[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop timing
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
totalDuration += duration
|
||||||
|
|
||||||
|
// Use sum to prevent optimization
|
||||||
|
_ = sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate average speed
|
||||||
|
avgDuration := totalDuration / time.Duration(iterations)
|
||||||
|
speed := int64(float64(size) / avgDuration.Seconds())
|
||||||
|
|
||||||
|
return speed, nil
|
||||||
|
}
|
||||||
42
pkg/syscheck/mem_test.go
Normal file
42
pkg/syscheck/mem_test.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package syscheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetMemorySpace(t *testing.T) {
|
||||||
|
size, err := GetMemorySpace(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get memory space: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
t.Errorf("expected memory space to be greater than 0, got %d", size)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Total physical memory: %d bytes (%.2f GB)", size, float64(size)/(1024*1024*1024))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMemorySpeed(t *testing.T) {
|
||||||
|
// Test memory speed (may take a few seconds)
|
||||||
|
// Skip in short mode
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skipping memory speed test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, ws, err := GetMemorySpeed(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get memory speed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs <= 0 {
|
||||||
|
t.Errorf("expected read speed > 0, got %d", rs)
|
||||||
|
}
|
||||||
|
if ws <= 0 {
|
||||||
|
t.Errorf("expected write speed > 0, got %d", ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Memory read speed: %d bytes/s (%.2f GB/s)", rs, float64(rs)/(1024*1024*1024))
|
||||||
|
t.Logf("Memory write speed: %d bytes/s (%.2f GB/s)", ws, float64(ws)/(1024*1024*1024))
|
||||||
|
}
|
||||||
@@ -1,318 +0,0 @@
|
|||||||
package syscheck
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CheckResult represents the result of a system check
|
|
||||||
type CheckResult struct {
|
|
||||||
Name string
|
|
||||||
Passed bool
|
|
||||||
Actual string
|
|
||||||
Expected string
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiskInfo represents disk information
|
|
||||||
type DiskInfo struct {
|
|
||||||
AvailableGB float64
|
|
||||||
WriteSpeed float64 // MB/s
|
|
||||||
ReadSpeed float64 // MB/s
|
|
||||||
}
|
|
||||||
|
|
||||||
// MemInfo represents memory information
|
|
||||||
type MemInfo struct {
|
|
||||||
TotalGB float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CPUInfo represents CPU information
|
|
||||||
type CPUInfo struct {
|
|
||||||
Cores int
|
|
||||||
FrequencyMHz float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandExecutor defines interface for executing commands
|
|
||||||
type CommandExecutor interface {
|
|
||||||
ExecuteCommand(ctx context.Context, cmds ...string) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDiskSpace checks if disk space meets minimum requirements
|
|
||||||
func CheckDiskSpace(ctx context.Context, executor CommandExecutor, minGB float64) (*CheckResult, error) {
|
|
||||||
// Use df to check available disk space on root partition
|
|
||||||
output, err := executor.ExecuteCommand(ctx, "df", "-BG", "/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check disk space: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
|
||||||
if len(lines) < 2 {
|
|
||||||
return nil, fmt.Errorf("unexpected df output format")
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(lines[1])
|
|
||||||
if len(fields) < 4 {
|
|
||||||
return nil, fmt.Errorf("unexpected df fields count")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse available space (4th field, format: "500G")
|
|
||||||
availableStr := strings.TrimSuffix(fields[3], "G")
|
|
||||||
available, err := strconv.ParseFloat(availableStr, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse available disk space: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &CheckResult{
|
|
||||||
Name: "Disk Space",
|
|
||||||
Passed: available >= minGB,
|
|
||||||
Actual: fmt.Sprintf("%.2f GB", available),
|
|
||||||
Expected: fmt.Sprintf(">= %.2f GB", minGB),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.Passed {
|
|
||||||
result.Message = fmt.Sprintf("Insufficient disk space: %.2f GB available, %.2f GB required", available, minGB)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDiskPerformance checks disk read/write performance
|
|
||||||
func CheckDiskPerformance(ctx context.Context, executor CommandExecutor, minWriteMBps, minReadMBps float64) (*CheckResult, error) {
|
|
||||||
// Use dd to test write performance
|
|
||||||
writeCmd := "dd if=/dev/zero of=/tmp/test_write bs=1M count=1024 oflag=direct 2>&1 | tail -1"
|
|
||||||
writeOutput, err := executor.ExecuteCommand(ctx, "bash", "-c", writeCmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check disk write performance: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse write speed from dd output (format: "... copied, X.XX s, XXX MB/s")
|
|
||||||
writeSpeed, err := parseDDSpeed(writeOutput)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse write speed: %w, output: %s", err, writeOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test read performance and clean up test file
|
|
||||||
readCmd := "dd if=/tmp/test_write of=/dev/null bs=1M count=1024 iflag=direct 2>&1 | tail -1; rm -f /tmp/test_write"
|
|
||||||
readOutput, err := executor.ExecuteCommand(ctx, "bash", "-c", readCmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check disk read performance: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse read speed from dd output
|
|
||||||
readSpeed, err := parseDDSpeed(readOutput)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse read speed: %w, output: %s", err, readOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
passed := writeSpeed >= minWriteMBps && readSpeed >= minReadMBps
|
|
||||||
result := &CheckResult{
|
|
||||||
Name: "Disk Performance",
|
|
||||||
Passed: passed,
|
|
||||||
Actual: fmt.Sprintf("Write: %.2f MB/s, Read: %.2f MB/s", writeSpeed, readSpeed),
|
|
||||||
Expected: fmt.Sprintf("Write: >= %.2f MB/s, Read: >= %.2f MB/s", minWriteMBps, minReadMBps),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !passed {
|
|
||||||
result.Message = fmt.Sprintf("Insufficient disk performance")
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDDSpeed parses the speed from dd command output
|
|
||||||
// Expected format: "104857600 bytes (105 MB, 100 MiB) copied, 0.125749 s, 834 MB/s"
|
|
||||||
func parseDDSpeed(output string) (float64, error) {
|
|
||||||
output = strings.TrimSpace(output)
|
|
||||||
if output == "" {
|
|
||||||
return 0, fmt.Errorf("empty output")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the last occurrence of "MB/s" or "GB/s"
|
|
||||||
var speed float64
|
|
||||||
var unit string
|
|
||||||
|
|
||||||
// Try to match "XXX MB/s" or "XXX GB/s" pattern
|
|
||||||
if idx := strings.LastIndex(output, " MB/s"); idx != -1 {
|
|
||||||
// Extract the number before " MB/s"
|
|
||||||
fields := strings.Fields(output[:idx])
|
|
||||||
if len(fields) == 0 {
|
|
||||||
return 0, fmt.Errorf("no speed value found")
|
|
||||||
}
|
|
||||||
speedStr := fields[len(fields)-1]
|
|
||||||
var err error
|
|
||||||
speed, err = strconv.ParseFloat(speedStr, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to parse speed value '%s': %w", speedStr, err)
|
|
||||||
}
|
|
||||||
unit = "MB/s"
|
|
||||||
} else if idx := strings.LastIndex(output, " GB/s"); idx != -1 {
|
|
||||||
// Extract the number before " GB/s"
|
|
||||||
fields := strings.Fields(output[:idx])
|
|
||||||
if len(fields) == 0 {
|
|
||||||
return 0, fmt.Errorf("no speed value found")
|
|
||||||
}
|
|
||||||
speedStr := fields[len(fields)-1]
|
|
||||||
var err error
|
|
||||||
speed, err = strconv.ParseFloat(speedStr, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to parse speed value '%s': %w", speedStr, err)
|
|
||||||
}
|
|
||||||
unit = "GB/s"
|
|
||||||
speed *= 1024 // Convert GB/s to MB/s
|
|
||||||
} else {
|
|
||||||
return 0, fmt.Errorf("no MB/s or GB/s found in output")
|
|
||||||
}
|
|
||||||
|
|
||||||
if unit == "MB/s" || unit == "GB/s" {
|
|
||||||
return speed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("unexpected unit: %s", unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckMemory checks if system memory meets minimum requirements
|
|
||||||
func CheckMemory(ctx context.Context, executor CommandExecutor, minGB float64) (*CheckResult, error) {
|
|
||||||
// Use free -m to check memory in MB for better precision
|
|
||||||
output, err := executor.ExecuteCommand(ctx, "free", "-m")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check memory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
|
||||||
if len(lines) < 2 {
|
|
||||||
return nil, fmt.Errorf("unexpected free output format")
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(lines[1])
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return nil, fmt.Errorf("unexpected free fields count")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse total memory in MB
|
|
||||||
totalMB, err := strconv.ParseFloat(fields[1], 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse memory size: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert MB to GB (1 GB = 1024 MB)
|
|
||||||
totalGB := totalMB / 1024.0
|
|
||||||
|
|
||||||
result := &CheckResult{
|
|
||||||
Name: "Memory Size",
|
|
||||||
Passed: totalGB >= minGB,
|
|
||||||
Actual: fmt.Sprintf("%.2f GB", totalGB),
|
|
||||||
Expected: fmt.Sprintf(">= %.2f GB", minGB),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.Passed {
|
|
||||||
result.Message = fmt.Sprintf("Insufficient memory: %.2f GB available, %.2f GB required", totalGB, minGB)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckCPUCores checks if CPU core count meets minimum requirements
|
|
||||||
func CheckCPUCores(ctx context.Context, executor CommandExecutor, minCores int) (*CheckResult, error) {
|
|
||||||
// Read /proc/cpuinfo to get CPU core count (more universal than nproc)
|
|
||||||
output, err := executor.ExecuteCommand(ctx, "cat", "/proc/cpuinfo")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check CPU cores: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the number of "processor" lines
|
|
||||||
cores := 0
|
|
||||||
lines := strings.Split(output, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.HasPrefix(strings.TrimSpace(line), "processor") {
|
|
||||||
cores++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cores == 0 {
|
|
||||||
return nil, fmt.Errorf("failed to parse CPU cores from /proc/cpuinfo")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &CheckResult{
|
|
||||||
Name: "CPU Cores",
|
|
||||||
Passed: cores >= minCores,
|
|
||||||
Actual: fmt.Sprintf("%d cores", cores),
|
|
||||||
Expected: fmt.Sprintf(">= %d cores", minCores),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.Passed {
|
|
||||||
result.Message = fmt.Sprintf("Insufficient CPU cores: %d cores available, %d cores required", cores, minCores)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckCPUFrequency checks if CPU frequency meets minimum requirements
|
|
||||||
func CheckCPUFrequency(ctx context.Context, executor CommandExecutor, minGHz float64) (*CheckResult, error) {
|
|
||||||
// Read /proc/cpuinfo to get CPU frequency (more universal than lscpu)
|
|
||||||
output, err := executor.ExecuteCommand(ctx, "cat", "/proc/cpuinfo")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check CPU frequency: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxFreqMHz float64
|
|
||||||
lines := strings.Split(output, "\n")
|
|
||||||
|
|
||||||
// Try to parse from "cpu MHz" field (runtime frequency)
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "cpu MHz") {
|
|
||||||
fields := strings.Split(line, ":")
|
|
||||||
if len(fields) >= 2 {
|
|
||||||
freqStr := strings.TrimSpace(fields[1])
|
|
||||||
freq, err := strconv.ParseFloat(freqStr, 64)
|
|
||||||
if err == nil && freq > maxFreqMHz {
|
|
||||||
maxFreqMHz = freq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not found, try to parse from "model name" field (base frequency)
|
|
||||||
if maxFreqMHz == 0 {
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "model name") {
|
|
||||||
// Look for pattern like "@ 2.60GHz"
|
|
||||||
if idx := strings.Index(line, "@"); idx != -1 {
|
|
||||||
freqPart := line[idx+1:]
|
|
||||||
// Extract GHz value
|
|
||||||
if ghzIdx := strings.Index(freqPart, "GHz"); ghzIdx != -1 {
|
|
||||||
freqStr := strings.TrimSpace(freqPart[:ghzIdx])
|
|
||||||
freqGHz, err := strconv.ParseFloat(freqStr, 64)
|
|
||||||
if err == nil {
|
|
||||||
maxFreqMHz = freqGHz * 1000.0
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if maxFreqMHz == 0 {
|
|
||||||
return nil, fmt.Errorf("failed to parse CPU frequency from /proc/cpuinfo")
|
|
||||||
}
|
|
||||||
|
|
||||||
freqGHz := maxFreqMHz / 1000.0
|
|
||||||
minMHz := minGHz * 1000.0
|
|
||||||
|
|
||||||
result := &CheckResult{
|
|
||||||
Name: "CPU Frequency",
|
|
||||||
Passed: maxFreqMHz >= minMHz,
|
|
||||||
Actual: fmt.Sprintf("%.2f GHz", freqGHz),
|
|
||||||
Expected: fmt.Sprintf(">= %.2f GHz", minGHz),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.Passed {
|
|
||||||
result.Message = fmt.Sprintf("Insufficient CPU frequency: %.2f GHz available, %.2f GHz required", freqGHz, minGHz)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
50
pkg/tool/human/size.go
Normal file
50
pkg/tool/human/size.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package human
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func Duration(nano int64) string {
|
||||||
|
duration := float64(nano)
|
||||||
|
unit := "ns"
|
||||||
|
if duration >= 1000 {
|
||||||
|
duration /= 1000
|
||||||
|
unit = "us"
|
||||||
|
}
|
||||||
|
|
||||||
|
if duration >= 1000 {
|
||||||
|
duration /= 1000
|
||||||
|
unit = "ms"
|
||||||
|
}
|
||||||
|
|
||||||
|
if duration >= 1000 {
|
||||||
|
duration /= 1000
|
||||||
|
unit = " s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%6.2f%s", duration, unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Size(size int64) string {
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
KB = 1 << (10 * iota) // 1 KB = 1024 bytes
|
||||||
|
MB // 1 MB = 1024 KB
|
||||||
|
GB // 1 GB = 1024 MB
|
||||||
|
TB // 1 TB = 1024 GB
|
||||||
|
PB // 1 PB = 1024 TB
|
||||||
|
)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case size >= PB:
|
||||||
|
return fmt.Sprintf("%.2f PB", float64(size)/PB)
|
||||||
|
case size >= TB:
|
||||||
|
return fmt.Sprintf("%.2f TB", float64(size)/TB)
|
||||||
|
case size >= GB:
|
||||||
|
return fmt.Sprintf("%.2f GB", float64(size)/GB)
|
||||||
|
case size >= MB:
|
||||||
|
return fmt.Sprintf("%.2f MB", float64(size)/MB)
|
||||||
|
case size >= KB:
|
||||||
|
return fmt.Sprintf("%.2f KB", float64(size)/KB)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d bytes", size)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user