1.4.5.2
This commit is contained in:
184
EdgeNode/internal/utils/fs/ssd.go
Normal file
184
EdgeNode/internal/utils/fs/ssd.go
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fsutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// StorageType 存储类型
|
||||
type StorageType int
|
||||
|
||||
const (
|
||||
StorageTypeUnknown StorageType = 0
|
||||
StorageTypeSSD StorageType = 1
|
||||
StorageTypeHDD StorageType = 2
|
||||
)
|
||||
|
||||
var (
|
||||
ssdCache = map[string]StorageType{} // path => StorageType
|
||||
ssdCacheLock sync.RWMutex
|
||||
)
|
||||
|
||||
// IsSSD 检测指定路径是否为 SSD
|
||||
// 方法:
|
||||
// 1. 检查 /sys/block/*/queue/rotational(Linux)
|
||||
// 2. 使用 lsblk 命令(Linux)
|
||||
// 3. 检查设备名称(nvme, ssd 等关键词)
|
||||
func IsSSD(path string) (bool, error) {
|
||||
storageType, err := DetectStorageType(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return storageType == StorageTypeSSD, nil
|
||||
}
|
||||
|
||||
// DetectStorageType 检测存储类型
|
||||
func DetectStorageType(path string) (StorageType, error) {
|
||||
// 检查缓存
|
||||
ssdCacheLock.RLock()
|
||||
if cachedType, ok := ssdCache[path]; ok {
|
||||
ssdCacheLock.RUnlock()
|
||||
return cachedType, nil
|
||||
}
|
||||
ssdCacheLock.RUnlock()
|
||||
|
||||
// 获取绝对路径
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return StorageTypeUnknown, err
|
||||
}
|
||||
|
||||
// 获取设备路径
|
||||
devicePath, err := getDevicePath(absPath)
|
||||
if err != nil {
|
||||
return StorageTypeUnknown, err
|
||||
}
|
||||
|
||||
// 方法 1:检查 /sys/block/*/queue/rotational(Linux)
|
||||
if isSSD, err := checkRotational(devicePath); err == nil {
|
||||
storageType := StorageTypeHDD
|
||||
if isSSD {
|
||||
storageType = StorageTypeSSD
|
||||
}
|
||||
ssdCacheLock.Lock()
|
||||
ssdCache[path] = storageType
|
||||
ssdCacheLock.Unlock()
|
||||
return storageType, nil
|
||||
}
|
||||
|
||||
// 方法 2:使用 lsblk 命令(Linux)
|
||||
if isSSD, err := checkWithLsblk(devicePath); err == nil {
|
||||
storageType := StorageTypeHDD
|
||||
if isSSD {
|
||||
storageType = StorageTypeSSD
|
||||
}
|
||||
ssdCacheLock.Lock()
|
||||
ssdCache[path] = storageType
|
||||
ssdCacheLock.Unlock()
|
||||
return storageType, nil
|
||||
}
|
||||
|
||||
// 方法 3:检查设备名称(nvme, ssd 等关键词)
|
||||
if isSSD := checkDeviceName(devicePath); isSSD {
|
||||
ssdCacheLock.Lock()
|
||||
ssdCache[path] = StorageTypeSSD
|
||||
ssdCacheLock.Unlock()
|
||||
return StorageTypeSSD, nil
|
||||
}
|
||||
|
||||
// 默认返回未知
|
||||
ssdCacheLock.Lock()
|
||||
ssdCache[path] = StorageTypeUnknown
|
||||
ssdCacheLock.Unlock()
|
||||
return StorageTypeUnknown, nil
|
||||
}
|
||||
|
||||
// getDevicePath 获取设备路径
|
||||
func getDevicePath(path string) (string, error) {
|
||||
// 从路径提取设备名(简化实现)
|
||||
// 实际应该通过 stat 获取设备信息
|
||||
// 这里使用路径的第一级目录作为设备标识
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
if len(parts) > 0 {
|
||||
return parts[0], nil
|
||||
}
|
||||
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
|
||||
// checkRotational 检查 /sys/block/*/queue/rotational
|
||||
func checkRotational(devicePath string) (bool, error) {
|
||||
// 提取设备名(如 sda, nvme0n1)
|
||||
deviceName := extractDeviceName(devicePath)
|
||||
if len(deviceName) == 0 {
|
||||
return false, os.ErrNotExist
|
||||
}
|
||||
|
||||
// 检查 /sys/block/{device}/queue/rotational
|
||||
rotationalPath := "/sys/block/" + deviceName + "/queue/rotational"
|
||||
data, err := os.ReadFile(rotationalPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 0 表示 SSD,1 表示 HDD
|
||||
value := strings.TrimSpace(string(data))
|
||||
return value == "0", nil
|
||||
}
|
||||
|
||||
// checkWithLsblk 使用 lsblk 命令检查
|
||||
func checkWithLsblk(devicePath string) (bool, error) {
|
||||
cmd := exec.Command("lsblk", "-d", "-o", "name,rota", "-n")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
deviceName := extractDeviceName(devicePath)
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 && fields[0] == deviceName {
|
||||
// rota=0 表示 SSD,rota=1 表示 HDD
|
||||
return fields[1] == "0", nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, os.ErrNotExist
|
||||
}
|
||||
|
||||
// checkDeviceName 检查设备名称
|
||||
func checkDeviceName(devicePath string) bool {
|
||||
deviceName := strings.ToLower(extractDeviceName(devicePath))
|
||||
|
||||
// 检查常见 SSD 关键词
|
||||
ssdKeywords := []string{"nvme", "ssd", "flash"}
|
||||
for _, keyword := range ssdKeywords {
|
||||
if strings.Contains(deviceName, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// extractDeviceName 从路径提取设备名
|
||||
func extractDeviceName(path string) string {
|
||||
// 简化实现:从路径中提取可能的设备名
|
||||
// 实际应该通过更可靠的方法获取
|
||||
parts := strings.Split(path, "/")
|
||||
for _, part := range parts {
|
||||
if len(part) > 0 && (strings.HasPrefix(part, "sd") ||
|
||||
strings.HasPrefix(part, "nvme") ||
|
||||
strings.HasPrefix(part, "hd") ||
|
||||
strings.HasPrefix(part, "vd")) {
|
||||
return part
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user