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.
161 lines
4.5 KiB
161 lines
4.5 KiB
package task
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
|
)
|
|
|
|
// TaskDiscoveryEngine discovers volumes that need maintenance tasks
|
|
type TaskDiscoveryEngine struct {
|
|
masterClient *wdclient.MasterClient
|
|
scanInterval time.Duration
|
|
ecDetector *ECDetector
|
|
vacuumDetector *VacuumDetector
|
|
}
|
|
|
|
// NewTaskDiscoveryEngine creates a new task discovery engine
|
|
func NewTaskDiscoveryEngine(masterClient *wdclient.MasterClient, scanInterval time.Duration) *TaskDiscoveryEngine {
|
|
return &TaskDiscoveryEngine{
|
|
masterClient: masterClient,
|
|
scanInterval: scanInterval,
|
|
ecDetector: NewECDetector(),
|
|
vacuumDetector: NewVacuumDetector(),
|
|
}
|
|
}
|
|
|
|
// ScanForTasks scans for volumes that need maintenance tasks
|
|
func (tde *TaskDiscoveryEngine) ScanForTasks() ([]*VolumeCandidate, error) {
|
|
var candidates []*VolumeCandidate
|
|
|
|
// Get cluster topology and volume information
|
|
volumeInfos, err := tde.getVolumeInformation()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Scan for EC candidates
|
|
ecCandidates, err := tde.ecDetector.DetectECCandidates(volumeInfos)
|
|
if err != nil {
|
|
glog.Errorf("EC detection failed: %v", err)
|
|
} else {
|
|
candidates = append(candidates, ecCandidates...)
|
|
}
|
|
|
|
// Scan for vacuum candidates
|
|
vacuumCandidates, err := tde.vacuumDetector.DetectVacuumCandidates(volumeInfos)
|
|
if err != nil {
|
|
glog.Errorf("Vacuum detection failed: %v", err)
|
|
} else {
|
|
candidates = append(candidates, vacuumCandidates...)
|
|
}
|
|
|
|
glog.V(1).Infof("Task discovery found %d candidates (%d EC, %d vacuum)",
|
|
len(candidates), len(ecCandidates), len(vacuumCandidates))
|
|
|
|
return candidates, nil
|
|
}
|
|
|
|
// getVolumeInformation retrieves volume information from master
|
|
func (tde *TaskDiscoveryEngine) getVolumeInformation() ([]*VolumeInfo, error) {
|
|
var volumeInfos []*VolumeInfo
|
|
|
|
err := tde.masterClient.WithClient(false, func(client master_pb.SeaweedClient) error {
|
|
resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.TopologyInfo != nil {
|
|
for _, dc := range resp.TopologyInfo.DataCenterInfos {
|
|
for _, rack := range dc.RackInfos {
|
|
for _, node := range rack.DataNodeInfos {
|
|
for _, diskInfo := range node.DiskInfos {
|
|
for _, volInfo := range diskInfo.VolumeInfos {
|
|
volumeInfo := &VolumeInfo{
|
|
ID: volInfo.Id,
|
|
Size: volInfo.Size,
|
|
Collection: volInfo.Collection,
|
|
FileCount: volInfo.FileCount,
|
|
DeleteCount: volInfo.DeleteCount,
|
|
DeletedByteCount: volInfo.DeletedByteCount,
|
|
ReadOnly: volInfo.ReadOnly,
|
|
Server: node.Id,
|
|
DataCenter: dc.Id,
|
|
Rack: rack.Id,
|
|
DiskType: volInfo.DiskType,
|
|
ModifiedAtSecond: volInfo.ModifiedAtSecond,
|
|
RemoteStorageKey: volInfo.RemoteStorageKey,
|
|
}
|
|
volumeInfos = append(volumeInfos, volumeInfo)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return volumeInfos, err
|
|
}
|
|
|
|
// VolumeInfo contains detailed volume information
|
|
type VolumeInfo struct {
|
|
ID uint32
|
|
Size uint64
|
|
Collection string
|
|
FileCount uint64
|
|
DeleteCount uint64
|
|
DeletedByteCount uint64
|
|
ReadOnly bool
|
|
Server string
|
|
DataCenter string
|
|
Rack string
|
|
DiskType string
|
|
ModifiedAtSecond int64
|
|
RemoteStorageKey string
|
|
}
|
|
|
|
// GetUtilization calculates volume utilization percentage
|
|
func (vi *VolumeInfo) GetUtilization() float64 {
|
|
if vi.Size == 0 {
|
|
return 0.0
|
|
}
|
|
// Assuming max volume size of 30GB
|
|
maxSize := uint64(30 * 1024 * 1024 * 1024)
|
|
return float64(vi.Size) / float64(maxSize) * 100.0
|
|
}
|
|
|
|
// GetGarbageRatio calculates the garbage ratio
|
|
func (vi *VolumeInfo) GetGarbageRatio() float64 {
|
|
if vi.Size == 0 {
|
|
return 0.0
|
|
}
|
|
return float64(vi.DeletedByteCount) / float64(vi.Size)
|
|
}
|
|
|
|
// GetIdleTime calculates how long the volume has been idle
|
|
func (vi *VolumeInfo) GetIdleTime() time.Duration {
|
|
lastModified := time.Unix(vi.ModifiedAtSecond, 0)
|
|
return time.Since(lastModified)
|
|
}
|
|
|
|
// IsECCandidate checks if volume is a candidate for EC
|
|
func (vi *VolumeInfo) IsECCandidate() bool {
|
|
return !vi.ReadOnly &&
|
|
vi.GetUtilization() >= 95.0 &&
|
|
vi.GetIdleTime() > time.Hour &&
|
|
vi.RemoteStorageKey == "" // Not already EC'd
|
|
}
|
|
|
|
// IsVacuumCandidate checks if volume is a candidate for vacuum
|
|
func (vi *VolumeInfo) IsVacuumCandidate() bool {
|
|
return !vi.ReadOnly &&
|
|
vi.GetGarbageRatio() >= 0.3 &&
|
|
vi.DeleteCount > 0
|
|
}
|